-
-
Notifications
You must be signed in to change notification settings - Fork 354
/
HaloMorph.class.st
1456 lines (1179 loc) · 44.7 KB
/
HaloMorph.class.st
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
"
This morph provides a halo of handles for its target morph. Dragging, duplicating, rotating, and resizing to be done by mousing down on the appropriate handle. There are also handles for help and for a menu of infrequently used operations.
"
Class {
#name : #HaloMorph,
#superclass : #Morph,
#instVars : [
'target',
'innerTarget',
'positionOffset',
'angleOffset',
'growingOrRotating',
'directionArrowAnchor',
'haloBox',
'originalExtent',
'nameMorph'
],
#classVars : [
'CurrentHaloSpecifications',
'HaloEnclosesFullBounds',
'HaloWithDebugHandle',
'ShowBoundsInHalo'
],
#category : #'Morphic-Base-Widgets'
}
{ #category : #'halo theme' }
HaloMorph class >> allHaloSpecsFromArray: anArray [
^ anArray collect: [:quin | self haloSpecFromArray: quin]
]
{ #category : #'halo theme' }
HaloMorph class >> classicHaloSpecifications [
"Non-iconic halos with traditional placements"
"
self installHaloTheme: #classicHaloSpecifications
will result in the standard default halos being reinstalled"
"NB: listed below in clockwise order"
^ #(
" selector horiz vert color info icon key
--------- ------ ----------- ------------------------------- ---------------"
(addMenuHandle: left top (red) none)
(addDismissHandle: leftCenter top (red muchLighter) #haloDismissIcon)
(addGrabHandle: center top (black) none)
(addDragHandle: rightCenter top (brown) none)
(addDupHandle: right top (green) none)
(addDebugHandle: right topCenter (blue veryMuchLighter) none)
(addGrowHandle: right bottom (yellow) none)
(addScaleHandle: right bottom (lightOrange) none)
(addFontEmphHandle: rightCenter bottom (lightBrown darker) none)
(addFontStyleHandle: center bottom (lightRed) none)
(addFontSizeHandle: leftCenter bottom (lightGreen) none)
(addRecolorHandle: right bottomCenter (magenta darker) none)
(addRotateHandle: left bottom (blue) none))
]
{ #category : #'halo theme' }
HaloMorph class >> currentHaloSpecifications [
^ CurrentHaloSpecifications ifNil: [self installHaloTheme: #iconicHaloSpecifications]
]
{ #category : #'halo theme' }
HaloMorph class >> customHaloSpecifications [
"Intended for you to modify to suit your personal preference. What is implemented in the default here is just a skeleton; in comment at the bottom of this method are some useful lines you may wish to paste in to the main body here, possibly modifying positions, colors, etc..
Note that in this example, we include:
Dismiss handle, at top-left
Menu handle, at top-right
Resize handle, at bottom-right
Rotate handle, at bottom-left
Drag handle, at top-center
Recolor handle, at left-center. (this one is NOT part of the standard formulary --
it is included here to illustrate how to
add non-standard halos)
Note that the optional handles for specialized morphs, such as Sketch, Text, PasteUp, are also included"
^ #(
(addDismissHandle: left top (red muchLighter) #haloDismissIcon)
(addMenuHandle: right top (red) #haloMenuIcon)
(addDragHandle: center top (brown) #haloDragIcon)
(addGrowHandle: right bottom (yellow) #haloScaleIcon)
(addScaleHandle: right bottom (lightOrange) #haloScaleIcon)
(addRecolorHandle: left center (green muchLighter lighter) #haloRecolorIcon)
(addFontSizeHandle: leftCenter bottom (lightGreen) #haloFontSizeIcon)
(addFontStyleHandle: center bottom (lightRed) #haloFontStyleIcon)
(addFontEmphHandle: rightCenter bottom (lightBrown darker) #haloFontEmphIcon)
(addRotateHandle: left bottom (blue) #haloRotIcon)
(addDebugHandle: right topCenter (blue veryMuchLighter) #haloDebugIcon) )
" Other useful handles...
selector horiz vert color info icon key
--------- ------ ----------- ------------------------------- ---------------
(addTileHandle: left bottomCenter (lightBrown) #haloTileIcon)
(addViewHandle: left center (cyan) #haloViewIcon)
(addGrabHandle: center top (black) #haloGrabIcon)
(addDragHandle: rightCenter top (brown) #haloDragIcon)
(addDupHandle: right top (green) #haloDupIcon)
(addHelpHandle: center bottom (lightBlue) #haloHelpIcon)
(addFewerHandlesHandle: left topCenter (paleBuff) #haloFewerHandlesIcon)
(addPaintBgdHandle: right center (lightGray) #haloPaintIcon)
"
]
{ #category : #settings }
HaloMorph class >> haloEnclosesFullBounds [
^ HaloEnclosesFullBounds ifNil: [HaloEnclosesFullBounds := false]
]
{ #category : #settings }
HaloMorph class >> haloEnclosesFullBounds: aBoolean [
HaloEnclosesFullBounds := aBoolean
]
{ #category : #'halo theme' }
HaloMorph class >> haloSpecFromArray: anArray [
| aColor |
aColor := Color.
anArray fourth
do: [:sel | aColor := aColor perform: sel].
^ HaloSpec new
horizontalPlacement: anArray second
verticalPlacement: anArray third
color: aColor
iconSymbol: anArray fifth
addHandleSelector: anArray first
]
{ #category : #'halo theme' }
HaloMorph class >> haloSpecificationsForWorld [
| desired |
"Answer a list of HaloSpecs that describe which halos
are to be used on a world halo, what they should
look like, and where they should be situated"
desired := #(addDebugHandle: addMenuHandle: addHelpHandle: addRecolorHandle:).
^ self currentHaloSpecifications
select: [:spec | desired includes: spec addHandleSelector]
]
{ #category : #settings }
HaloMorph class >> haloWithDebugHandle [
^ HaloWithDebugHandle ifNil: [HaloWithDebugHandle := true]
]
{ #category : #settings }
HaloMorph class >> haloWithDebugHandle: aBoolean [
HaloWithDebugHandle := aBoolean
]
{ #category : #'halo theme' }
HaloMorph class >> iconicHaloSpecifications [
"Answer an array that characterizes the locations, colors, icons, and selectors of the halo handles that may be used in the iconic halo scheme"
"self installHaloTheme: #iconicHaloSpecifications"
^ #(
" selector horiz vert color info icon key
--------- ------ ----------- ------------------------------- ---------------"
(addCollapseHandle: left topCenter (tan) #haloCollapseIcon)
(addDebugHandle: right topCenter (blue veryMuchLighter) #haloDebugIcon)
(addDismissHandle: left top (red muchLighter) #haloDismissIcon)
(addRotateHandle: left bottom (blue) #haloRotIcon)
(addMenuHandle: leftCenter top (red) #haloMenuIcon)
(addGrabHandle: center top (black) #haloGrabIcon)
(addDragHandle: rightCenter top (brown) #haloDragIcon)
(addDupHandle: right top (green) #haloDupIcon)
(addHelpHandle: center bottom (lightBlue) #haloHelpIcon)
(addGrowHandle: right bottom (yellow) #haloScaleIcon)
(addScaleHandle: right bottom (lightOrange) #haloScaleIcon)
(addFontSizeHandle: leftCenter bottom (lightGreen) #haloFontSizeIcon)
(addFontStyleHandle: center bottom (lightRed) #haloFontStyleIcon)
(addFontEmphHandle: rightCenter bottom (lightBrown darker) #haloFontEmphIcon)
(addRecolorHandle: right bottomCenter (magenta darker) #haloRecolorIcon) )
]
{ #category : #'halo theme' }
HaloMorph class >> initialize [
"self initialize"
self installHaloTheme: #iconicHaloSpecifications
]
{ #category : #'halo theme' }
HaloMorph class >> installHaloTheme: haloSpecificationsSelector [
^ CurrentHaloSpecifications := self allHaloSpecsFromArray: (self perform: haloSpecificationsSelector).
]
{ #category : #settings }
HaloMorph class >> showBoundsInHalo [
^ ShowBoundsInHalo ifNil: [ShowBoundsInHalo := false]
]
{ #category : #settings }
HaloMorph class >> showBoundsInHalo: aBoolean [
ShowBoundsInHalo := aBoolean
]
{ #category : #'halo theme' }
HaloMorph class >> simpleFullHaloSpecifications [
"This method gives the specs for the 'full' handles variant when simple halos are in effect"
"
self installHaloTheme: #simpleFullHaloSpecifications
will result in the standard default halos being reinstalled"
"NB: listed below in clockwise order"
^ #(
" selector horiz vert color info icon key
--------- ------ ----------- ------------------------------- ---------------"
(addDebugHandle: right topCenter (blue veryMuchLighter) #haloDebugIcon)
(addDismissHandle: left top (red muchLighter) #haloDismissIcon)
(addRotateHandle: left bottom (blue) #haloRotIcon)
(addMenuHandle: leftCenter top (red) #haloMenuIcon)
(addGrabHandle: center top (black) #halograbIcon)
(addDragHandle: rightCenter top (brown) #haloDragIcon)
(addDupHandle: right top (green) #haloDupIcon)
(addHelpHandle: center bottom (lightBlue) #haloHelpIcon')
(addGrowHandle: right bottom (yellow) #haloScaleIcon)
(addScaleHandle: right bottom (lightOrange) #haloScaleIcon)
(addFewerHandlesHandle: left topCenter (paleBuff) #haloFewerHandlesIcon')
(addFontSizeHandle: leftCenter bottom (lightGreen) #haloFontSizeIcon)
(addFontStyleHandle: center bottom (lightRed) #haloFontStyleIcon)
(addFontEmphHandle: rightCenter bottom (lightBrown darker) #haloFontEmphIcon)
(addRecolorHandle: right bottomCenter (magenta darker) #haloRecolorIcon) )
]
{ #category : #private }
HaloMorph >> addCircleHandles [
| box |
target isWorldMorph ifTrue: [^ self addHandlesForWorldHalos].
self removeAllMorphs. "remove old handles, if any"
self bounds: (self worldBoundsForMorph: target renderedMorph). "update my size"
box := self basicBox.
target addHandlesTo: self box: box.
self addName.
growingOrRotating := false.
self layoutChanged.
self changed.
]
{ #category : #handles }
HaloMorph >> addCollapseHandle: handleSpec [
"Add the collapse handle, with all of its event handlers set up, unless the target's owner is not the world or the hand."
| collapseHandle |
(target owner notNil "nil happens, amazingly"
and: [target owner isWorldOrHandMorph])
ifFalse: [^ self].
collapseHandle := self addHandle: handleSpec
on: #mouseDown send: #mouseDownInCollapseHandle:with: to: self.
collapseHandle on: #mouseUp send: #maybeCollapse:with: to: self.
collapseHandle on: #mouseMove send: #setDismissColor:with: to: self
]
{ #category : #handles }
HaloMorph >> addDebugHandle: handleSpec [
self class haloWithDebugHandle ifTrue:
[self addHandle: handleSpec
on: #mouseDown send: #doDebug:with: to: self]
]
{ #category : #private }
HaloMorph >> addDirectionHandles [
| centerHandle d w directionShaft patch patchColor crossHairColor |
self showingDirectionHandles ifFalse: [^ self].
directionArrowAnchor := (target point: target referencePosition in: self world) rounded.
patch := target imageFormForRectangle: (Rectangle center: directionArrowAnchor extent: 3@3).
patchColor := patch colorAt: 1@1.
(directionShaft := LineMorph newSticky makeForwardArrow)
borderWidth: 2; borderColor: (Color green orColorUnlike: patchColor).
self positionDirectionShaft: directionShaft.
self addMorphFront: directionShaft.
directionShaft setCenteredBalloonText: 'Set forward direction' translated;
on: #mouseDown send: #doDirection:with: to: self;
on: #mouseMove send: #trackDirectionArrow:with: to: self;
on: #mouseUp send: #setDirection:with: to: self.
d := 15. "diameter" w := 3. "borderWidth"
crossHairColor := Color red orColorUnlike: patchColor.
(centerHandle := EllipseMorph newBounds: (0@0 extent: d@d) color: Color transparent)
borderWidth: w; borderColor: (Color blue orColorUnlike: patchColor);
addMorph: (LineMorph from: (d//2)@w to: (d//2)@(d-w-1) color: crossHairColor width: 1) lock;
addMorph: (LineMorph from: w@(d//2) to: (d-w-1)@(d//2) color: crossHairColor width: 1) lock;
align: centerHandle bounds center with: directionArrowAnchor.
centerHandle wantsYellowButtonMenu: false.
self addMorph: centerHandle.
centerHandle setCenteredBalloonText: 'Rotation center (hold down the shift key and drag from here to change it)' translated;
on: #mouseDown send: #prepareToTrackCenterOfRotation:with: to: self;
on: #mouseMove send: #trackCenterOfRotation:with: to: self;
on: #mouseUp send: #setCenterOfRotation:with: to: self
]
{ #category : #handles }
HaloMorph >> addDismissHandle: handleSpec [
"Add the dismiss handle according to the spec, unless my target resists
dismissal "
| dismissHandle |
target okayToAddDismissHandle
ifTrue: [dismissHandle := self
addHandle: handleSpec
on: #mouseDown
send: #mouseDownInDimissHandle:with:
to: self.
dismissHandle
on: #mouseUp
send: #maybeDismiss:with:
to: self.
dismissHandle
on: #mouseDown
send: #setDismissColor:with:
to: self.
dismissHandle
on: #mouseMove
send: #setDismissColor:with:
to: self]
]
{ #category : #handles }
HaloMorph >> addDragHandle: haloSpec [
(self addHandle: haloSpec on: #mouseDown send: #startDrag:with: to: self)
on: #mouseMove send: #doDrag:with: to: self
]
{ #category : #handles }
HaloMorph >> addDupHandle: haloSpec [
"Add the halo that offers duplication, or, when shift is down, make-sibling"
self addHandle: haloSpec on: #mouseDown send: #doDup:with: to: self
]
{ #category : #handles }
HaloMorph >> addFontEmphHandle: haloSpec [
(innerTarget isTextMorph)
ifTrue:
[self
addHandle: haloSpec
on: #mouseDown
send: #chooseEmphasisOrAlignment
to: innerTarget]
]
{ #category : #handles }
HaloMorph >> addFontSizeHandle: haloSpec [
(innerTarget isTextMorph)
ifTrue:
[self
addHandle: haloSpec
on: #mouseDown
send: #chooseFont
to: innerTarget]
]
{ #category : #handles }
HaloMorph >> addFontStyleHandle: haloSpec [
(innerTarget isTextMorph)
ifTrue:
[self
addHandle: haloSpec
on: #mouseDown
send: #chooseStyle
to: innerTarget]
]
{ #category : #private }
HaloMorph >> addFullHandles [
self addCircleHandles
]
{ #category : #handles }
HaloMorph >> addGrabHandle: haloSpec [
"If appropriate, add the black halo handle for picking up the target"
self addHandle: haloSpec on: #mouseDown send: #doGrab:with: to: self
]
{ #category : #private }
HaloMorph >> addGraphicalHandle: formKey at: aPoint on: eventName send: selector to: recipient [
"Add the supplied form as a graphical handle centered at the given point, and set it up to respond to the given event by sending the given selector to the given recipient. Return the handle."
| handle |
handle := self addGraphicalHandleFrom: formKey at: aPoint.
handle on: eventName send: selector to: recipient.
handle setBalloonText: (target balloonHelpTextForHandle: handle) translated.
^ handle
]
{ #category : #private }
HaloMorph >> addGraphicalHandleFrom: formKey at: aPoint [
"Add the supplied form as a graphical handle centered at the given point. Return the handle."
| handle aForm |
aForm := Smalltalk ui icons
iconNamed: formKey
ifNone: [ self iconNamed: #solidMenuIcon ].
handle := ImageMorph new form: aForm; bounds: (Rectangle center: aPoint extent: aForm extent).
handle wantsYellowButtonMenu: false.
self addMorph: handle.
handle on: #mouseUp send: #endInteraction to: self.
^ handle
]
{ #category : #handles }
HaloMorph >> addGrowHandle: haloSpec [
target shouldFlex ifFalse:
[(self addHandle: haloSpec
on: #mouseDown send: #startGrow:with: to: self)
on: #mouseMove send: #doGrow:with: to: self]
"This or addScaleHandle:, but not both, will prevail at any one time"
]
{ #category : #private }
HaloMorph >> addHandle: handleSpec on: eventName send: selector to: recipient [
"Add a handle within the halo box as per the haloSpec, and set
it up to respond to the given event by sending the given
selector to the given recipient. Return the handle."
| handle aPoint |
aPoint := self
positionIn: haloBox
horizontalPlacement: handleSpec horizontalPlacement
verticalPlacement: handleSpec verticalPlacement.
handle := self
addHandleAt: aPoint
color: (Color colorFrom: handleSpec color)
icon: handleSpec iconSymbol
on: eventName
send: selector
to: recipient.
^ handle
]
{ #category : #private }
HaloMorph >> addHandleAt: aPoint color: aColor icon: iconName on: eventName send: selector to: recipient [
"Add a handle centered at the given point with the given color,
and set it up to respond to the given event by sending the
given selector to the given recipient. Return the handle."
| handle |
handle := self createHandleAt: aPoint color: aColor iconName: iconName.
self addMorph: handle.
handle on: #mouseUp send: #endInteraction to: self.
handle on: eventName send: selector to: recipient.
handle setBalloonText: (target balloonHelpTextForHandle: handle) translated.
^ handle
]
{ #category : #private }
HaloMorph >> addHandleAt: aPoint color: aColor on: eventName send: selector to: recipient [
^ self addHandleAt: aPoint color: aColor icon: nil on: eventName send: selector to: recipient
]
{ #category : #private }
HaloMorph >> addHandles [
self addCircleHandles.
]
{ #category : #private }
HaloMorph >> addHandlesForWorldHalos [
"Add handles for world halos, like the man said"
| box w |
w := self world ifNil:[target world].
self removeAllMorphs. "remove old handles, if any"
self bounds: target bounds.
box := w bounds insetBy: 9.
target addWorldHandlesTo: self box: box.
self addNameBeneath: (box insetBy: (0@0 corner: 0@10)) string: innerTarget externalName.
growingOrRotating := false.
self layoutChanged.
self changed.
]
{ #category : #handles }
HaloMorph >> addHelpHandle: haloSpec [
target balloonText ifNotNil:
[(self addHandle: haloSpec on: #mouseDown send: #mouseDownOnHelpHandle: to: innerTarget)
on: #mouseUp send: #deleteBalloon to: innerTarget]
]
{ #category : #handles }
HaloMorph >> addMenuHandle: haloSpec [
self addHandle: haloSpec on: #mouseDown send: #doMenu:with: to: self
]
{ #category : #private }
HaloMorph >> addName [
"Add a name readout at the bottom of the halo."
self addNameBeneath: self basicBox string: target externalName
]
{ #category : #private }
HaloMorph >> addNameBeneath: outerRectangle string: aString [
"Add a name display centered beneath the bottom of the outer rectangle. Return the handle."
| namePosition w |
w := self world ifNil:[target world].
nameMorph := StringMorph contents: aString font: StandardFonts haloFont.
nameMorph
wantsYellowButtonMenu: false;
color: self theme balloonTextColor;
backgroundColor: self theme balloonBackgroundColor;
target: innerTarget.
namePosition := outerRectangle bottomCenter -
((nameMorph width // 2) @ (self handleSize negated // 2 - 1)).
nameMorph position: (namePosition min: w viewBox bottomRight - nameMorph extent y + 2).
self addMorph: nameMorph.
^ nameMorph
]
{ #category : #handles }
HaloMorph >> addRecolorHandle: haloSpec [
"Add a recolor handle to the receiver, if appropriate"
"since this halo now opens a more general properties panel, allow it in all cases"
"innerTarget canSetColor ifTrue:"
self addHandle: haloSpec on: #mouseUp send: #doRecolor:with: to: self.
]
{ #category : #handles }
HaloMorph >> addRotateHandle: haloSpec [
(self addHandle: haloSpec on: #mouseDown send: #startRot:with: to: self)
on: #mouseMove send: #doRot:with: to: self
]
{ #category : #handles }
HaloMorph >> addScaleHandle: haloSpec [
target shouldFlex ifTrue:
[(self addHandle: haloSpec
on: #mouseDown send: #startScale:with: to: self)
on: #mouseMove send: #doScale:with: to: self].
"This or addGrowHandle:, but not both, will prevail at any one time"
]
{ #category : #private }
HaloMorph >> addSimpleHandlesForWorldHalos [
"Nothing special at present here -- just use the regular handles. Cannot rotate or resize world"
self addHandlesForWorldHalos
]
{ #category : #'halos and balloon help' }
HaloMorph >> addSimpleHandlesTo: aHaloMorph box: aBox [
| aHandle |
target isWorldMorph ifTrue: [^ self addSimpleHandlesForWorldHalos].
self removeAllMorphs. "remove old handles, if any"
self bounds: (self worldBoundsForMorph: target renderedMorph). "update my size"
self addHandleAt: (((aBox topLeft + aBox leftCenter) // 2) + self simpleFudgeOffset) color: Color paleBuff icon: #haloMoreHandlesIcon
on: #mouseDown send: #addFullHandles to: self.
aHandle := self addGraphicalHandle: #rotateIcon at: aBox bottomLeft on: #mouseDown send: #startRot:with: to: self.
aHandle on: #mouseMove send: #doRot:with: to: self.
target shouldFlex
ifTrue: [(self addGraphicalHandle: #scaleIcon at: aBox bottomRight on: #mouseDown send: #startScale:with: to: self)
on: #mouseMove send: #doScale:with: to: self]
ifFalse: [(self addGraphicalHandle: #scaleIcon at: aBox bottomRight on: #mouseDown send: #startGrow:with: to: self)
on: #mouseMove send: #doGrow:with: to: self].
growingOrRotating := false.
self layoutChanged.
self changed.
]
{ #category : #private }
HaloMorph >> basicBox [
| aBox minSide anExtent w |
minSide := 4 * self handleSize.
anExtent := ((self width + self handleSize + 8) max: minSide) @
((self height + self handleSize + 8) max: minSide).
aBox := Rectangle center: self center extent: anExtent.
w := self world ifNil:[target outermostWorldMorph].
^ w
ifNil:
[aBox]
ifNotNil:
[aBox intersect: (w viewBox insetBy: 8@8) ifNone: [self error: 'should not happen' ]]
]
{ #category : #private }
HaloMorph >> basicBoxForSimpleHalos [
| w |
w := self world ifNil:[target outermostWorldMorph].
^ ((self worldBoundsForMorph: target topRendererOrSelf) expandBy: self handleAllowanceForIconicHalos)
intersect: (w bounds insetBy: 8@8) ifNone: [self error: 'should not happen' ]
]
{ #category : #'meta-actions' }
HaloMorph >> blueButtonDown: event [
"Transfer the halo to the next likely recipient"
target ifNil:[^self delete].
event hand obtainHalo: self.
positionOffset := event position - (target point: target position in: owner).
"wait for drags or transfer"
event hand
waitForClicksOrDrag: self
event: event
selectors: { #transferHalo:. nil. nil. #dragTarget:. }
threshold: 5.
]
{ #category : #accessing }
HaloMorph >> borderStyle [
"Answer the border style to use for the receiver.
Depends on the target and on some settings."
^(target notNil and: [self showBoundsInHalo and: [target isWorldMorph not]])
ifTrue: [super borderStyle]
ifFalse: [SimpleBorder width: 0 color: Color transparent]
]
{ #category : #updating }
HaloMorph >> changed [
"Quicker to invalidate handles individually if target is large (especially the world)"
self extent > (200@200)
ifTrue: [(target notNil and: [target ~~ self world]) ifTrue:
["Invalidate 4 outer strips first, thus subsuming separate damage."
(self fullBounds areasOutside: target bounds) do:
[:r | self invalidRect: r]].
self submorphsDo: [:m | m changed]]
ifFalse: [super changed].
]
{ #category : #'geometry testing' }
HaloMorph >> containsPoint: aPoint [
"This method is overridden so that, once up, the handles will stay up as long as the mouse is within the box that encloses all the handles even if it is not over any handle or over its owner."
^ target ifNil: [super containsPoint: aPoint] ifNotNil: [false]
]
{ #category : #'events-processing' }
HaloMorph >> containsPoint: aPoint event: anEvent [
"Blue buttons are handled by the halo"
(anEvent isMouse and:[anEvent isMouseDown and:[anEvent blueButtonPressed]])
ifFalse:[^super containsPoint: aPoint event: anEvent].
^bounds containsPoint: anEvent position
]
{ #category : #private }
HaloMorph >> createHandleAt: aPoint color: aColor iconName: iconName [
| bou handle |
bou := Rectangle center: aPoint extent: self handleSize asPoint.
self gradientHalo
ifTrue: [ handle := Morph newBounds: bou color: aColor.
handle borderWidth: 1.
handle useRoundedCorners.
self setColor: aColor toHandle: handle
]
ifFalse: [ handle := EllipseMorph newBounds: bou color: aColor ].
handle borderColor: aColor muchDarker.
handle wantsYellowButtonMenu: false.
iconName
ifNotNil: [ | form |
form := self iconNamed: iconName.
form isNil
ifFalse: [ | image |
image := ImageMorph new.
image form: form.
image color: aColor contrastingBlackAndWhiteColor.
image lock.
handle addMorphCentered: image
]
].
^ handle
]
{ #category : #initialization }
HaloMorph >> defaultColor [
"Answer the default color/fill style for the receiver."
^Color transparent
]
{ #category : #'submorphs-add/remove' }
HaloMorph >> delete [
"Delete the halo. Tell the target that it no longer has the halo; accept any pending edits to the name; and then actually delete myself"
target ifNotNil: [target hasHalo: false].
super delete.
]
{ #category : #private }
HaloMorph >> directionArrowLength [
^ 25
]
{ #category : #private }
HaloMorph >> doDebug: evt with: menuHandle [
"Ask hand to invoke the a debugging menu for my inner target. If shift key is down, immediately put up an inspector on the inner target"
| menu |
"self obtainHaloForEvent: evt andRemoveAllHandlesBut: nil."
self world displayWorld.
evt shiftPressed ifTrue:
[self delete.
^ innerTarget inspectInMorphic: evt].
menu := innerTarget buildDebugMenu: evt hand.
menu buildTitle: [ :menuTitle | menuTitle
title: (innerTarget externalName asString truncateWithElipsisTo: 40);
"icon: Smalltalk ui icons smallDebugIcon;"
withCloseBox;
withPinBox
];
popUpEvent: evt in: self world
]
{ #category : #private }
HaloMorph >> doDirection: anEvent with: directionHandle [
anEvent hand obtainHalo: self.
self removeAllHandlesBut: directionHandle
]
{ #category : #private }
HaloMorph >> doDrag: evt with: dragHandle [
| thePoint |
evt hand obtainHalo: self.
thePoint := target point: evt position - positionOffset from: owner.
target setConstrainedPosition: thePoint hangOut: true.
]
{ #category : #private }
HaloMorph >> doDup: evt with: dupHandle [
"Ask hand to duplicate my target."
(target isKindOf: SelectionMorph) ifTrue:
[^ target doDup: evt fromHalo: self handle: dupHandle].
self obtainHaloForEvent: evt andRemoveAllHandlesBut: dupHandle.
self setTarget: (target duplicateMorph: evt).
evt hand grabMorph: target.
self step. "update position if necessary"
evt hand addMouseListener: self. "Listen for the drop"
]
{ #category : #private }
HaloMorph >> doGrab: evt with: grabHandle [
"Ask hand to grab my target."
self obtainHaloForEvent: evt andRemoveAllHandlesBut: grabHandle.
evt hand grabMorph: target.
self step. "update position if necessary"
evt hand addMouseListener: self. "Listen for the drop"
]
{ #category : #private }
HaloMorph >> doGrow: evt with: growHandle [
"Called while the mouse is down in the grow handle"
| newExtent extentToUse scale |
evt hand obtainHalo: self.
newExtent := (target pointFromWorld: (evt cursorPoint - positionOffset)) - target topLeft.
evt shiftPressed ifTrue: [
scale := (newExtent x / (originalExtent x max: 1)) min:
(newExtent y / (originalExtent y max: 1)).
newExtent := (originalExtent x * scale) asInteger @ (originalExtent y * scale) asInteger
].
(newExtent x < 1 or: [newExtent y < 1 ]) ifTrue: [^ self].
target renderedMorph extent: (extentToUse := newExtent).
growHandle position: evt cursorPoint - (growHandle extent // 2).
self layoutChanged.
]
{ #category : #private }
HaloMorph >> doMenu: evt with: menuHandle [
"Ask hand to invoke the halo menu for my inner target."
| menu |
self obtainHaloForEvent: evt andRemoveAllHandlesBut: nil.
target world displayWorld.
menu := innerTarget buildHandleMenu: evt hand.
innerTarget addTitleForHaloMenu: menu.
menu popUpEvent: evt in: target world.
]
{ #category : #private }
HaloMorph >> doRecolor: evt with: aHandle [
"The mouse went down in the 'recolor' halo handle. Allow the user to change the color of the innerTarget"
evt hand obtainHalo: self.
(aHandle containsPoint: evt cursorPoint)
ifFalse: "only do it if mouse still in handle on mouse up"
[self delete.
target addHalo: evt]
ifTrue: [innerTarget changeColor].
self showingDirectionHandles ifTrue: [self addHandles]
]
{ #category : #private }
HaloMorph >> doRot: evt with: rotHandle [
"Update the rotation of my target if it is rotatable. Keep the relevant command object up to date."
| degrees |
evt hand obtainHalo: self.
degrees := (evt cursorPoint - (target pointInWorld: target referencePosition)) degrees.
degrees := degrees - angleOffset degrees.
degrees := degrees detentBy: 10.0 atMultiplesOf: 90.0 snap: false.
degrees = 0.0
ifTrue: [self setColor: Color lightBlue toHandle: rotHandle]
ifFalse: [self setColor: Color blue toHandle: rotHandle].
rotHandle submorphsDo:
[:m | m color: rotHandle color contrastingBlackAndWhiteColor].
self removeAllHandlesBut: rotHandle.
self showingDirectionHandles ifFalse:
[self showDirectionHandles: true addHandles: false].
self addDirectionHandles.
target rotationDegrees: degrees.
rotHandle position: evt cursorPoint - (rotHandle extent // 2).
]
{ #category : #private }
HaloMorph >> doScale: evt with: scaleHandle [
"Update the scale of my target if it is scalable."
| newHandlePos colorToUse |
evt hand obtainHalo: self.
newHandlePos := evt cursorPoint - (scaleHandle extent // 2).
target scaleToMatch: newHandlePos.
colorToUse := target scale = 1.0
ifTrue: [Color yellow]
ifFalse: [Color orange].
self setColor: colorToUse toHandle: scaleHandle.
scaleHandle
submorphsDo: [:m | m color: colorToUse contrastingBlackAndWhiteColor].
scaleHandle position: newHandlePos.
]
{ #category : #events }
HaloMorph >> dragTarget: event [
"Begin dragging the target"
| thePoint |
thePoint := target point: event position - positionOffset from: owner.
target setConstrainedPosition: thePoint hangOut: true.
event hand newMouseFocus: self.
]
{ #category : #drawing }
HaloMorph >> drawSubmorphsOn: aCanvas [
| alpha |
((alpha := self magicAlpha) = 1.0)
ifTrue:[^super drawSubmorphsOn: aCanvas].
^super drawSubmorphsOn: (aCanvas asAlphaBlendingCanvas: alpha)
]
{ #category : #private }
HaloMorph >> endInteraction [
"Clean up after a user interaction with the a halo control"
| m |
(target isInWorld not or: [owner isNil]) ifTrue: [^self].
[target isFlexMorph and: [target hasNoScaleOrRotation]] whileTrue:
[m := target firstSubmorph.
target removeFlexShell.
target := m].
self isInWorld
ifTrue:
["make sure handles show in front, even if flex shell added"
self comeToFront.
self addHandles].
]
{ #category : #settings }
HaloMorph >> gradientHalo [
^ true
]
{ #category : #accessing }
HaloMorph >> haloBox: aBox [
haloBox := aBox
]
{ #category : #settings }
HaloMorph >> haloEnclosesFullBounds [
^ self class haloEnclosesFullBounds
]
{ #category : #private }
HaloMorph >> handleAllowanceForIconicHalos [
^ 12
]
{ #category : #'events-processing' }
HaloMorph >> handleListenEvent: anEvent [
"We listen for possible drop events here to add back those handles after a dup/grab operation"
(anEvent isMouse and:[anEvent isMove not]) ifFalse:[^ self]. "not interested"
anEvent hand removeMouseListener: self. "done listening"
(self world ifNil: [target world]) ifNil: [^ self].
self addHandles "and get those handles back"
]
{ #category : #private }
HaloMorph >> handleSize [
^ 20
]
{ #category : #'meta-actions' }
HaloMorph >> handlerForBlueButtonDown: anEvent [
"Blue button was clicked within the receiver"
^self
]
{ #category : #initialization }
HaloMorph >> initialize [
"initialize the state of the receiver"
super initialize.
growingOrRotating := false.
self borderStyle: (SimpleBorder
width: 2
color: self theme settings menuSelectionColor)
]
{ #category : #accessing }
HaloMorph >> innerTarget [
^ innerTarget
]
{ #category : #stepping }
HaloMorph >> localHaloBoundsFor: aMorph [
"aMorph may be in the hand and perhaps not in our world"
| r |
r := (self worldBoundsForMorph: aMorph) truncated.
aMorph world = self world ifFalse: [^r].
^((self transformFromOutermostWorld) globalBoundsToLocal: r) truncated
]
{ #category : #accessing }
HaloMorph >> magicAlpha [
^self valueOfProperty: #magicAlpha ifAbsent:[1.0]
]
{ #category : #accessing }
HaloMorph >> magicAlpha: alpha [