Animation on Mouse Click

Post your working scripts, libraries and tools
JJohnston2
Posts: 193
Joined: 24 Jun 2015, 23:38

Animation on Mouse Click

04 Mar 2017, 20:36

This is proof-of-concept code that will animate mouse clicks with concentric/fading circles that can be adjusted to taste.

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.).
Image

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
brutus_skywalker
Posts: 175
Joined: 24 Dec 2016, 13:16
Location: Antarctica

Re: Animation on Mouse Click

05 Mar 2017, 00:54

SWEET!
Outsourcing Clicks & Presses Since 2004.

Return to “Scripts and Functions”

Who is online

Users browsing this forum: Chiefkes, leehere10 and 24 guests