#include-once
#include <GDIPlus.au3>

If Opt('MouseCoordMode', 2) <> 2 Then ConsoleWrite('- _GDIPlus_Transform needs Opt("MouseCoordMode", 2). This was set automatically.' & @CRLF)

Global Const $_GDIPT_LEFT = 1   ; Linksklick
Global Const $_GDIPT_RIGHT = 2  ; Rechtsklick
Global Const $_GDIPT_MIDDLE = 3 ; Mouse Wheel Click (heute hat ja keiner mehr ne Maus mit 3 Tasten)

Global $__GDIPT_MOUSEWHEEL = 0
Global $__GDIPT_DEFAULTGUI = Null ; Für WM_MOUSEWHEEL
Global Const $__GDIPT_WM_MOUSEWHEEL = 0x020A
Global $__GDIPT_USER32 = DllOpen('user32.dll')
OnAutoItExitRegister(__GDIPlus_TransformShutdown)
GUIRegisterMsg($__GDIPT_WM_MOUSEWHEEL, __GDIPlus_Transform_WM_MOUSEWHEEL)

; x, y, w, h müssen der Ort sein an den später der Buffer gezeichnet wird.
Func _GDIPlus_TransformCreate($hGUI, $hGFX, $iX, $iY, $iW, $iH)
	Local $Transform[], $Mouse[]

	; Interne Informationen (bitte als ReadOnly betrachten)
	$Transform.x = $iX
	$Transform.y = $iY
	$Transform.w = $iW
	$Transform.h = $iH
	$Transform.dx = 0.0
	$Transform.dy = 0.0
	$Transform.zoom = 1.0
	$Transform.targetZoom = 1.0
	$Transform.gfx = $hGFX
	$Transform.mat = 0               ;
	$Transform.gui = $hGUI           ;
	$Transform.Mouse = $Mouse        ; Enthält aktuelle Informationen zur Mausposition (im ScreenSpace!) und Buttons, [x, y, leftDown, middleDown, rightDown, ctrlID]
	$Transform.containsMouse = False ; Ist automatisch True/False, wenn die Maus innerhalb oder außerhalb des Rechtecks ist
	$Transform.primaryDown = False   ; True, wenn $Transform.moveButton gedrückt ist

	; Einstellungen (diese Werte kann man nach beliebigen einstellen)
	$Transform.borderX = 1e6             ; Wie weit kann in x-Richtung verschoben werden? [Wert in Px]
	$Transform.borderY = 1e6             ; Wie weit kann in y-Richtung verschoben werden? [Wert in Px]
	$Transform.zoomMin = 0.1             ; Minimal erlaubter Zoomfaktor
	$Transform.zoomMax = 10.0            ; Maximal erlaubter Zoomfaktor
	$Transform.zoomStep = 1.2            ; Schrittweite beim Zoomen in % (1.2 = 120% = Die Anzeige wird pro zoom-Tick auf 120% bzw auf 1/120% skaliert)
	$Transform.invertY = False           ; Für Graphplots kann man invertY aktivieren (dann ist 0/0 unten links statt oben links)
	$Transform.smoothZoom = 1.0          ; Je höher der Wert, desto smoother der Zoom. 0 Deaktiviert den smooth-zoom
	$Transform.moveButton = $_GDIPT_LEFT ; Hier kann man $_GDIPT_LEFT, $_GDIPT_RIGHT, $_GDIPT_MIDDLE angeben

	If $__GDIPT_DEFAULTGUI = Null Then $__GDIPT_DEFAULTGUI = $hGUI
	Return $Transform
EndFunc

; Vor die Zeichenoperationen die man durchführen will.
Func _GDIPlus_TransformBegin(ByRef $Transform)
	__GDIPlus_TransformUpdateCursorInfo($Transform)
	$Transform.containsMouse = __GDIPlus_TransformMouseInside($Transform)
	If $Transform.containsMouse Or ($Transform.zoom <> $Transform.targetZoom) Then
		__GDIPlus_TransformTranslate($Transform)
		If ($__GDIPT_MOUSEWHEEL <> 0 Or $Transform.zoom <> $Transform.targetZoom) Then
			Local $aPos = [$Transform.Mouse.x - $Transform.x, $Transform.Mouse.y - $Transform.y]
			Local $nZoomFactor = $__GDIPT_MOUSEWHEEL > 0 ? $Transform.zoomStep : $__GDIPT_MOUSEWHEEL < 0 ? 1 / $Transform.zoomStep : 1
			$__GDIPT_MOUSEWHEEL = 0
			Local $nZoomBefore = $Transform.targetZoom, $nZoomAfter = $nZoomBefore * $nZoomFactor
			If $nZoomAfter < $Transform.zoomMin Then $nZoomAfter = $Transform.zoomMin
			If $nZoomAfter > $Transform.zoomMax Then $nZoomAfter = $Transform.zoomMax
			$Transform.targetZoom = $nZoomAfter
			$nZoomBefore = $Transform.zoom
			$Transform.zoom = ($Transform.zoom * $Transform.smoothZoom + $Transform.targetZoom) / ($Transform.smoothZoom + 1)
			$nZoomAfter = $Transform.zoom
			$nZoomFactor = $nZoomAfter / $nZoomBefore
			If Abs($Transform.zoom - $Transform.targetZoom) < 0.0001 Then $Transform.zoom = $Transform.targetZoom
			Local $dx = ((($aPos[0] - $Transform.dx) / $nZoomAfter) - (($aPos[0] - $Transform.dx) / $nZoomBefore)) * $nZoomAfter
			Local $dy = 0
			If $Transform.invertY Then
				$dy = (((($Transform.h - $aPos[1]) - $Transform.dy) / $nZoomAfter) - ((($Transform.h - $aPos[1]) - $Transform.dy) / $nZoomBefore)) * $nZoomAfter
			Else
				$dy = ((($aPos[1] - $Transform.dy) / $nZoomAfter) - (($aPos[1] - $Transform.dy) / $nZoomBefore)) * $nZoomAfter
			EndIf
			__GDIPlus_TransformTranslate($Transform, $dx, $dy, True)
		EndIf
	EndIf
	$Transform.mat = _GDIPlus_MatrixCreate()
	_GDIPlus_MatrixTranslate($Transform.mat, $Transform.dx, $Transform.dy)
	_GDIPlus_MatrixScale($Transform.mat, $Transform.zoom, $Transform.zoom)
	_GDIPlus_GraphicsSetTransform($Transform.gfx, $Transform.mat)
EndFunc

; Berechnet die World Space Mausposition (z.B. die Maus ist bei 250/1000px, das entspricht 0.1241256/-154.554256 in der Anzeige)
Func _GDIPlus_TransformMouseGetPos(ByRef $Transform)
	Local $aPos = [($Transform.Mouse.x - $Transform.dx - $Transform.x) / $Transform.zoom, ($Transform.Mouse.y - $Transform.dy - $Transform.y) / $Transform.zoom]
	Return $aPos
EndFunc

; Nach den Zeichenoperationen
Func _GDIPlus_TransformEnd(ByRef $Transform)
	_GDIPlus_GraphicsResetTransform($Transform.gfx)
	If $Transform.mat Then
		_GDIPlus_MatrixDispose($Transform.mat)
		$Transform.mat = 0
	EndIf
EndFunc

; Falls man mehrere Fenster verwenden möchte (ist nicht getestet!)
Func _GDIPlus_TransformSetGUI($hGUI)
	$__GDIPT_DEFAULTGUI = $hGUI
EndFunc

#Region Internal use

Func __GDIPlus_TransformTranslate(ByRef $Transform, $dx = 0, $dy = 0, $bDirect = False)
	If $bDirect Or ($Transform.primaryDown And WinActive($Transform.gui)) Then
		If Not $bDirect Then
			$dx = -($Transform.Mouse.lastX - $Transform.Mouse.x)
			$dy = $Transform.invertY ? $Transform.Mouse.lastY - $Transform.Mouse.y : -($Transform.Mouse.lastY - $Transform.Mouse.y)
		EndIf
		$Transform.dx += $dx
		$Transform.dy += $dy
		If $Transform.dx < $Transform.w - ($Transform.w + $Transform.borderX) * $Transform.zoom Then $Transform.dx = $Transform.w - ($Transform.w + $Transform.borderX) * $Transform.zoom
		If $Transform.dy < $Transform.h - ($Transform.h + $Transform.borderY) * $Transform.zoom Then $Transform.dy = $Transform.h - ($Transform.h + $Transform.borderY) * $Transform.zoom
		If $Transform.dx > $Transform.borderX * $Transform.zoom Then $Transform.dx = $Transform.borderX * $Transform.zoom
		If $Transform.dy > $Transform.borderY * $Transform.zoom Then $Transform.dy = $Transform.borderY * $Transform.zoom
	EndIf
EndFunc

; Aktualisiert die Maus Informationen
Func __GDIPlus_TransformUpdateCursorInfo(ByRef $Transform)
	Local $_ = GUIGetCursorInfo($Transform.gui)
	$Transform.Mouse.lastX = $Transform.Mouse.x
	$Transform.Mouse.lastY = $Transform.Mouse.y
	$Transform.Mouse.x = $_[0]
	$Transform.Mouse.y = $_[1]
	$Transform.Mouse.leftDown = $_[2]
	$Transform.Mouse.middleDown = BitAND(DllCall($__GDIPT_USER32, 'short', 'GetAsyncKeyState', 'int', 0x04)[0], 0x8000) <> 0
	$Transform.Mouse.rightDown = $_[3]
	$Transform.Mouse.ctrlID = $_[4]
	Switch $Transform.moveButton
		Case $_GDIPT_LEFT
			$Transform.primaryDown = $Transform.Mouse.leftDown
		Case $_GDIPT_MIDDLE
			$Transform.primaryDown = $Transform.Mouse.middleDown
		Case $_GDIPT_RIGHT
			$Transform.primaryDown = $Transform.Mouse.rightDown
	EndSwitch
EndFunc

; Wird via OnAutoItExitRegister automatisch aufgerufen
Func __GDIPlus_TransformShutdown()
	DllClose($__GDIPT_USER32)
EndFunc

; Return: True  -> Maus ist innerhalb des Rechtecks
;         False -> Maus ist außerhalb
Func __GDIPlus_TransformMouseInside(ByRef $Transform)
	Return $Transform.Mouse.x > $Transform.x And $Transform.Mouse.x < $Transform.x + $Transform.w And $Transform.Mouse.y > $Transform.y And $Transform.Mouse.y < $Transform.y + $Transform.h
EndFunc

; GUIRegisterMsg WM_MOUSEWHEEL
Func __GDIPlus_Transform_WM_MOUSEWHEEL($hWnd, $iMsg, $wParam, $lParam)
	If $hWnd <> $__GDIPT_DEFAULTGUI Then Return
	$__GDIPT_MOUSEWHEEL = BitShift($wParam, 16) / 120.0
EndFunc
#EndRegion