2 Copyright 1995-2003, The AROS Development Team. All rights reserved.
3 Copyright 2001-2003, The MorphOS Development Team. All Rights Reserved.
6 Responsible for executing deferred Intuition actions like MoveWindow,
7 SizeWindow, ActivateWindow, etc.
10 #include <proto/exec.h>
11 #include <proto/intuition.h>
12 #include <proto/alib.h>
13 #include <proto/layers.h>
14 #include <proto/graphics.h>
15 #include <proto/keymap.h>
16 #include <proto/input.h>
17 #include <exec/memory.h>
18 #include <exec/alerts.h>
19 #include <exec/interrupts.h>
20 #include <exec/ports.h>
21 #include <intuition/intuition.h>
22 #include <intuition/intuitionbase.h>
23 #include <intuition/gadgetclass.h>
24 #include <intuition/cghooks.h>
25 #include <intuition/sghooks.h>
26 #include <devices/input.h>
27 #include <devices/inputevent.h>
28 #include "inputhandler.h"
30 #include "boopsigadgets.h"
31 #include "boolgadgets.h"
32 #include "propgadgets.h"
33 #include "strgadgets.h"
35 #include "intuition_intern.h" /* EWFLG_xxx */
36 #include "inputhandler_support.h"
37 #include "inputhandler_actions.h"
42 #include <aros/debug.h>
44 #define LOCK_ACTIONS() ObtainSemaphore(&GetPrivIBase(IntuitionBase)->IntuiActionLock);
45 #define UNLOCK_ACTIONS() ReleaseSemaphore(&GetPrivIBase(IntuitionBase)->IntuiActionLock);
48 static void move_family(struct Window
*, int , int);
51 /*******************************************************************************************************/
53 static void CheckLayerRefresh(struct Layer
*lay
, struct Screen
*targetscreen
,
54 struct IntuitionBase
*IntuitionBase
)
56 //if (lay->Flags & LAYERREFRESH)
58 struct Window
*win
= (struct Window
*)lay
->Window
;
60 if (lay
== targetscreen
->BarLayer
)
62 RenderScreenBar(targetscreen
, TRUE
, IntuitionBase
);
66 /* Does it belong to a GZZ window and is it
67 the outer window of that GZZ window? */
68 if (IS_GZZWINDOW(win
) && (lay
== BLAYER(win
)))
70 /* simply refresh that window's frame */
71 Gad_BeginUpdate(lay
, IntuitionBase
);
72 int_refreshwindowframe(win
,REFRESHGAD_BORDER
,0,IntuitionBase
);
73 lay
->Flags
&= ~LAYERREFRESH
;
74 Gad_EndUpdate(lay
, TRUE
, IntuitionBase
);
76 /* Is it the window layer ? */
77 else if (lay
== WLAYER(win
))
79 WindowNeedsRefresh(win
, IntuitionBase
);
81 /* Otherwise, it's a requester. */
84 struct Requester
*req
;
86 for (req
= win
->FirstRequest
; req
&& req
->ReqLayer
!= lay
; req
= req
->OlderRequest
);
90 /* FIXME: should send an IDCMP refresh message too. */
91 Gad_BeginUpdate(lay
, IntuitionBase
);
92 render_requester(req
, IntuitionBase
);
93 lay
->Flags
&= ~LAYERREFRESH
;
94 Gad_EndUpdate(lay
, TRUE
, IntuitionBase
);
99 } /* if (lay->Flags & LAYERREFRESH) */
102 /*******************************************************************************************************/
104 void CheckLayers(struct Screen
*screen
, struct IntuitionBase
*IntuitionBase
)
108 LOCK_REFRESH(screen
);
110 for (L
= screen
->LayerInfo
.top_layer
; L
; L
= L
->back
)
112 if (L
->Flags
& LAYERREFRESH
)
114 CheckLayerRefresh(L
, screen
, IntuitionBase
);
118 UNLOCK_REFRESH(screen
);
121 void WindowSizeWillChange(struct Window
*targetwindow
, WORD dx
, WORD dy
,
122 struct IntuitionBase
*IntuitionBase
)
124 struct Rectangle
*clipto
= NULL
;
125 struct Rectangle final_innerrect
;
127 /* Erase the old frame on the right/lower side if
128 new size is bigger than old size
131 D(bug("********* WindowSizeWillChange ******** dx = %d dy = %d\n", dx
, dy
));
133 if (AVOID_WINBORDERERASE
)
135 final_innerrect
.MinX
= targetwindow
->BorderLeft
;
136 final_innerrect
.MinY
= targetwindow
->BorderTop
;
137 final_innerrect
.MaxX
= targetwindow
->Width
+ dx
- 1 - targetwindow
->BorderRight
;
138 final_innerrect
.MaxY
= targetwindow
->Height
+ dy
- 1 - targetwindow
->BorderBottom
;
139 clipto
= &final_innerrect
;
142 if ( ((dx
> 0) && (targetwindow
->BorderRight
> 0)) ||
143 ((dy
> 0) && (targetwindow
->BorderBottom
> 0)) )
145 struct RastPort
*rp
= targetwindow
->BorderRPort
;
146 struct Layer
*L
= (BLAYER(targetwindow
)) ? BLAYER(targetwindow
) : WLAYER(targetwindow
);
147 struct Rectangle rect
;
148 struct Region
*oldclipregion
;
153 ** In case a clip region is installed then I have to
154 ** install the regular cliprects of the layer
155 ** first. Otherwise the frame might not get cleared correctly.
160 oldclipregion
= InstallClipRegion(L
, NULL
);
162 ScrollX
= L
->Scroll_X
;
163 ScrollY
= L
->Scroll_Y
;
168 if ((dx
> 0) && (targetwindow
->BorderRight
> 0))
170 rect
.MinX
= targetwindow
->Width
- targetwindow
->BorderRight
;
172 rect
.MaxX
= targetwindow
->Width
- 1;
173 rect
.MaxY
= targetwindow
->Height
- 1;
175 OrRectRegion(L
->DamageList
, &rect
);
176 L
->Flags
|= LAYERREFRESH
;
178 if (!AVOID_WINBORDERERASE
|| AndRectRect(&rect
, &final_innerrect
, &rect
))
180 EraseRect(rp
, rect
.MinX
, rect
.MinY
, rect
.MaxX
, rect
.MaxY
);
185 if ((dy
> 0) && (targetwindow
->BorderBottom
> 0))
189 rect
.MinY
= targetwindow
->Height
- targetwindow
->BorderBottom
;
190 rect
.MaxX
= targetwindow
->Width
- 1;
191 rect
.MaxY
= targetwindow
->Height
- 1;
193 OrRectRegion(L
->DamageList
, &rect
);
194 L
->Flags
|= LAYERREFRESH
;
196 if (!AVOID_WINBORDERERASE
|| AndRectRect(&rect
, &final_innerrect
, &rect
))
198 EraseRect(rp
, rect
.MinX
, rect
.MinY
, rect
.MaxX
, rect
.MaxY
);
204 ** Reinstall the clipregions rectangles if there are any.
206 if (NULL
!= oldclipregion
)
208 InstallClipRegion(L
, oldclipregion
);
211 L
->Scroll_X
= ScrollX
;
212 L
->Scroll_Y
= ScrollY
;
216 } /* if ( ((dx > 0) && (targetwindow->BorderRight > 0)) || ((dy > 0) && (targetwindow->BorderBottom > 0)) ) */
218 /* Before resizing the layers eraserect the area of all
219 GFLG_REL??? gadgets and add the area to the damagelist */
221 EraseRelGadgetArea(targetwindow
, clipto
, FALSE
, IntuitionBase
);
225 /*******************************************************************************************************/
227 void WindowSizeHasChanged(struct Window
*targetwindow
, WORD dx
, WORD dy
,
228 BOOL is_sizewindow
, struct IntuitionBase
*IntuitionBase
)
230 struct IIHData
*iihdata
= (struct IIHData
*)GetPrivIBase(IntuitionBase
)->InputHandler
->is_Data
;
233 D(bug("********* WindowSizeHasChanged ********\n"));
235 lay
= (BLAYER(targetwindow
)) ? BLAYER(targetwindow
) : WLAYER(targetwindow
);
237 /* Fix GadgetInfo domain if there is an active gadget in window
240 if ((iihdata
->ActiveGadget
) && (targetwindow
== iihdata
->GadgetInfo
.gi_Window
))
242 GetGadgetDomain(iihdata
->ActiveGadget
,
243 iihdata
->GadgetInfo
.gi_Screen
,
244 iihdata
->GadgetInfo
.gi_Window
,
246 &iihdata
->GadgetInfo
.gi_Domain
);
249 /* Relayout GFLG_REL??? gadgets */
250 DoGMLayout(targetwindow
->FirstGadget
, targetwindow
, NULL
, -1, FALSE
, IntuitionBase
);
252 /* Add the new area of all GFLG_REL??? gadgets to the damagelist, but
253 don't EraseRect() as the gadgets will be re-rendered at their new
257 struct Rectangle innerrect
;
259 innerrect
.MinX
= targetwindow
->BorderLeft
;
260 innerrect
.MinY
= targetwindow
->BorderTop
;
261 innerrect
.MaxX
= targetwindow
->Width
- 1 - targetwindow
->BorderRight
;
262 innerrect
.MaxY
= targetwindow
->Height
- 1 - targetwindow
->BorderBottom
;
264 EraseRelGadgetArea(targetwindow
, AVOID_WINBORDERERASE
? &innerrect
: NULL
, TRUE
, IntuitionBase
);
267 /* If new size is smaller than old size add right/bottom
268 frame to damagelist */
269 if ( ((dx
< 0) && (targetwindow
->BorderRight
> 0)) ||
270 ((dx
> 0) && (targetwindow
->BorderTop
> 0)) ||
271 ((dy
< 0) && (targetwindow
->BorderBottom
> 0)) )
273 struct Rectangle rect
;
277 if ((dx
< 0) && (targetwindow
->BorderRight
> 0))
279 rect
.MinX
= targetwindow
->Width
- targetwindow
->BorderRight
;
281 rect
.MaxX
= targetwindow
->Width
- 1;
282 rect
.MaxY
= targetwindow
->Height
- 1;
284 OrRectRegion(lay
->DamageList
, &rect
);
285 lay
->Flags
|= LAYERREFRESH
;
288 if ((dx
> 0) && (targetwindow
->BorderTop
> 0))
292 rect
.MaxX
= targetwindow
->Width
- 1;
293 rect
.MaxY
= targetwindow
->BorderTop
- 1;
295 OrRectRegion(lay
->DamageList
, &rect
);
296 lay
->Flags
|= LAYERREFRESH
;
299 if ((dy
< 0) && (targetwindow
->BorderBottom
> 0))
302 rect
.MinY
= targetwindow
->Height
- targetwindow
->BorderBottom
;
303 rect
.MaxX
= targetwindow
->Width
- 1;
304 rect
.MaxY
= targetwindow
->Height
- 1;
306 OrRectRegion(lay
->DamageList
, &rect
);
307 lay
->Flags
|= LAYERREFRESH
;
312 } /* if ( ((dx < 0) && (targetwindow->BorderRight > 0)) || ((dy < 0) && (targetwindow->BorderBottom > 0)) ) */
314 ((struct IntWindow
*)(targetwindow
))->specialflags
|= SPFLAG_LAYERRESIZED
;
317 if (IS_GZZWINDOW(targetwindow
))
319 lay
= targetwindow
->BorderRPort
->Layer
;
321 if (lay
->Flags
& LAYERREFRESH
)
323 Gad_BeginUpdate(lay
, IntuitionBase
);
324 RefreshWindowFrame(targetwindow
);
325 lay
->Flags
&= ~LAYERREFRESH
;
326 Gad_EndUpdate(lay
, TRUE
, IntuitionBase
);
329 lay
= targetwindow
->WLayer
;
331 if (lay
->Flags
& LAYERREFRESH
)
333 Gad_BeginUpdate(lay
, IntuitionBase
);
334 int_refreshglist(targetwindow
->FirstGadget
, targetwindow
, NULL
, -1, 0, REFRESHGAD_BORDER
, IntuitionBase
);
335 Gad_EndUpdate(lay
, IS_NOCAREREFRESH(targetwindow
), IntuitionBase
);
341 lay
= targetwindow
->WLayer
;
343 if (lay
->Flags
& LAYERREFRESH
)
345 Gad_BeginUpdate(lay
, IntuitionBase
);
346 RefreshWindowFrame(targetwindow
);
347 int_refreshglist(targetwindow
->FirstGadget
, targetwindow
, NULL
, -1, 0, REFRESHGAD_BORDER
, IntuitionBase
);
348 Gad_EndUpdate(lay
, IS_NOCAREREFRESH(targetwindow
), IntuitionBase
);
352 lay
= targetwindow
->WLayer
;
354 if (IS_NOCAREREFRESH(targetwindow
))
357 lay
->Flags
&= ~LAYERREFRESH
;
365 /* Send IDCMP_NEWSIZE to resized window */
367 ih_fire_intuimessage(targetwindow
,
374 if (ie
= AllocInputEvent(iihdata
))
376 ie
->ie_Class
= IECLASS_EVENT
;
377 ie
->ie_Code
= IECODE_NEWSIZE
;
378 ie
->ie_EventAddress
= targetwindow
;
379 CurrentTime(&ie
->ie_TimeStamp
.tv_secs
, &ie
->ie_TimeStamp
.tv_micro
);
382 /* Send IDCMP_CHANGEWINDOW to resized window */
384 ih_fire_intuimessage(targetwindow
,
393 /*******************************************************************************************************/
395 void DoMoveSizeWindow(struct Window
*targetwindow
, LONG NewLeftEdge
, LONG NewTopEdge
,
396 LONG NewWidth
, LONG NewHeight
, BOOL send_newsize
, struct IntuitionBase
*IntuitionBase
)
398 struct IIHData
*iihdata
= (struct IIHData
*)GetPrivIBase(IntuitionBase
)->InputHandler
->is_Data
;
399 //struct IntWindow *w = (struct IntWindow *)targetwindow;
400 struct Layer
*targetlayer
= WLAYER(targetwindow
)/*, *L*/;
401 struct Requester
*req
;
402 struct InputEvent
*ie
;
403 LONG OldLeftEdge
= targetwindow
->LeftEdge
;
404 LONG OldTopEdge
= targetwindow
->TopEdge
;
405 LONG OldWidth
= targetwindow
->Width
;
406 LONG OldHeight
= targetwindow
->Height
;
407 LONG pos_dx
, pos_dy
, size_dx
, size_dy
;
409 /* correct new window coords if necessary */
411 FixWindowCoords(targetwindow
, &NewLeftEdge
, &NewTopEdge
, &NewWidth
, &NewHeight
,IntuitionBase
);
413 D(bug("DoMoveSizeWindow to %d,%d %d x %d\n", NewLeftEdge
, NewTopEdge
, NewWidth
, NewHeight
));
415 pos_dx
= NewLeftEdge
- OldLeftEdge
;
416 pos_dy
= NewTopEdge
- OldTopEdge
;
417 size_dx
= NewWidth
- OldWidth
;
418 size_dy
= NewHeight
- OldHeight
;
420 LOCK_REFRESH(targetwindow
->WScreen
);
422 /* jDc: intuition 68k doesn't care about that */
423 // if (pos_dx || pos_dy || size_dx || size_dy)
426 if (size_dx
|| size_dy
)
428 WindowSizeWillChange(targetwindow
, size_dx
, size_dy
, IntuitionBase
);
431 targetwindow
->LeftEdge
+= pos_dx
;
432 targetwindow
->TopEdge
+= pos_dy
;
434 targetwindow
->RelLeftEdge
+= pos_dx
;
435 targetwindow
->RelTopEdge
+= pos_dy
;
438 targetwindow
->Width
= NewWidth
;
439 targetwindow
->Height
= NewHeight
;
440 targetwindow
->GZZWidth
= targetwindow
->Width
- targetwindow
->BorderLeft
- targetwindow
->BorderRight
;
441 targetwindow
->GZZHeight
= targetwindow
->Height
- targetwindow
->BorderTop
- targetwindow
->BorderBottom
;
443 /* check for GZZ window */
444 if (BLAYER(targetwindow
))
446 /* move outer window first */
447 MoveSizeLayer(BLAYER(targetwindow
), pos_dx
, pos_dy
, size_dx
, size_dy
);
450 MoveSizeLayer(targetlayer
, pos_dx
, pos_dy
, size_dx
, size_dy
);
452 for (req
= targetwindow
->FirstRequest
; req
; req
= req
->OlderRequest
)
454 struct Layer
*layer
= req
->ReqLayer
;
459 int left
, top
, right
, bottom
;
461 left
= NewLeftEdge
+ req
->LeftEdge
;
462 top
= NewTopEdge
+ req
->TopEdge
;
463 right
= left
+ req
->Width
- 1;
464 bottom
= top
+ req
->Height
- 1;
466 if (left
> NewLeftEdge
+ NewWidth
- 1)
467 left
= NewLeftEdge
+ NewWidth
- 1;
469 if (top
> NewTopEdge
+ NewHeight
- 1)
470 top
= NewTopEdge
+ NewHeight
- 1;
472 if (right
> NewLeftEdge
+ NewWidth
- 1)
473 right
= NewLeftEdge
+ NewWidth
- 1;
475 if (bottom
> NewTopEdge
+ NewHeight
- 1)
476 bottom
= NewTopEdge
+ NewHeight
- 1;
478 dx
= left
- layer
->bounds
.MinX
;
479 dy
= top
- layer
->bounds
.MinY
;
480 dw
= right
- left
- layer
->bounds
.MaxX
+ layer
->bounds
.MinX
;
481 dh
= bottom
- top
- layer
->bounds
.MaxY
+ layer
->bounds
.MinY
;
483 MoveSizeLayer(layer
, dx
, dy
, dw
, dh
);
488 if (w
->ZipLeftEdge
!= ~0) w
->ZipLeftEdge
= OldLeftEdge
;
489 if (w
->ZipTopEdge
!= ~0) w
->ZipTopEdge
= OldTopEdge
;
490 if (w
->ZipWidth
!= ~0) w
->ZipWidth
= OldWidth
;
491 if (w
->ZipHeight
!= ~0) w
->ZipHeight
= OldHeight
;
494 if (pos_dx
|| pos_dy
)
496 UpdateMouseCoords(targetwindow
);
498 if (HAS_CHILDREN(targetwindow
))
499 move_family(targetwindow
, pos_dx
, pos_dy
);
503 // } /* if (pos_dx || pos_dy || size_dx || size_dy) */
505 if (size_dx
|| size_dy
)
507 WindowSizeHasChanged(targetwindow
, size_dx
, size_dy
, FALSE
, IntuitionBase
);
510 ih_fire_intuimessage(targetwindow
,
518 /* Send IDCMP_NEWSIZE and IDCMP_CHANGEWINDOW to resized window, even
519 if there was no resizing/position change at all. BGUI for example
522 ih_fire_intuimessage(targetwindow
,
528 if ((ie
= AllocInputEvent(iihdata
)))
530 ie
->ie_Class
= IECLASS_EVENT
;
531 ie
->ie_Code
= IECODE_NEWSIZE
;
532 ie
->ie_EventAddress
= targetwindow
;
533 CurrentTime(&ie
->ie_TimeStamp
.tv_secs
, &ie
->ie_TimeStamp
.tv_micro
);
537 // jDc: CheckLayers calls LOCK_REFRESH, so there's no reason to UNLOCK here!
538 // UNLOCK_REFRESH(targetwindow->WScreen);
540 CheckLayers(targetwindow
->WScreen
, IntuitionBase
);
542 UNLOCK_REFRESH(targetwindow
->WScreen
);
545 if (size_dx
|| size_dy
)
547 if (!(((struct IntWindow
*)targetwindow
)->CustomShape
))
549 struct wdpWindowShape shapemsg
;
550 struct Region
*shape
;
551 shapemsg
.MethodID
= WDM_WINDOWSHAPE
;
552 shapemsg
.wdp_Width
= targetwindow
->Width
;
553 shapemsg
.wdp_Height
= targetwindow
->Height
;
554 shapemsg
.wdp_Window
= targetwindow
;
555 shapemsg
.wdp_TrueColor
= (((struct IntScreen
*)targetwindow
->WScreen
)->DInfo
.dri
.dri_Flags
& DRIF_DIRECTCOLOR
);
556 shapemsg
.wdp_UserBuffer
= ((struct IntWindow
*)targetwindow
)->DecorUserBuffer
;
557 shape
= DoMethodA(((struct IntScreen
*)(targetwindow
->WScreen
))->WinDecorObj
, (Msg
)&shapemsg
);
559 if (((struct IntWindow
*)targetwindow
)->OutlineShape
) DisposeRegion(((struct IntWindow
*)targetwindow
)->OutlineShape
);
560 ((struct IntWindow
*)targetwindow
)->OutlineShape
= shape
;
561 ChangeWindowShape(targetwindow
, shape
, NULL
);
562 ((struct IntWindow
*)targetwindow
)->CustomShape
= FALSE
;
569 /*******************************************************************************************************/
571 void DoSyncAction(void (*func
)(struct IntuiActionMsg
*, struct IntuitionBase
*),
572 struct IntuiActionMsg
*msg
,
573 struct IntuitionBase
*IntuitionBase
)
575 struct IIHData
*iihd
= (struct IIHData
*)GetPrivIBase(IntuitionBase
)->InputHandler
->is_Data
;
576 struct Task
*me
= FindTask(NULL
);
578 if (me
== iihd
->InputDeviceTask
)
580 func(msg
, IntuitionBase
);
587 struct InputEvent ie
;
594 ObtainSemaphore(&GetPrivIBase(IntuitionBase
)->IntuiActionLock
);
595 AddTail((struct List
*)GetPrivIBase(IntuitionBase
)->IntuiActionQueue
, (struct Node
*)msg
);
596 ReleaseSemaphore(&GetPrivIBase(IntuitionBase
)->IntuiActionLock
);
599 port
.mp_Flags
= PA_SIGNAL
;
600 port
.mp_SigTask
= me
;
601 port
.mp_SigBit
= SIGB_INTUITION
;
602 NEWLIST(&port
.mp_MsgList
);
604 req
.io_Message
.mn_ReplyPort
= &port
;
605 req
.io_Device
= GetPrivIBase(IntuitionBase
)->InputIO
->io_Device
;
606 req
.io_Unit
= GetPrivIBase(IntuitionBase
)->InputIO
->io_Unit
;
607 req
.io_Command
= IND_WRITEEVENT
;
608 req
.io_Length
= sizeof(ie
);
611 ie
.ie_Class
= IECLASS_NULL
;
623 Wait(SIGF_INTUITION
);
629 /*******************************************************************************************************/
631 BOOL
DoASyncAction(void (*func
)(struct IntuiActionMsg
*, struct IntuitionBase
*),
632 struct IntuiActionMsg
*msg
, ULONG size
,
633 struct IntuitionBase
*IntuitionBase
)
635 struct IIHData
*iihd
= (struct IIHData
*)GetPrivIBase(IntuitionBase
)->InputHandler
->is_Data
;
636 struct Task
*me
= FindTask(NULL
);
637 struct IntuiActionMsg
*new_msg
;
639 if (me
== iihd
->InputDeviceTask
)
641 func(msg
, IntuitionBase
);
644 else if ((new_msg
= AllocVecPooled(iihd
->ActionsMemPool
,size
)))
646 new_msg
->handler
= func
;
647 new_msg
->task
= NULL
;
648 if (size
> sizeof(*msg
))
650 CopyMem(msg
+ 1, new_msg
+ 1, size
- sizeof(*msg
));
653 ObtainSemaphore(&GetPrivIBase(IntuitionBase
)->IntuiActionLock
);
654 AddTail((struct List
*)GetPrivIBase(IntuitionBase
)->IntuiActionQueue
, (struct Node
*)new_msg
);
655 ReleaseSemaphore(&GetPrivIBase(IntuitionBase
)->IntuiActionLock
);
668 /*******************************************************************************************************/
670 void HandleIntuiActions(struct IIHData
*iihdata
,
671 struct IntuitionBase
*IntuitionBase
)
673 struct IntuiActionMsg
*am
;
675 D(bug("Handle Intuition action messages\n"));
677 if (iihdata
->ActiveSysGadget
)
679 D(bug("Handle Intuition action messages. Doing nothing because of active drag or resize gadget!\n"));
686 am
= (struct IntuiActionMsg
*)RemHead((struct List
*)&iihdata
->IntuiActionQueue
);
692 am
->handler(am
, IntuitionBase
);
698 Signal(am
->task
, SIGF_INTUITION
);
703 FreeVecPooled(iihdata
->ActionsMemPool
,am
);
707 D(bug("Intuition action messages handled\n"));
711 static void move_family(struct Window
* w
, int dx
, int dy
)
713 struct Window
* _w
= w
->firstchild
;
719 if (HAS_CHILDREN(_w
))
720 move_family(_w
,dx
,dy
);