;-- TIME_STAMP   2020-04-12 08:06:11   v 0.2

#include-once
#include <WinAPI.au3>
#include <WinAPISysWin.au3>
#include <WindowsConstants.au3>
OnAutoItExitRegister('__MouseTrapCircleExit')

Global $g__hSTUB__MouseProc, $g__hMOD, $g__hHOOK_MOUSE
Global $g__bTrapped = False
Global $g__aCircle[3]



; #FUNCTION# ====================================================================================================================
; Name ..........: _MouseTrapCircle
; Description ...: Traps the mouse in a circle, defined by center or top-left edge and its diameter.
; Parameters ....: $_hWnd       If the circle is inside a window - its handle, otherwise "-1". (Default = -1)
; ...............: $_x          The x value of the circles center (default) or its top-left edge.
; ...............: $_y          The y value of the circles center (default) or its top-left edge.
; ...............: $_iDiameter  The diameter of the circle. (Default = Null)
; ...............: $_bTopLeft   Determines, if the kind of passed x,y is top-left edge (True). Default (False) uses the center of circle.
; Return values .: None
; Author ........: BugFix
; Remarks .......: Call the function with the defaults to give the mouse free.
; ===============================================================================================================================
Func _MouseTrapCircle($_hWnd=-1, $_x=Null, $_y=Null, $_iDiameter=Null, $_bTopLeft=False)
	Local Static $bStarted = False
	Local $iTopborder = 0, $iSideborder = 0, $tWin = DllStructCreate('int Left;int Top;int Right;int Bottom')
	If Not $bStarted Then
		__MouseTrapCircleStartUp()
		$bStarted = True
	EndIf
	If $_x = Null Then
		$g__bTrapped = False
	Else
		If IsHWnd($_hWnd) Then
			$tWin = _WinAPI_GetWindowRect($_hWnd)
			_SystemGetWindowBorder($iTopBorder, $iSideBorder)
		EndIf
		If $_bTopLeft Then
			$_x -= $_iDiameter/2
    		$_y -= $_iDiameter/2
		EndIf
		$g__aCircle[0] = $_x + $tWin.Left + $iSideborder
		$g__aCircle[1] = $_y + $tWin.Top + $iTopborder
		$g__aCircle[2] = $_iDiameter/2
		MouseMove($g__aCircle[0], $g__aCircle[1], 5) ; when activated, move to center
		$g__bTrapped = True
	EndIf
EndFunc  ;==>_MouseTrapCircle



#Region - helper functions

Func __MouseTrapMouseProc($nCode, $wParam, $lParam)
	If Not $g__bTrapped Then Return _WinAPI_CallNextHookEx($g__hHOOK_MOUSE, $nCode, $wParam, $lParam)
	Local $tMOUSE = DllStructCreate("int X;int Y;dword mouseData;dword flags;dword time;ulong_ptr dwExtraInfo", $lParam)
	If Not __MouseTrapPointInCircle($tMOUSE.X, $tMOUSE.Y, $g__aCircle[0], $g__aCircle[1], $g__aCircle[2]) Then
		Return -1 ; mouse can move only inside the circle
	Else
		Return _WinAPI_CallNextHookEx($g__hHOOK_MOUSE, $nCode, $wParam, $lParam)
	EndIf
EndFunc  ;==>__MouseTrapMouseProc


Func __MouseTrapPointInCircle($_xPoint, $_yPoint, $_xCenter, $_yCenter, $_iRadius)
	Return (($_xCenter-($_xPoint))^2 + ($_yCenter-($_yPoint))^2 <= $_iRadius^2)
EndFunc  ;==>__MouseTrapPointInCircle


Func __MouseTrapCircleStartUp()
	; == initialize Callback Function to analyze MOUSE-Message
	$g__hSTUB__MouseProc = DllCallbackRegister("__MouseTrapMouseProc", "long", "int;wparam;lparam")
	$g__hMOD = _WinAPI_GetModuleHandle(0)
	$g__hHOOK_MOUSE = _WinAPI_SetWindowsHookEx($WH_MOUSE_LL, DllCallbackGetPtr($g__hSTUB__MouseProc), $g__hMOD)
EndFunc  ;==>__MouseTrapCircleStartUp


Func __MouseTrapCircleExit()
    _WinAPI_UnhookWindowsHookEx($g__hHOOK_MOUSE)
    DllCallbackFree($g__hSTUB__MouseProc)
EndFunc  ;==>__MouseTrapCircleExit


;===============================================================================
; Function Name....: __SystemGetWindowBorder
; Description......: Calculates side and top border of window
; Author(s)........: BugFix
;===============================================================================
Func _SystemGetWindowBorder(ByRef $_iTopBorder, ByRef $_iSideBorder)
	Local Const $SM_CYCAPTION = 4, $SM_CYEDGE = 46, $SM_CYBORDER = 6, $SM_CXBORDER = 5, $SM_CXEDGE = 45
	Local $aMetrics[5][2] = [[$SM_CYCAPTION], [$SM_CYEDGE], [$SM_CYBORDER], [$SM_CXBORDER], [$SM_CXEDGE]]
	Local $dll = DllOpen("user32.dll"), $aRet
	For $i = 0 To 4
		$aRet = DllCall($dll, "int", "GetSystemMetrics", "int", $aMetrics[$i][0])
		If IsArray($aRet) Then $aMetrics[$i][1] = $aRet[0]
	Next
	DllClose($dll)
	$_iTopBorder  = $aMetrics[0][1] + $aMetrics[1][1] + $aMetrics[2][1]
	$_iSideBorder = $aMetrics[3][1] + $aMetrics[4][1]
EndFunc  ;==>__SystemGetWindowBorder

#EndRegion
