Skip to content

Commit 3243a4d

Browse files
Update dragAndDropExample.md
1 parent 83e682c commit 3243a4d

File tree

1 file changed

+288
-45
lines changed

1 file changed

+288
-45
lines changed
Lines changed: 288 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,59 +1,302 @@
1-
### Drag and Drop Example
1+
### Drag and Drop
22

3+
Bloc has basic support for drag-and-drop which needs to be further improved. In this chapter we try to explain the basics of drag-and-drop with Bloc events and we display some valuable examples.
4+
5+
#### Three basic events
6+
7+
To start understanding the Drag-and-drop process, you must acknowledge the following 3 basic events:
8+
9+
- BlDragStartEvent : Starts the drag process, sent when you move the cursor for the first time after having a MouseDownEvent.
10+
- BlDragEvent : sent whenever you move the cursor during the drag process. **Needs the BlDragStartEvent to be consumed in order to be sent**.
11+
- BlDragEndEvent : Ends the drag process, sent when you lift your cursor (i.e. send a MouseUpEvent)
12+
13+
#### First drag and drop
14+
15+
Using the basic events we can write the simplest drag and drop snippet
16+
17+
```st
18+
element := BlElement new background: Color lightGreen.
19+
offset := 0.
20+
element addEventHandlerOn: BlDragStartEvent do: [ :event |
21+
offset := event position - element position.
22+
event consume. ].
23+
24+
element addEventHandlerOn: BlDragEvent do: [ :event |
25+
event consume.
26+
element position: event position - offset.].
27+
28+
element openInSpace
329
```
4-
| surface elt counter|
5-
counter :=1.
6-
elt := BlElement new
7-
size: 20 @ 20;
8-
geometry: (BlPolygonGeometry vertices: {
9-
(10 @ 5).
10-
(11.5 @ 9).
11-
(15 @ 9).
12-
(12.5 @ 11).
13-
(13.5 @ 15).
14-
(10 @ 13).
15-
(6.5 @ 15).
16-
(7.5 @ 11).
17-
(5 @ 9).
18-
(8.5 @ 9) });
19-
background: (Color red alpha: 0.5);
20-
border: (BlBorder paint: Color blue width: 1).
2130

22-
surface := BlElement new
23-
size: 400 @ 400;
24-
geometry: BlSquareGeometry new;
25-
background: (Color pink alpha: 0.2);
26-
border: (BlBorder paint: Color orange width: 4).
31+
Here we add a small offset variable that is defined when starting to drag the element so that we can redefine the element's position according to the mouse position and have a smooth drag and drop.
32+
33+
#### Custom Drag Handlers
2734

28-
surface addChild: elt.
29-
elt position: 200 @200.
35+
##### DragHandler
3036

31-
surface addEventHandler: (BlEventHandler
32-
on: BlMouseWheelEvent
33-
do: [ :anEvent |
34-
anEvent consumed: true.
35-
anEvent isScrollDown ifTrue: [ counter := counter- 0.5 ].
36-
anEvent isScrollUp ifTrue: [ counter := counter + 0.5 ].
37-
elt transformDo: [ :t | t scaleBy: counter].]).
37+
In Bloc, there is a an implementation of an object called DragHandler. This object is a a CustomEventHandler aiming to deal with basic drag and drop without having to write again the lines above.
3838

39-
surface openInNewSpace.
40-
^ surface
41-
```
42-
43-
I want to do different things in the BlDragEvent handler depending on whether the ALT key is currently being pressed (snapping vs no snapping). But the event modifiers list is always empty in all drag event handlers, no matter what I do. Is there any way I can poll whether the ALT key is down right now, or otherwise check this?
39+
As an EventHandler you can simply add it to an Element and use it to start dragging the Element.
4440

45-
You can listen to the alt key at the space level by adding
41+
```st
42+
element := BlElement new background: Color lightGreen.
4643
44+
element addEventHandler: BlDragHandler new.
45+
46+
element openInSpace
4747
```
48-
space addEventHandler: (BlEventHandler on: BlKeyDownEvent do: [ :evt | (evt key = KeyboardKey altLeft or: [ evt key = KeyboardKey altRight ]) ifTrue: [ self inform: 'space alt key pressed' ] ]).
48+
However you can see the default behavior when dropping the Element is to bring the element back to its original position.
49+
50+
##### PullHandler
51+
52+
There is also the BlPullHandler which has a more detailed API allowing you more behaviours.
53+
54+
Adapting the same default snippet, we can have a basic drag and drop with the PullHandler however, here the default drop behaviour will leave the element right where it currently is.
55+
56+
```st
57+
element := BlElement new background: Color lightGreen.
58+
59+
element addEventHandler: BlPullHandler new.
60+
61+
element openInSpace
4962
```
50-
51-
This event will be caught even if you're in the middle of a BlDragEvent. You can then adapt your logic. Alt being a keyboard event, it must be managed by listening to the proper event raised. As far as I can see in Pharo, anEvent modifiers is only set in the context of keyboard handling.
52-
Note that if you want to create keyboard shortcut, you should use BlShorcutWithAction, like:
5363

64+
This handler allows you to confine or not your draggable element into its parents bounds using the messages `disallowOutOfBounds`/`allowOutOfBounds`.
65+
66+
```st
67+
element := BlElement new background: Color lightGreen.
68+
69+
element addEventHandler: BlPullHandler new.
70+
71+
"parent := BlElement new size: 200 asPoint; border: (BlBorder paint: Color red width: 2); yourself.
72+
73+
parent addChild: element."
74+
75+
element openInSpace
5476
```
55-
BlShortcutWithAction new
56-
combination: (BlKeyCombination builder alt; control; key: KeyboardKey C; build);
57-
action: [ flag := true ].
77+
78+
You can also have different dragging strategies such as dragging only horizontally or vertically. The default strategy is called 'free' and those strategies can me switch dynamically.
79+
80+
In the following example, you can click the element to switch to the next strategy.
81+
82+
```st
83+
element := BlElement new background: Color lightGreen.
84+
85+
pullHandler := BlPullHandler new.
86+
87+
strategy = 'free'.
88+
element addEventHandler: pullHandler.
89+
90+
element addEventHandlerOn: BlClickEvent do: [ :event |
91+
event consume.
92+
strategy = 'free'
93+
ifTrue: [
94+
strategy := 'horizontal'.
95+
element background: Color lightBlue.
96+
pullHandler beHorizontal
97+
]
98+
ifFalse: [
99+
strategy = 'horizontal'
100+
ifTrue: [
101+
strategy := 'vertical'.
102+
element background: Color lightRed.
103+
pullHandler beVertical
104+
]
105+
ifFalse: [
106+
strategy := 'free'.
107+
element background: Color lightGreen.
108+
pullHandler beFree
109+
]
110+
]
111+
].
112+
113+
element openInSpace
114+
```
115+
116+
#### Events for the environment
117+
118+
Other Events related to Drag and Drop are available in Bloc but they mainly concern the environment in which an Element is dragged.
119+
120+
These events are:
121+
- BlSpaceDragLiftEvent
122+
- BlDropEvent
123+
- BlDragEnterEvent
124+
- BlDragLeaveEvent
125+
126+
Let's start with `BlSpaceDragLiftEvent` that acts similar to the `BlDragStartEvent` meaning it is sent once at the beginning of the drag phase but contrary to the DragStart, the SpaceDragLift is an event sent to the space
127+
128+
```st
129+
"SpaceDragLiftEvent seems to be the same as DragStartEvent but sent to the space instead of the dragged Element"
130+
131+
| child space border |
132+
child := BlElement new
133+
background: Color random;
134+
addEventHandler: BlDragHandler new.
135+
136+
space := BlSpace new.
137+
space root addChild: child.
138+
139+
child addEventHandlerOn: BlDragEvent do: [
140+
border := BlBorder paint: Color random width: 5.
141+
child border: border ].
142+
"event sent during each frame of the drag"
143+
space
144+
addEventHandlerOn: BlSpaceDragLiftEvent
145+
do: [ child background: Color random ].
146+
"event sent at the beginning of the drag"
147+
148+
space show
58149
```
59150

151+
---
152+
153+
Then the `BlDropEvent` is an event received by the element behind the dragged element. This event is of course sent at the end of the process after you drop the element
154+
155+
Here is an example snippet:
156+
157+
```st
158+
"sends correctly (and inform) a DropEvent to the target area when dropping the element on it"
159+
| child target space |
160+
child := BlElement new
161+
background: Color red;
162+
addEventHandler: BlDragHandler new.
163+
164+
target := BlElement new
165+
background: Color lightGreen;
166+
border: (BlBorder paint: Color green width: 3);
167+
size: 200 asPoint;
168+
position: 200 @ 200.
169+
170+
space := BlSpace new.
171+
172+
space root addChildren: {
173+
target.
174+
child }.
175+
176+
target addEventHandlerOn: BlDropEvent do: [
177+
'Drop on target' traceCr.
178+
target background: Color random ].
179+
180+
child
181+
addEventHandlerOn: BlDragEndEvent
182+
do: [ 'Drag Ended' traceCr ].
183+
184+
child addEventHandlerOn: BlDropEvent do: [ 'Dropped' traceCr].
185+
"This should never trigger only target will receive this DropEvent"
186+
space show
187+
```
188+
189+
---
190+
191+
The last 2 events are quite similar as `BlDragEnterEvent` and `BlDragLeaveEvent` check if your cursor enters ou leaves the bounds of an Element while dragging. However, it is **important** to know these events are sent to the element directly under the cursor during the drag process. This means that because we usually drag an element that follows the cursor, it won't be possible for the cursor to know if it entered another element's bounds behind.
192+
193+
The following snippet show this behaviour:
194+
195+
```st
196+
element := BlElement new background: Color lightGreen.
197+
198+
element addEventHandler: BlPullHandler new.
199+
200+
target := BlElement new background: Color lightRed; size: 100 asPoint; position: 200 asPoint.
201+
202+
border := BlBorder paint: Color black width: 3.
203+
204+
target addEventHandlerOn: BlDragEnterEvent do: [ :event |
205+
event consume.
206+
target border: border. ].
207+
208+
target addEventHandlerOn: BlDragLeaveEvent do: [ :event |
209+
event consume.
210+
target border: (BlBorder empty) ].
211+
212+
space := BlSpace new.
213+
space root addChildren: { target. element }.
214+
space show
215+
```
216+
217+
In this snippet, we add a border to the red element whenever the green element enters its bounds while dragging, and we remove this borders when it leaves.
218+
We can see that to border doesn't appears as intended (unless you drag too quickly the element meaning the cursor will enter the parent first and then consider leaving it when the green element will be brought to the right position).
219+
220+
To avoid this we can make the green element 'transparent to the events' when starting to drag it. Meaning the cursor will know 'see through' the green element and send the correct enter and leave events to the red one. For this we can simply use the messages `preventMouseEvents` and `allowMouseEvents` by adding the next lines :
221+
222+
```st
223+
element addEventHandlerOn: BlDragStartEvent do: [ :event |
224+
event consume.
225+
element preventMouseEvents ].
226+
227+
element addEventHandlerOn: BlDragEndEvent do: [ :event |
228+
event consume.
229+
element allowMouseEvents ].
230+
```
231+
232+
This example now works as we wanted, adding and removing a border to the red element when the cursor enters it while dragging.
233+
Remember this looks if the **cursor** enters and leaves the bounds and not if the green element itself enters or leaves. To see if 2 elements 'collide' you need to use the `BlBounds` API.
234+
235+
#### Include keyboard events
236+
237+
In this section, we present an example where we can add some keyboard events during the drag process to change its behaviour dynamically
238+
239+
The following example lets us drag an element freely, however we add some event handlers on keyboard events and check if the shift or alt key is pressed, these will dynamically change the way the handler drags the element (as well as changing the element's background for visual clarity)
240+
241+
```st
242+
| handler child space strategy |
243+
handler := BlPullHandler new.
244+
child := BlElement new
245+
size: 100 asPoint;
246+
background: Color red;
247+
addEventHandler: handler.
248+
249+
child openInSpace.
250+
251+
child addEventHandlerOn: BlDragStartEvent do: [ :evt |
252+
child requestFocus ].
253+
254+
child addEventHandlerOn: BlDragEndEvent do: [ :evt |
255+
child loseFocus ].
256+
257+
child addEventHandlerOn: BlKeyDownEvent do: [ :evt |
258+
handler beFree.
259+
child background: Color red.
260+
261+
evt modifiers isShift ifTrue: [
262+
handler beHorizontal.
263+
child background: Color green. ].
264+
265+
evt modifiers isAlt ifTrue: [
266+
handler beVertical.
267+
child background: Color blue. ].
268+
269+
(evt modifiers isShift and: [ evt modifiers isAlt ]) ifTrue: [
270+
child background: Color black.
271+
child removeEventHandler: handler]
272+
273+
].
274+
275+
child addEventHandlerOn: BlKeyUpEvent do: [ :evt |
276+
(child hasEventHandler: handler) ifFalse: [ child addEventHandler: handler ].
277+
handler beFree.
278+
child background: Color red.
279+
280+
evt modifiers isShift ifTrue: [
281+
handler beHorizontal.
282+
child background: Color green. ].
283+
284+
evt modifiers isAlt ifTrue: [
285+
handler beVertical.
286+
child background: Color blue. ].
287+
288+
(evt modifiers isShift and: [ evt modifiers isAlt ]) ifTrue: [
289+
child background: Color black.
290+
child removeEventHandler: handler ]
291+
292+
].
293+
```
294+
295+
One thing important in this snippet is to use the messages `requestFocus` and `loseFocus` so that keyboard events can be listened and we send them only at the start and end of the drag and drop meaning this behaviour can only appear during this process.
296+
297+
The example is not exactly perfect nor practical but it serves as a good example on how to dynamically change the drag behaviour with keyboard events.
298+
299+
#### Other Examples
300+
301+
You can find more examples/experiments on Drag and Drop in the class `BlDragAndDropExamples`.
302+
We suggest looking at the class `BlDragAndDropLetterExampleDemo` that shows an example of a letter sorter with letters reacting if hovering the 'vowel' or 'consonant' areas.

0 commit comments

Comments
 (0)