-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathDragToScroll.ahk
1604 lines (1387 loc) · 49.3 KB
/
DragToScroll.ahk
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
/*
DragToScroll.ahk
new discussion:
https://autohotkey.com/boards/viewtopic.php?f=6&t=38457
old discussion:
http://www.autohotkey.com/forum/viewtopic.php?t=59726
https://autohotkey.com/board/topic/55289-dragtoscroll-universal-drag-flingflick-scrolling/
Scroll any active window by clicking and dragging with the right mouse button.
Should not interfere with normal right clicking.
See the discussion link above for more information.
This script has one dependency, on Tuncay's ini lib, found at:
http://www.autohotkey.com/forum/viewtopic.php?t=46226
*/
#SingleInstance Force
#Persistent
#NoEnv
#NoTrayIcon
#Include %A_ScriptDir%\ini.ahk
GoSub, Init
Return
ApplySettings:
; Settings
;--------------------------------
; Global toggle. Should generally always be false
Setting("ScrollDisabled", false)
; The chosen hotkey button
; Should work with pretty much any button, though
; mouse or KB special keys (ctrl, alt, etc) are preferred.
Setting("Button", "RButton")
; Delay time before drag starts
; You must click and release "Button" before this time;
; Increase if you are having trouble getting "normal behavior"
Setting("DragDelay", 150) ; in ms.
; How often to poll for mouse movement & drag
; The major time unit for this script, everything happens on this
; schedule. Affects script responsiveness, scroll speed, etc.
Setting("PollFrequency", 20) ; in ms
; Speed
; Affects the overall speed of scrolling before acceleration
; Speed is "normalized" to 1.0 as a default
Setting("DragThreshold", 0) ; in pixels
Setting("SpeedX", 0.5)
Setting("SpeedY", 1.0)
; MovementCheck
; if enabled, this check will abort dragging
; if you have not moved the mouse over MovementThreshold
; within the first MovementCheckDelay ms
; This is used for compatibility with other button-hold actions
Setting("UseMovementCheck", true)
Setting("MovementCheckDelay", 500) ; in ms
Setting("MovementThreshold", 0) ; in px
; scroll method
; choose one of: mWheelKey, mWheelMessage, mScrollMessage
; WheelMessage & WheelKey are preferred; your results may vary
Setting("ScrollMethodX", mWheelKey)
Setting("ScrollMethodY", mWheelKey)
; invert drag
; by default, you "drag" the document; moving up drags the document up,
; showing more of the document below. This behavior is the inverse of
; scrolling up, where you see more of the document above.
; The invert flag switches the drag to the "scroll" behavior
Setting("InvertDrag", true)
; Edge Scrolling
; allows you to hover over a window edge
; to continue scrolling, at a fixed rate
Setting("UseEdgeScrolling", false)
Setting("EdgeScrollingThreshold", 15) ; in px, distance from window edge
Setting("EdgeScrollSpeed", 2.0) ; in 'speed'; 1.0 is about 5px/sec drag
; Targeting
; if Confine is enabled, drag will be immediately halted
; if the mouse leaves the target window or control
;
; it is advisable to not use BOTH confine and EdgeScrolling
; in that case, edge scrolling will only work if you
; never leave the bounds of the window edge
Setting("UseControlTargeting", true)
Setting("ConfineToWindow", false)
Setting("ConfineToControl", false)
; Acceleration & momentum
Setting("UseAccelerationX", true)
Setting("UseAccelerationY", true)
Setting("MomentumThreshold", 0.7) ; in 'speed'. Minimum speed to trigger momentum. 1 is always
Setting("MomentumStopSpeed", 0.25) ; in 'speed'. Scrolling is stopped when momentum slows to this value
Setting("MomentumInertia", .93) ; (0 < VALUE < 1) Describes how fast the scroll momentum dampens
Setting("UseScrollMomentum", false)
; Acceleration function
; - modify very carefully!!
; - default is a pretty modest curve
;
; Based on the initial speed "arg", accelerate and return the updated value
; Think of this function as a graph of drag-speed v.s. scroll-speed.
;
Accelerate(arg)
{
return .006 * arg **3 + arg
}
; double-click checking
;
; If enabled, a custom action can be performed a double-click is detected.
; Simply set UseDoubleClickCheck := true
; Define ButtonDoubleClick (below) to do anything you want
Setting("DoubleClickThreshold", DllCall("GetDoubleClickTime"))
Setting("UseDoubleClickCheck", false)
; Gesture checking
;
; If enabled, simple gestures are detected, (only supports flick UDLR)
; and gesture events are called for custom actions,
; rather than dragging with momentum.
Setting("UseGestureCheck", false)
Setting("GestureThreshold", 30)
Setting("GesturePageSize", 15)
Setting("GestureBrowserNames", "chrome.exe,firefox.exe,iexplore.exe")
; Change Mouse Cursor
; If enabled, mouse cursor is set to the cursor specified below
Setting("ChangeMouseCursor", true)
; If the above ChangeMouseCursor setting is true, this determines what cursor style
; Choose either:
; "cursorHand" - the original DragToScroll hand icon
; "cursorScrollPointer" - the scrollbar and pointer icon (SYNTPRES.ico)
Setting("ChangedCursorStyle", "cursorScrollPointer")
; If enabled, cursor will stay in its initial position for the duration of the drag
; This can look jittery with the "cursorHand" style because it updates based
; on the PollFrequency setting above
Setting("KeepCursorStationary", true)
Return
; User-Customizable Handlers
;--------------------------------
; double-click handler
; this label is called by DoubleClickCheck
;
ButtonDoubleClick:
; change this to whatever you want to happen at Button double-click
; default behavior below toggles "slow mode"
; close the menu that probably popped up
; the extra "menu" popup is unavoidable.
; You may however attempt to close it automatically
; this may yield unintended results, sending a random {esc}
Sleep 200
Send {Esc}
slowSpeed := .5
bSlowMode := !bSlowMode
Tooltip((bSlowMode ? "Slow" : "Fast") . " Mode")
if (bSlowMode)
{
SpeedY *= slowSpeed
SpeedX *= slowSpeed
}
else
{
SpeedY /= slowSpeed
SpeedX /= slowSpeed
}
Return
; Handlers for gesture actions
; The Up/Down gestures will scroll the page
;
GestureU:
if (WinProcessName = "AcroRd32.exe")
Send, ^{PgDn}
else if (Get("ScrollMethodY") = mWheelMessage)
Loop, % GesturePageSize
Scroll(-1 * (GesturePageSize-A_Index))
else
Send, {PgDn}
Return
GestureD:
if (WinProcessName = "AcroRd32.exe")
Send, ^{PgUp}
else if (Get("ScrollMethodY") = mWheelMessage)
Loop, % GesturePageSize
Scroll((GesturePageSize-A_Index))
else
Send, {PgUp}
Return
GestureL:
if WinProcessName in %GestureBrowserNames%
{
ToolTip("Back", 1)
Send {Browser_Back}
}
else
Send {Home}
Return
GestureR:
if WinProcessName in %GestureBrowserNames%
{
ToolTip("Forward", 1)
Send {Browser_Forward}
}
else
Send {End}
Return
;--------------------------------
;--------------------------------
;--------------------------------
; END OF SETTINGS
; MODIFY BELOW CAREFULLY
;--------------------------------
;--------------------------------
;--------------------------------
; Init
;--------------------------------
Init:
;// make sure to get the correct coords from MouseGetPos when used on
;// a 2nd screen which uses a different scaling % inside Windows
;// https://www.autohotkey.com/boards/viewtopic.php?f=14&t=13810
DllCall("SetThreadDpiAwarenessContext", "ptr", -3, "ptr")
CoordMode, Mouse, Screen
Gosub, Constants
Gosub, Reset
Gosub, LoadLocalSettings
; initialize non-setting & non-reset vars
;ScrollDisabled := false
DragStatus := DS_NEW
TimeOfLastButtonDown := 0
TimeOf2ndLastButtonDown:= 0
TimeOfLastButtonUp := 0
; Initialize menus & Hotkeys
Gosub, MenuInit
; Initialize icons
Menu, Tray, Icon
GoSub, TrayIconInit
GoSub, UpdateTrayIcon
; Initialize GUI for new cursor
if (ChangeMouseCursor) && (ChangedCursorStyle = "cursorScrollPointer")
{
Gui, 98: Add, Pic, x0 y0 vMyIconVar hWndMyIconHwnd 0x3, %A_ScriptDir%\SYNTPRES.ico ; 0x3 = SS_ICON
Gui, 98: Color, gray
Gui, 98: +LastFound -Caption +AlwaysOnTop +ToolWindow
WinSet, TransColor, gray
}
Return
; Constants
;--------------------------------
Constants:
VERSION = 2.5
DEBUG = 0
WM_HSCROLL = 0x114
WM_VSCROLL = 0x115
WM_MOUSEWHEEL = 0x20A
WM_MOUSEHWHEEL = 0x20E
WHEEL_DELTA = 120
SB_LINEDOWN = 1
SB_LINEUP = 0
SB_LINELEFT = 0
SB_LINERIGHT = 1
X_ADJUST = .2 ; constant. normalizes user setting Speed to 1.0
Y_ADJUST = .2 ; constant. normalizes user setting Speed to 1.0
;DragStatus
DS_NEW = 0 ; click has taken place, no action taken yet
DS_DRAGGING = 1 ; handler has picked up the click, suppressed normal behavior, and started a drag
DS_HANDLED = 2 ; click is handled; either finished dragging, normal behavior, or double-clicked
DS_HOLDING = 3 ; drag has been skipped, user is holding down button
DS_MOMENTUM = 4 ; drag is finished, in the momentum phase
INI_GENERAL := "General"
INI_EXCLUDE = ServerSettings
; scroll method
mWheelKey := "WheelKey" ; simulate mousewheel
mWheelMessage := "WheelMessage" ; send WHEEL messages
mScrollMessage := "ScrollMessage" ; send SCROLL messages
URL_DISCUSSION := "https://autohotkey.com/boards/viewtopic.php?f=6&t=38457"
Return
; Cleans up after each drag.
; Ensures there are no false results from info about the previous drag
;
Reset:
OldY=
OldX=
NewX=
NewY=
DiffX=
DiffY=
DiffXSpeed=
DiffYSpeed=
OriginalX=
OriginalY=
CtrlClass=
WinClass=
WinProcessName=
WinHwnd=
CtrlHwnd=
NewWinHwnd=
NewCtrlHwnd=
Target=
Return
; Implementation
;--------------------------------
; Hotkey Handler for button down
;
ButtonDown:
Critical
; Critical forces a hotkey handler thread to be attended to handling any others.
; If not, a rapid click could cause the Button-Up event to be processed
; before Button-Down, thanks to AHK's pseudo-multithreaded handling of hotkeys.
;
; Thanks to 'Guest' for an update to these hotkey routines.
; This update further cleans up, bigfixes, and simplifies the updates.
; Initialize DragStatus, indicating a new click
DragStatus := DS_NEW
GoSub, Reset
; Keep track of the last two click times.
; This allows us to check for double clicks.
;
; Move the previously recorded time out, for the latest button press event.
; Record the current time at the last click
; The stack has only 2 spaces; older values are discarded.
TimeOf2ndLastButtonDown := TimeOfLastButtonDown
TimeOfLastButtonDown := A_TickCount
; Capture the original position mouse position
; Window and Control Hwnds being hovered over
; for use w/ "Constrain" mode & messaging
; Get class names, and process name for per-app settings
MouseGetPos, OriginalX, OriginalY, WinHwnd, CtrlHwnd, 3
MouseGetPos, ,,, CtrlClass, 1
WinGetClass, WinClass, ahk_id %WinHwnd%
WinGet, WinProcessName, ProcessName, ahk_id %WinHwnd%
WinGet, WinProcessID, PID, ahk_id %WinHwnd%
WinProcessPath := GetModuleFileNameEx(WinProcessID)
; Figure out the target
if (UseControlTargeting && CtrlHwnd)
Target := "Ahk_ID " . CtrlHwnd
else if (WinHwnd)
Target := "Ahk_ID " . WinHwnd
else
Target := ""
;ToolTip("Target: " . Target . " ID-WC:" . WinHwnd . "/" . CtrlHwnd . " X/Y:" . OriginalX . "/" . OriginalY . " Class-WC:" . WinClass . "/" CtrlClass . " Process:" . WinProcessPath)
;ToolTip("Process Name:" . WinProcessName . "Process:" . WinProcessPath)
; if we're using the WheelKey method for this window,
; activate the window, so that the wheel key messages get picked up
if (Get("ScrollMethodY") = mWheelKey && !WinActive("ahk_id " . WinHwnd))
WinActivate, ahk_id %WinHwnd%
; Optionally start a timer to see if
; user is holding but not moving the mouse
if (Get("UseMovementCheck"))
SetTimer, MovementCheck, % -1 * Abs(MovementCheckDelay)
if (!Get("ScrollDisabled"))
{
; if scrolling is enabled,
; schedule the drag to start after the delay.
; specifying a negative interval forces the timer to run once
SetTimer, DragStart, % -1 * Abs(DragDelay)
}
else
GoSub, HoldStart
Return
; Hotkey Handler for button up
;
ButtonUp:
; Check for a double-click
; DoubleClickCheck may mark DragStatus as HANDLED
if (UseDoubleClickCheck)
GoSub CheckDoubleClick
; abort any pending checks to click/hold mouse
; and release any holds already started.
SetTimer, MovementCheck, Off
if (DragStatus == DS_HOLDING && GetKeyState(Button))
GoSub, HoldStop
; If status is still NEW (not already dragging, or otherwise handled),
; then the user has released before the drag threshold.
; Check if the user has performed a gesture.
if (DragStatus == DS_NEW && UseGestureCheck)
GoSub, GestureCheck
; If status is STILL NEW (not a gesture either)
; then user has quick press-released, without moving.
; Skip dragging, and treat like a normal click.
if (DragStatus == DS_NEW)
GoSub, DragSkip
; update icons & cursor
; done before handling momentum since we've already released the button
GoSub UpdateTrayIcon
if (ChangeMouseCursor)
{
RestoreSystemCursor()
if (ChangedCursorStyle = "cursorScrollPointer")
Gui, 98: Hide
}
; check for and apply momentum
if (DragStatus == DS_DRAGGING)
GoSub, DragMomentum
; Always stop the drag.
; This marks the status as HANDLED,
; and cleans up any drag that may have started.
GoSub, DragStop
Return
DisabledButtonDown:
Send, {%Button% Down}
Return
DisabledButtonUp:
Send, {%Button% Up}
Return
; Handler for dragging
; Checking to see if scrolling should take place
; for both horizontal and vertical scrolling.
;
; This handler repeatedly calls itself to continue
; the drag once it has been started. Dragging will continue
; until stopped by calling DragStop, halting the timmer.
;
DragStart:
; double check that the click wasn't already handled
if (DragStatus == DS_HANDLED)
return
; schedule the next run of this handler
SetTimer, DragStart, % -1 * Abs(PollFrequency)
; if status is still NEW
; user is starting to drag
; initialize scrolling
if (DragStatus == DS_NEW)
{
; Update the status, we're dragging now
DragStatus := DS_DRAGGING
; Update the cursor & trayicon
SetTrayIcon(hIconDragging)
if (ChangeMouseCursor)
{
if (ChangedCursorStyle = "cursorScrollPointer")
{
;// show GUI with scrolling icon
Gui, 98: Show, x%OriginalX% y%OriginalY% NoActivate
Gui, 98: +LastFound
WinSet, AlwaysOnTop, On
;// "hide" cursor by replacing it with blank cursor (from the AHK help file for DllCall command)
VarSetCapacity(AndMask, 32*4, 0xFF)
VarSetCapacity(XorMask, 32*4, 0)
SetSystemCursor(DllCall("CreateCursor", "uint", 0, "int", 0, "int", 0, "int", 32, "int", 32, "uint", &AndMask, "uint", &XorMask))
}
else
SetSystemCursor(hIconDragging)
}
; set up for next pass
; to find the difference (New - Old)
OldX := OriginalX
OldY := OriginalY
}
Else
{
; DragStatus is now DRAGGING
; get the new mouse position and new hovering window
MouseGetPos, NewX, NewY, NewWinHwnd, NewCtrlHwnd, 3
;ToolTip % "@(" . NewX . "X , " . NewY . "Y) ctrl_" . CtrlClass . " win_" . WinClass . " " . WinProcessName
; If the old and new HWNDs do not match,
; We have moved out of the original window.
; If "Constrain" mode is on, stop scrolling.
if (ConfineToControl && CtrlHwnd != NewCtrlHwnd)
GoSub DragStop
if (ConfineToWindow && WinHwnd != NewWinHwnd)
GoSub DragStop
; Calculate/Scroll - X
; Find the absolute difference in X values
; i.e. the amount the mouse moved in _this iteration_ of the DragStart handler
; If the distance the mouse moved is over the threshold,
; then scroll the window & update the coords for the next pass
DiffX := NewX - OldX
if (abs(DiffX) > DragThreshold)
{
SetTimer, MovementCheck, Off
Scroll(DiffX, true)
if (DragThreshold > 0) && (!KeepCursorStationary)
OldX := NewX
}
; Calculate/Scroll - Y
; SAME AS X
DiffY := NewY - OldY
if (abs(DiffY) > DragThreshold)
{
SetTimer, MovementCheck, Off
Scroll(DiffY)
if (DragThreshold > 0) && (!KeepCursorStationary)
OldY := NewY
}
if (KeepCursorStationary)
MouseMove, OriginalX, OriginalY
else if (ChangedCursorStyle = "cursorScrollPointer")
Gui, 98: Show, x%NewX% y%NewY% NoActivate
; Check for window edge scrolling
GoSub CheckEdgeScrolling
; a threshold of 0 means we update coords
; and attempt to drag every iteration.
; whereas with a positive non-zero threshold,
; coords are updated only when threshold crossing (above)
if (DragThreshold <= 0) && (!KeepCursorStationary)
{
OldX := NewX
OldY := NewY
}
}
Return
; Handler for stopping and cleaning up after a drag is started
; We should always call this after every click is handled
;
DragStop:
; stop drag timer immediately
SetTimer, DragStart, Off
; finish drag
DragStatus := DS_HANDLED
Return
; Handler for skipping a drag
; This just passes the mouse click.
;
DragSkip:
DragStatus := DS_HANDLED
Send {%Button%}
Return
; Entering the HOLDING state
HoldStart:
; abort any pending drag, update status, start holding
SetTimer, DragStart, Off
DragStatus := DS_HOLDING
Send, {%Button% Down}
GoSub UpdateTrayIcon
if (ChangeMouseCursor)
{
RestoreSystemCursor()
if (ChangedCursorStyle = "cursorScrollPointer")
Gui, 98: Hide
}
Return
; Exiting the HOLDING state.
; Should probably mark DragStatus as handled
HoldStop:
DragStatus := DS_HANDLED
Send {%Button% Up}
GoSub UpdateTrayIcon
Return
; This handler allows a click-hold to abort dragging,
; if the mouse has not moved beyond a threshold
MovementCheck:
Critical
; Calculate the distance moved, pythagorean thm
MouseGetPos, MoveX, MoveY
MoveDist := sqrt((OriginalX - MoveX)**2 + (OriginalY - MoveY)**2)
; if we havent moved past the threshold start hold
if (MoveDist <= MovementThreshold)
GoSub, HoldStart
Critical, Off
Return
; Handler to apply momentum at DragStop
; This code continues to scroll the window if
; a "fling" action is detected, where the user drags
; and releases the drag while moving at a minimum speed
;
DragMomentum:
; Check for abort cases
; momentum disabled
; below threshold to use momentum
if (abs(DiffYSpeed) <= MomentumThreshold)
return
if (!Get("UseScrollMomentum"))
return
; passed checks, now using momentum
DragStatus := DS_MOMENTUM
; Immediately stop dragging,
; momentum should not respond to mouse movement
SetTimer, DragStart, Off
; capture the speed when mouse released
; we want to gradually slow to scroll speed
; down to a stop from this initial speed
mSpeed := DiffYSpeed * (Get("InvertDrag")?-1:1)
Loop
{
; stop case: status changed, indicating a user abort
; another hotkey thread has picked up execution from here
; simply exit, do not reset.
if (DragStatus != DS_MOMENTUM)
Exit
; stop case: momentum slowed to minum speed
if (abs(mSpeed) <= MomentumStopSpeed)
return
; for each iteration in the loop,
; reduce the momentum speed linearly
; scroll the window
mSpeed *= MomentumInertia
Scroll(mSpeed, false, "speed")
Sleep % Abs(PollFrequency)
}
Return
; Implementation of Scroll
;
; Summary:
; This is the business end, it simulates input to scroll the window.
; This handler is called when the mouse cursor has been click-dragged
; past the drag threshold.
;
; Arguments:
; * arg
; - measured in Pixels, can just pass mouse coords difference
; - the sign determins direction: positive is down or right
; - the magnitude determines speed
; * horizontal
; - Any non-zero/null/empty value
; will scroll horizontally instead of vertically
; * format
; - Used in some rare cases where passing in 'speed' instead of px
;
; The goal is to take the amount dragged (arg), and convert it into
; an appropriate amount of scroll in the window (Factor).
; First we scale the drag-ammount, according to speed and acceleration
; to the final scroll amount.
; Then we scroll the window, according to the method selected.
;
Scroll(arg, horizontal="", format="px")
{
global
local Direction, Factor, Method, wparam
; get the speed and direction from arg arg
Direction := ( arg < 0 ? -1 : 1 ) * ( Get("InvertDrag") ? -1 : 1 )
Factor := abs( arg )
; Special "hidden" setting, for edge cases (visual studio 2010)
if (horizontal && Get("InvertDragX"))
Direction *= -1
; Do the math to convert this raw px measure into scroll speed
if (format = "px")
{
; Scale by the user-set scroll speed & const adjust
if (!horizontal)
Factor *= Get("SpeedY") * Y_ADJUST
else
Factor *= Get("SpeedX") * X_ADJUST
; Scale by the acceleration function, if enabled
if (!horizontal && Get("UseAccelerationY"))
Factor := Accelerate(Factor)
if (horizontal && Get("UseAccelerationX"))
Factor := Accelerate(Factor)
}
;if (!horizontal) ToolTip, Speed: %arg% -> %Factor%
; Capture the current speed
if (!horizontal)
DiffYSpeed := Factor * Direction
else
DiffXSpeed := Factor * Direction
; Get the requested scroll method
if (!horizontal)
Method := Get("ScrollMethodY")
else
Method := Get("ScrollMethodX")
; Do scroll
; According to selected method
; wparam is used in all methods, as the final "message" to send.
; All methods check for direction by comparing (NewY < OldY)
if (Method = mWheelMessage)
{
; format wparam; one wheel tick scaled by yFactor
; format and send the message to the original window, at the original mouse location
wparam := WHEEL_DELTA * Direction * Factor
;ToolTip, %arg% -> %factor% -> %wparam%
if (!horizontal)
PostMessage, WM_MOUSEWHEEL, (wparam<<16), (OriginalY<<16)|OriginalX,, %Target%
else
{
wparam *= -1 ; reverse the direction for horizontal
PostMessage, WM_MOUSEHWHEEL, (wparam<<16), (OriginalY<<16)|OriginalX,, %Target%
}
}
else if (Method = mWheelKey)
{
; format wparam; either WheelUp or WheelDown
; send as many messages needed to scroll at the desired speed
if (!horizontal)
wparam := Direction < 0 ? "{WheelDown}" : "{WheelUp}"
else
wparam := Direction < 0 ? "{WheelRight}" : "{WheelLeft}"
Loop, %Factor%
Send, %wparam%
}
else if (Method = mScrollMessage)
{
; format wparam; either LINEUP, LINEDOWN, LINELEFT, or LINERIGHT
; send as many messages needed to scroll at the desired speed
if (!horizontal)
{
wparam := Direction < 0 ? SB_LINEDOWN : SB_LINEUP
Loop, %Factor%
PostMessage, WM_VSCROLL, wparam, 0,, Ahk_ID %CtrlHwnd%
}
else
{
wparam := Direction < 0 ? SB_LINERIGHT : SB_LINELEFT
Loop, %Factor%
PostMessage, WM_HSCROLL, wparam, 0,, Ahk_ID %CtrlHwnd%
}
}
}
; Handler to check for a double-click of the right mouse button
; (press-release-press-release), quickly.
; This is called every time the button is released.
;
; We assume that if the mouse button was released,
; then it had to be pressed down to begin with (reasonable?);
; this should be handled by AHK's 'Critical' declaration.
;
CheckDoubleClick:
if (!UseDoubleClickCheck)
return
; Record latest button release time and
; Calculate difference between previous click-release and re-click
; if the difference is below the threshold, treat it as a double-click
TimeOfLastButtonUp := A_TickCount
DClickDiff := TimeOfLastButtonUp - TimeOf2ndLastButtonDown
if (DClickDiff <= DoubleClickThreshold)
{
; Mark the status as Handled,
; so the user-configurable ButtonDoubleClick doesn't have to
; Call the user defined function.
DragStatus := DS_HANDLED
GoSub ButtonDoubleClick
}
Return
; Handler to check for edge scrolling
; Activated when the mouse is dragging and stops
; within a set threshold of the window's edge
; Causes the window to keep scrolling at a set rate
;
CheckEdgeScrolling:
if (!Get("UseEdgeScrolling"))
return
; Get scrolling window position
WinGetPos, WinX, WinY, WinWidth, WinHeight, ahk_id %WinHwnd%
; Find mouse position relative to the window
WinMouseX := NewX - WinX
WinMouseY := NewY - WinY
; find which edge we're closest to and the distance to it
InLowerHalf := (WinMouseY > WinHeight/2)
EdgeDistance := (InLowerHalf) ? Abs( WinHeight - WinMouseY ) : Abs( WinMouseY )
;atEdge := (EdgeDistance <= EdgeScrollingThreshold ? " @Edge" : "") ;debug
;ToolTip, %WinHwnd%: %WinMouseY% / %WinHeight% -> %EdgeDistance% %atEdge% ;debug
; if we're close enough, scroll the window
if (EdgeDistance <= EdgeScrollingThreshold)
{
; prep and call scrolling
; the second arg requests the scroll at the set speed without accel
arg := (InLowerHalf ? 1 : -1) * (Get("InvertDrag") ? -1 : 1) * Get("EdgeScrollSpeed")
Scroll(arg, false, "speed")
}
Return
; Handler to check for gesture actions
; This handler only supports simple "flick" gestures;
; because the whole gesture needs to be completed before DragThreshold,
; and also makes the logic easy, by a simple threshold
;
GestureCheck:
MouseGetPos, MoveX, MoveY
MoveAmount := (abs(OriginalY-MoveY) >= abs(OriginalX-MoveX)) ? OriginalY-MoveY : OriginalX-MoveX
MoveDirection := (abs(OriginalY-MoveY) >= abs(OriginalX-MoveX)) ? (OriginalY>MoveY ? "U" : "D") : (OriginalX>MoveX ? "L" : "R")
; If the move amount is above the threshold,
; Immediately stop/cancel dragging and call the correct gesture handler
if (abs(MoveAmount) >= GestureThreshold)
{
GoSub, DragStop
GoSub, Gesture%MoveDirection%
}
Return
; Settings Functions
;--------------------------------
; A wrapper around the GetSetting function.
; Returns the ini GetSetting value, or the
; in-memory global variable of the same name.
;
; Provides and easy and seamless wrapper to
; overlay user preferences on top of app settings.
;
Get(name, SectionName="")
{
global
local temp
if (DEBUG)
{
temp := %name%
return temp
}
temp := GetSetting(name, SectionName)
if (temp != "")
return temp
else
{
temp := %name%
return temp
}
}
; Retrieves a named setting from the global ini
; This function operates both as a "search" of
; the ini, as well as a named get. You can optionally
; specify a section name to retrieve a specific value.
;
; By Default, this searches the ini file in any of
; a set of valid SectionNames. The default section 'General'
; is a last resort, if an app specific setting was not found.
; Section names are searched for the target control class,
; window class, and process name. If any of these named sections
; exist in ini, its key value is returned first.
;
GetSetting(name, SectionName="")
{
global INI_GENERAL
global CtrlClass, WinClass, WinProcessName, WinProcessPath
global ini, ConfigSections
; find the section, using the cached list
if (!SectionName)
{
; by control class
IfNotEqual, CtrlClass
If CtrlClass in %ConfigSections%
SectionName := CtrlClass
; by window class
IfNotEqual, WinClass
If WinClass in %ConfigSections%
SectionName := WinClass
; by process name
IfNotEqual, WinProcessName
If WinProcessName in %ConfigSections%
SectionName := WinProcessName
; by process path
IfNotEqual, WinProcessPath,
If WinProcessPath in %ConfigSections%
SectionName := WinProcessPath
; last chance
if (!SectionName)
SectionName := INI_GENERAL
}
;get the value
temp := ini_getValue(ini, SectionName, name)
; check for special keywords
if (temp = "false")
temp := 0
if (temp = "true")
temp := 1
;if (SectionName != INI_GENERAL)
; ToolTip, % "Request " . name . ":`n" . ini_getSection(ini, SectionName)
return temp
}
; Saves a setting/variable to the ini file
; in the given section name (default General)
; with the given value, or the current variable value
;
SaveSetting(name, value="", SectionName="General")
{
; prep value
global
local keyList, temp
if (SectionName = "")
{
MsgBox, 16, DtS, Setting Save Failed `nEmpty SectionName
return
}
keyList := ini_getAllKeyNames(ini, SectionName)
if (!value)
value := %name%
; if no section
if SectionName not in %ConfigSections%
{
if (!ini_insertSection(ini, SectionName, name . "=" . value))
{
MsgBox, 16, DtS, Setting Save Failed `ninsertSection %ErrorLevel%
return
}
ConfigSections := ini_getAllSectionNames(ini)
}
; if no value
else if name not in %keyList%
{
if (!ini_insertKey(ini, SectionName, name . "=" . value))
{
MsgBox, 16, DtS, Setting Save Failed `ninsertKey %ErrorLevel%
return
}
}