I wanted something like this to show an automated mouse-click event at a particular location--this demo code fires the animation any time the user clicks the mouse.
It also requires the GDIP library which can be downloaded via the links on this thread.
It looks a little like the animation image below, although not quite as polished since the circles that get drawn with the GDIP aren't anti-aliased. The overall effect however is very similar, and there are several animation adjustments that have been grouped together in the code to change or exaggerate the effect if desired (size, color, speed, etc.).
Code: Select all
;-----------------------------------------------------------------------------------------------------------------------
; AddAnimatedClick.ahk - Animate mouse clicks
;
; See https://autohotkey.com/boards/viewtopic.php?f=6&t=28837
;-----------------------------------------------------------------------------------------------------------------------
#SingleInstance, Force
#Persistent
;-----------------------------------------------------------------------------------------------------------------------
; Super-Globals
;-----------------------------------------------------------------------------------------------------------------------
Global oGdip:=Object()
Global savedX, savedY
;-----------------------------------------------------------------------------------------------------------------------
; Main()
;-----------------------------------------------------------------------------------------------------------------------
Main:
;-------------------------------------------------------------------------------
; Set exit routine
;-------------------------------------------------------------------------------
OnExit("myExit")
;-------------------------------------------------------------------------------
; Set up tray menu options
;-------------------------------------------------------------------------------
thisMenu:="Tray"
Menu, % thisMenu, Icon, shell32.dll, 101
Menu, % thisMenu, Tip, AddAnimatedClick
;-------------------------------------------------------------------------------
; Initialize
;-------------------------------------------------------------------------------
AnimatedClickInit()
Return
;-----------------------------------------------------------------------------------------------------------------------
; Animate the mouse button clicks but let the clicks pass through with ~ in front of Hotkey definitions
;-----------------------------------------------------------------------------------------------------------------------
~LButton::
~MButton:: ; This can be commented out if only Left Button is desired
~RButton:: ; This can be commented out if only Left Button is desired
SetTimer, SaveClick, -1
Return
;-----------------------------------------------------------------------------------------------------------------------
; SaveClick()
;
; This will only save 1 click since it's not really a true queue, but appears to work most of the time to keep up with user clicks...
; could technically be defeated by someone going really fast but this only has to account for the animated click taking just long enough to
; block the next click... i.e., works as long as the user is only one click ahead of us...
;-----------------------------------------------------------------------------------------------------------------------
SaveClick() {
Thread, Interrupt, 0 ; Allow multiple clicks immediately after one another without locking out the second one
CoordMode, Mouse, Screen
MouseGetPos, savedX, savedY
SetTimer, AnimatedClick, -1
}
;-----------------------------------------------------------------------------------------------------------------------
; AnimatedClickInit() - Initialize GUI and GDIP variables for click animations
;-----------------------------------------------------------------------------------------------------------------------
AnimatedClickInit() {
;-------------------------------------------------------------------------------
; Set up GUI
;-------------------------------------------------------------------------------
Gui, New
;-------------------------------------------------------------------------------
; Create a layered window (+E0x80000 : must be used for UpdateLayeredWindow to work!)
; Always on top = (+AlwaysOnTop), has no taskbar entry or caption
;-------------------------------------------------------------------------------
Gui, +AlwaysOnTop
Gui, +Hwndhwnd1 ; Get a handle to this window we have created in order to update it later
oGdip.hwnd1 := hwnd1 ; could use A_ScriptHwnd but this links directly to GUI if this is integrated in another program
Gui, -Caption +E0x80000 +LastFound +ToolWindow +OwnDialogs
Gui, Show, % "NA"
;-------------------------------------------------------------------------------
; Create GDI Context
;-------------------------------------------------------------------------------
If (!(oGdip.pToken := Gdip_Startup())) {
MsgBox, 48, % "gdiplus error!", % "Gdiplus failed to start. Please ensure you have gdiplus on your system"
ExitApp
}
;-------------------------------------------------------------------------------
; Get the full screen resolution
;-------------------------------------------------------------------------------
SysGet, VirtualScreenWidth, 78 ; See help files for message indices
SysGet, VirtualScreenHeight, 79
oGdip.x:=0, oGdip.y:=0 ; Location and size of the GDIP window
oGdip.w:=VirtualScreenWidth, oGdip.h:=VirtualScreenHeight
;-------------------------------------------------------------------------------
; Initialize GDIP variables and store in the global object for subsequent calls
;-------------------------------------------------------------------------------
oGdip.hbm := CreateDIBSection(oGdip.w, oGdip.h) ; Create a gdi bitmap with width and height of what we are going to draw into it. This is the entire drawing area for everything
oGdip.hdc := CreateCompatibleDC() ; Get a device context compatible with the screen
oGdip.obm := SelectObject(oGdip.hdc, oGdip.hbm) ; Select the bitmap into the device context
oGdip.G := Gdip_GraphicsFromHDC(oGdip.hdc) ; Get a pointer to the graphics of the bitmap, for use with drawing functions
Gdip_GraphicsClear(oGdip.G) ; Initial clear
}
;-----------------------------------------------------------------------------------------------------------------------
; AnimatedClick() - Animate a Mouse Click
;
; Calling Arguments
; - x/y - Screen position to show the animated click at. If these are blank the mouse location will be used
;
; Called From:
; - LButton Hotkey / handler
;
; Return Value
; - N/A
;-----------------------------------------------------------------------------------------------------------------------
AnimatedClick(x:="", y:="") {
Thread, Interrupt, 0 ; allow multiple clicks immediately after one another without locking out the second one
; If there are saved values ready, use those...
If ((x="") || (y="")) && ((savedX<>"") && (savedY<>"")) {
mouseX:=savedX, mouseY:=savedY
savedX:="", savedY:=""
; If there are no values passed in and no saved values, get mouse location...
} Else If ((x="") || (y="")) {
CoordMode, Mouse, Screen
MouseGetPos, mouseX, mouseY
; If there were values passed in, use those...
} Else
mouseX:=x, mouseY:=y
;-------------------------------------------------------------------------------
; Initialize variables that will define what the circles look like
;-------------------------------------------------------------------------------
oGdip.penWidth:=2 ; This will also affect how big the circles get since they are multiplied by penWidth
thisAlphaStart := 0xFF, alphaFadeRate:=6
thisGrayStart := 0x55, grayFadeRate:=0.5
startN:=1 ; Starting radius
maxN:=6, rate:=1.5 ; These values set how big the circles get and at what rate
FinalFadeOut:=True ; Perform a final fade-out at max radius if desired...
exitFadeAlpha := 0 ; Early exit value on final fade-out if non-zero (i.e., fade to almost zero but exit before then)
ProgressiveDelayEnable := True ; Slow down as the circles get bigger
ProgressiveDelayFactor := 0.5 ; DelayFactor for how much to slow down
;-------------------------------------------------------------------------------
; Loop to draw concentric circles...
;-------------------------------------------------------------------------------
While (n<=maxn) && (savedX="" && savedY="") { ; Check for saved values to exit early if needed (feels more responsive)
n:=startN+A_Index
nCircle := n*oGdip.penWidth*rate
thisAlpha := (thisAlphaStart-(A_Index**2)*alphaFadeRate) ; Set alphaFadeRate to determine how fast the circles fade
thisAlpha := (thisAlpha>=thisAlphaStart) ? 0 : thisAlpha ; Don't go past 0x00 if it gets too low
thisAlphaShifted := thisAlpha << 24
thisGray := (thisGrayStart+(A_Index**2)*grayFadeRate)
thisGray := (thisGray<=thisGrayStart) ? 0xFF : thisGray ; Don't go past 0xFF if it gets too high
thisARGB := thisAlphaShifted | (RGB := (thisGray | thisGray << 8 | thisGray << 16))
If (n>1)
Gdip_GraphicsClear(oGdip.G) ; Erase the last circle if we've already drawn one
If (n<=maxn) {
oGdip.pPen1:=Gdip_CreatePen(thisARGB, oGdip.penWidth)
Gdip_DrawEllipse(oGdip.G, oGdip.pPen1, mouseX-nCircle, mouseY-nCircle, nCircle*2, nCircle*2)
;thisARGB := ((0xFF-(thisAlpha>>24))/2) | RGB ; make the alpha halfway to 0xFF for the second circle to try and get anti-aliasing
;oGdip.pPen2:=Gdip_CreatePen(thisARGB, oGdip.penWidth*2)
;Gdip_DrawEllipse(oGdip.G, oGdip.pPen2, mouseX-(n*oGdip.penWidth)+oGdip.penWidth, mouseY-(n*oGdip.penWidth)+oGdip.penWidth, (n*oGdip.penWidth)*2-oGdip.penWidth, (n*oGdip.penWidth)*2-oGdip.penWidth)
UpdateLayeredWindow(oGdip.hwnd1, oGdip.hdc, oGdip.x, oGdip.y, oGdip.w, oGdip.h)
}
;-------------------------------------------------------------------------------
; Perform a final fade-out at max radius if desired...
;-------------------------------------------------------------------------------
If FinalFadeOut && (n=maxn) {
While ((thisAlpha:=(thisAlphaShifted>>24)) > exitFadeAlpha) {
thisAlpha := thisAlpha-(dec:=28) ; This decrement variable controls how fast the final fade happens at
thisAlpha := (thisAlpha<exitFadeAlpha) ? exitFadeAlpha : thisAlpha ; Don't go past exitFadeAlpha (or 0 worst case) if it gets too low
thisAlphaShifted := thisAlpha << 24
thisARGB := thisAlphaShifted | RGB ; Adjust inc variable to make the final fade slower
oGdip.pPen1:=Gdip_CreatePen(thisARGB, oGdip.penWidth) ; Update ARGB used for pen
Gdip_GraphicsClear(oGdip.G) ; Erase the last circle if we've already drawn one
Gdip_DrawEllipse(oGdip.G, oGdip.pPen1, mouseX-nCircle, mouseY-nCircle, nCircle*2, nCircle*2)
UpdateLayeredWindow(oGdip.hwnd1, oGdip.hdc, oGdip.x, oGdip.y, oGdip.w, oGdip.h)
}
} Else If (savedX="" && savedY="") && ProgressiveDelayEnable ; Don't sleep if we're exiting early...
Sleep % (n**2)*ProgressiveDelayFactor ; Sleep longer as a function of loop index squared
}
;-------------------------------------------------------------------------------
; Erase the last circle
;-------------------------------------------------------------------------------
Gdip_GraphicsClear(oGdip.G)
UpdateLayeredWindow(oGdip.hwnd1, oGdip.hdc, x, y, w, h)
}
;-----------------------------------------------------------------------------------------------------------------------
; myExit()
;-----------------------------------------------------------------------------------------------------------------------
myExit() {
Gdip_DeletePen(oGdip.pPen) ; Delete the pen as it is no longer needed and wastes memory
SelectObject(oGdip.hdc, oGdip.obm) ; Select the object back into the hdc
DeleteObject(oGdip.hbm) ; Now the bitmap may be deleted
DeleteDC(oGdip.hdc) ; Also the device context related to the bitmap may be deleted
Gdip_DeleteGraphics(oGdip.G) ; The graphics may now be deleted
Gui, Destroy ; Kill the GUI
ExitApp ; Exit Application
}
;-----------------------------------------------------------------------------------------------------------------------
; #Includes
;-----------------------------------------------------------------------------------------------------------------------
#Include GDIP.ahk