#include-once

; Name   : User Interface Bounding Box (UIBB)
; Author : Mars
; Version: 27.09.23

; Modified: 27.09.23
; - Aufräumdienst war hier
; - MinSize für Root-Box
; - Size Einstellungen sind jetzt auch in _UIBB_Box enthalten

; Geschwindigkeitsvergleich Map/Array, $TEST = 10, Arraysize = 11 -> Schnellstes: Map mit Integer Key, Für Lesbarkeit $CONST verwenden?
;~ $a[10]     = $i	0.114 (Map)
;~ $a[10]     = $i	0.150 (Array)
;~ $a[$TEST]  = $i	0.122 (Map)
;~ $a[$TEST]  = $i	0.166 (Array)
;~ $a['TEST'] = $i	0.146 (Map)
;~ $a.TEST    = $i	0.325 (Map)

; Globals ===========================================================
Global Const $UIBB_VBOX = -1  ; Vertical Vector of Elements
Global Const $UIBB_HBOX = -2  ; Horizontal Vector of Elements
Global Const $UIBB_EMPTY = -3 ; Padding
Global Const $UIBB_CTRL = -4  ; GuiCtrl
; ===================================================================

; TODO -> Neue Rechenmethode statt Iterativer Löser.
Global Const $__UIBB_MAXITER = 15 ; Anzahl Fit-Iterationen. Je mehr, desto genauer werden Fixierte Größen (z.B. Breite = 100) eingehalten.

; Functions =========================================================
Func _UIBB_SetSize(ByRef $BOX, $nMinSizePx, $nMaxSizePx = -1)
	If $nMaxSizePx = 0 Then $nMaxSizePx = 65535
	If $nMaxSizePx = -1 Or $nMaxSizePx < $nMinSizePx Then $nMaxSizePx = $nMinSizePx
	$BOX['MINP'] = $nMinSizePx
	$BOX['MAXP'] = $nMaxSizePx
EndFunc   ;==>_UIBB_SetSize

Func _UIBB_SetPadding(ByRef $NODE, $leftPx, $rightPx, $topPx, $botPx)
	$NODE['PADD'] = __UIBB_Padding($leftPx, $rightPx, $topPx, $botPx)
EndFunc   ;==>_UIBB_SetPadding

Func _UIBB_Box($iTYPE = $UIBB_EMPTY, $nMinSizePx = 0, $nMaxSizePx = 0, $hGUI = '')
	If @NumParams = 2 Then $nMaxSizePx = $nMinSizePx
	If $nMaxSizePx = 0 And Not IsPtr($hGUI) Then $nMaxSizePx = 65535
	Local $BOX = __UIBB_Node(), $MAP[]
	$BOX['TYPE'] = $iTYPE
	$BOX['CHIL'] = $MAP
	$BOX['MINP'] = $nMinSizePx
	$BOX['MAXP'] = $nMaxSizePx
	If IsPtr($hGUI) Then $BOX['HAND'] = $hGUI
	Return $BOX
EndFunc   ;==>_UIBB_Box

Func _UIBB_Insert(ByRef $PARENT, $xChild, $nScale = 1)
	If IsFunc($xChild) Then Return __UIBB_InsertCtrl($PARENT, $xChild, $nScale)
	Return __UIBB_InsertBox($PARENT, $xChild, $nScale)
EndFunc   ;==>_UIBB_Insert

Func _UIBB_Update(ByRef $NODE)
	__UIBB_UpdateClientArea($NODE, 0) ; Update everything using $hBOX as Root
EndFunc   ;==>_UIBB_Update
; ===================================================================

; Internals =========================================================
Func __UIBB_Node()
	Local $NODE[]
	$NODE['TYPE'] = $UIBB_EMPTY ; VBOX, HBOX, EMPTY, CTRL
	$NODE['RECT'] = __UIBB_Rect() ; Neutral Rectangle Size
	$NODE['SCAL'] = 1 ; Scale
	$NODE['ESCA'] = 1 ; Effective Scale
	$NODE['MINP'] = 0 ; Minimum Size in Pixels
	$NODE['MAXP'] = 65536 ; Maximum Size in Pixels
	$NODE['BLOC'] = True ; Should this Node consume Space?
	$NODE['PADD'] = __UIBB_Padding() ; Neutral Padding
	$NODE['IPAD'] = __UIBB_Padding() ; Implicit Padding (e.g. GUICtrlCreateGroup or GUICtrlCreateTab)
	$NODE['EPAD'] = __UIBB_Padding() ; Effective Padding (Neutral + Implicit)
	$NODE['HAND'] = 0 ; Root gets $hGUI here (Ptr), CTRL gets CTRL-ID, Everything else gets 0
	$NODE['CHIL'] = 0 ; List of children for this node, This is a BuiltIn-Function for CTRL and a Map for Boxes
	Return $NODE
EndFunc   ;==>__UIBB_Node

Func __UIBB_InsertCtrl(ByRef $PARENT, $xFunc, $nScale)
	If $PARENT['TYPE'] = $UIBB_CTRL Then
		ConsoleWrite('! ERROR: Cannot Insert Ctrl into another Ctrl. Insert it into a Box instead!' & @CRLF)
		Return SetError(1, 0, 0)
	EndIf

	Local $CTRL = __UIBB_Node()
	$CTRL['TYPE'] = $UIBB_CTRL
	$CTRL['CHIL'] = $xFunc
	$CTRL['SCAL'] = $nScale
	$CTRL['ESCA'] = $nScale

	Local $bSuccess = False
	Switch $xFunc
		; ("text", left, top, [...]) Blocking
		Case GUICtrlCreateButton, GUICtrlCreateCheckbox, GUICtrlCreateCombo, GUICtrlCreateDate, GUICtrlCreateEdit, GUICtrlCreateInput, GUICtrlCreateLabel, GUICtrlCreateList, GUICtrlCreateListView, GUICtrlCreateRadio, GUICtrlCreateMonthCal
			$CTRL['HAND'] = $CTRL['CHIL']('', 0, 0)
			$bSuccess = True

			; (left, top, [...]) Blocking
		Case GUICtrlCreateGraphic, GUICtrlCreateProgress, GUICtrlCreateSlider, GUICtrlCreateTreeView
			$CTRL['HAND'] = $CTRL['CHIL'](0, 0)
			$bSuccess = True

			; ("text", left, top, [...]) Nonblocking + Implicit Padding
		Case GUICtrlCreateGroup
			$CTRL['HAND'] = $CTRL['CHIL']('', 0, 0)
			$CTRL['BLOC'] = False
			$PARENT['IPAD'] = __UIBB_Padding(8, 8, 16, 8)
			$bSuccess = True

			; (left, top, [...]) Nonblocking + Implicit Padding
		Case GUICtrlCreateTab
			$CTRL['HAND'] = $CTRL['CHIL'](0, 0)
			$CTRL['BLOC'] = False
			$PARENT['IPAD'] = __UIBB_Padding(10, 12, 30, 8)
			$bSuccess = True

			; ('', '', 0, 0) Blocking
		Case GUICtrlCreateIcon ; (hier muss eine Datei angegeben werden...)
			$CTRL['HAND'] = $CTRL['CHIL']('', '', 0, 0)
			$bSuccess = True

		Case GUICtrlCreateListViewItem
		Case GUICtrlCreateMenu ; Das kann nicht verwendet werden
		Case GUICtrlCreateMenuItem ; das auch nicht
		Case GUICtrlCreateObj ; TODO -> Wie objvar übergeben?
		Case GUICtrlCreatePic ; Braucht Dateiname
		Case GUICtrlCreateTabItem ; ? -> Muss mit ' ' (Leerzeichen) erzeugt werden, sonst ist das Tab nonexistent.
		Case GUICtrlCreateTreeViewItem ; ?
		Case GUICtrlCreateUpdown ; ?
		Case GUICtrlCreateAvi ; (hier muss eine Datei angegeben werden...)
		Case GUICtrlCreateDummy ; (das Ding hat keine Größe und kann demnach nicht automatisch skaliert werden)

	EndSwitch
	If $bSuccess Then
		GUICtrlSetResizing(-1, 802) ; DOCKALL Wir machen den Resize ja von Hand.
		MapAppend($PARENT['CHIL'], $CTRL)
		Return $CTRL['HAND']
	EndIf
	Return SetError(1, 0, 0) ; Da ist doch böses im Busch.
EndFunc   ;==>__UIBB_InsertCtrl

Func __UIBB_InsertBox(ByRef $PARENT, ByRef $CHILD, $nScale)
	If $CHILD['TYPE'] = $UIBB_CTRL Then
		ConsoleWrite('! ERROR: Cannot Insert Box into another Ctrl. Insert it into a Box instead!' & @CRLF)
		Return SetError(1, 0, 0)
	EndIf
	$CHILD['SCAL'] = $nScale
	$CHILD['ESCA'] = $nScale
	$CHILD['BLOC'] = $nScale <> -1 ? True : False ; Boxen selbst sind IMMER Blockend.
	MapAppend($PARENT['CHIL'], $CHILD)
EndFunc   ;==>__UIBB_InsertBox

Func __UIBB_UpdateClientArea(ByRef $CHILD, ByRef $PARENT, $idx = -1)
	If $CHILD['TYPE'] = $UIBB_CTRL Then
		__UIBB_UpdateCtrlClientArea($CHILD, $PARENT, $idx)
	Else
		__UIBB_UpdateBoxClientArea($CHILD, $PARENT, $idx)
	EndIf
	__UIBB_ComputeEffectiveScale($CHILD)
	For $i = 0 To UBound($CHILD['CHIL']) - 1 Step 1 ; Recursively update children
		__UIBB_UpdateClientArea($CHILD['CHIL'][$i], $CHILD, $i)
	Next
EndFunc   ;==>__UIBB_UpdateClientArea

Func __UIBB_UpdateCtrlClientArea(ByRef $CTRL, ByRef $PARENT, $idx)
	If $PARENT['TYPE'] = $UIBB_CTRL Then
		ConsoleWrite('! ERROR: Cannot update client area of Ctrl... Ctrl is not in a Box... how?' & @CRLF)
		Return SetError(1, 0, 0)
	EndIf
	__UIBB_UpdateChildClientArea($CTRL, $PARENT, $idx)
;~ 	ConsoleWrite('Update(' & $CTRL['RECT']['x'] & ', ' & $CTRL['RECT']['y'] & ', ' & $CTRL['RECT']['w'] & ', ' & $CTRL['RECT']['h'] & @CRLF)
	GUICtrlSetPos($CTRL['HAND'], $CTRL['RECT']['x'], $CTRL['RECT']['y'], $CTRL['RECT']['w'], $CTRL['RECT']['h'])
EndFunc   ;==>__UIBB_UpdateCtrlClientArea

Func __UIBB_ComputeEffectiveScale(ByRef $PARENT)
	Local $iChildren = UBound($PARENT['CHIL'])
	If Not $iChildren Then Return

	; Default Total Scale
	Local $nScale = 0, $nScaleTotal = 0
	For $i = 0 To $iChildren - 1 Step 1
		If $PARENT['CHIL'][$i]['BLOC'] Then
			$nScaleTotal += $PARENT['CHIL'][$i]['SCAL']
			$PARENT['CHIL'][$i]['ESCAL'] = $PARENT['CHIL'][$i]['SCAL']
		EndIf
	Next

	$PARENT['EPAD']['l'] = $PARENT['PADD']['l'] + $PARENT['IPAD']['l']
	$PARENT['EPAD']['r'] = $PARENT['PADD']['r'] + $PARENT['IPAD']['r']
	$PARENT['EPAD']['t'] = $PARENT['PADD']['t'] + $PARENT['IPAD']['t']
	$PARENT['EPAD']['b'] = $PARENT['PADD']['b'] + $PARENT['IPAD']['b']

	; Check if Scale does not fit MinMax requirements and fit accordingly
	Local $nScaleTotalNew = $nScaleTotal, $nParentWidth, $nParentHeight, $nMin, $nMax
	Local $nEffectiveWidth = $PARENT['RECT']['w'] - $PARENT['EPAD']['l'] - $PARENT['EPAD']['r']
	Local $nEffectiveHeight = $PARENT['RECT']['h'] - $PARENT['EPAD']['t'] - $PARENT['EPAD']['b']

	Switch $PARENT['TYPE']
		Case $UIBB_HBOX
			For $iter = 0 To $__UIBB_MAXITER - 1 Step 1 ; Fit Iterations
				$nParentWidth = $nEffectiveWidth / $nScaleTotalNew
				$nParentHeight = $nEffectiveHeight / $nScaleTotalNew
				$nScaleTotalNew = 0
				For $i = 0 To $iChildren - 1 Step 1
					If $PARENT['CHIL'][$i]['BLOC'] Then
						$nScale = $PARENT['CHIL'][$i]['ESCA']
						$nMin = $PARENT['CHIL'][$i]['MINP']
						$nMax = $PARENT['CHIL'][$i]['MAXP']
						If $nScale * $nParentWidth < $nMin Then $nScale = $nMin / $nParentWidth
						If $nScale * $nParentWidth > $nMax Then $nScale = $nMax / $nParentWidth
						$PARENT['CHIL'][$i]['ESCA'] = $nScale
						$nScaleTotalNew += $nScale
					EndIf
				Next
			Next
		Case $UIBB_VBOX
			For $iter = 0 To $__UIBB_MAXITER - 1 Step 1 ; Fit Iterations
				$nParentWidth = $PARENT['RECT']['w'] / $nScaleTotalNew
				$nParentHeight = $PARENT['RECT']['h'] / $nScaleTotalNew
				$nScaleTotalNew = 0
				For $i = 0 To $iChildren - 1 Step 1
					If $PARENT['CHIL'][$i]['BLOC'] Then
						$nScale = $PARENT['CHIL'][$i]['ESCA']
						$nMin = $PARENT['CHIL'][$i]['MINP']
						$nMax = $PARENT['CHIL'][$i]['MAXP']
						If $nScale * $nParentHeight < $nMin Then $nScale = $nMin / $nParentHeight
						If $nScale * $nParentHeight > $nMax Then $nScale = $nMax / $nParentHeight
						$PARENT['CHIL'][$i]['ESCA'] = $nScale
						$nScaleTotalNew += $nScale
					EndIf
				Next
			Next
	EndSwitch

EndFunc   ;==>__UIBB_ComputeEffectiveScale

Func __UIBB_UpdateChildClientArea(ByRef $CHILD, ByRef $PARENT, $idx)
	Local $iNumChildren = UBound($PARENT['CHIL'])
	Local $nScaleTotal = 0, $nScaleUntilMe = 0, $nScaleCurrent = 0
	For $i = 0 To UBound($PARENT['CHIL']) - 1 Step 1
		If $PARENT['CHIL'][$i]['BLOC'] Then
			$nScaleCurrent = $PARENT['CHIL'][$i]['ESCA']
			$nScaleTotal += $nScaleCurrent
			If $i < $idx Then $nScaleUntilMe += $nScaleCurrent
		EndIf
	Next
	$CHILD['RECT'] = __UIBB_Rect($PARENT['RECT'])

	If $CHILD['BLOC'] Or $CHILD['TYPE'] <> $UIBB_CTRL Then
		$CHILD['RECT']['x'] += $PARENT['EPAD']['l']
		$CHILD['RECT']['y'] += $PARENT['EPAD']['t']
		$CHILD['RECT']['w'] -= $PARENT['EPAD']['l'] + $PARENT['EPAD']['r']
		$CHILD['RECT']['h'] -= $PARENT['EPAD']['t'] + $PARENT['EPAD']['b']
	EndIf

	If $iNumChildren > 0 And $CHILD['BLOC'] Then
		Switch $PARENT['TYPE']
			Case $UIBB_HBOX
				$CHILD['RECT']['x'] += $nScaleUntilMe / $nScaleTotal * $CHILD['RECT']['w']
				$CHILD['RECT']['w'] *= $CHILD['ESCA'] / $nScaleTotal
			Case $UIBB_VBOX
				$CHILD['RECT']['y'] += $nScaleUntilMe / $nScaleTotal * $CHILD['RECT']['h']
				$CHILD['RECT']['h'] *= $CHILD['ESCA'] / $nScaleTotal
		EndSwitch
	EndIf
EndFunc   ;==>__UIBB_UpdateChildClientArea

Func __UIBB_UpdateBoxClientArea(ByRef $CHILD, ByRef $PARENT, $idx)
	If IsPtr($CHILD['HAND']) Then
		Local $tRECT = DllStructCreate('int x1; int y1; int x2; int y2')
		DllCall('user32.dll', 'bool', 'GetClientRect', 'hwnd', $CHILD['HAND'], 'struct*', $tRECT)
		$CHILD['RECT']['x'] = $tRECT.x1
		$CHILD['RECT']['y'] = $tRECT.y1
		$CHILD['RECT']['w'] = $tRECT.x2 - $tRECT.x1
		$CHILD['RECT']['h'] = $tRECT.y2 - $tRECT.y1
		If $CHILD['RECT']['w'] <= $CHILD['MINP'] Then $CHILD['RECT']['w'] = $CHILD['MINP'] ; MINP für RootBox = iMinWidth
		If $CHILD['RECT']['h'] <= $CHILD['MAXP'] Then $CHILD['RECT']['h'] = $CHILD['MAXP'] ; MAXP für RootBox = iMinHeight
		Return
	EndIf
	__UIBB_UpdateChildClientArea($CHILD, $PARENT, $idx)
EndFunc   ;==>__UIBB_UpdateBoxClientArea

Func __UIBB_Rect($x = 0, $y = 0, $w = 0, $h = 0)
	Local $RECT[]
	If IsMap($x) Then
		$RECT['x'] = $x['x']
		$RECT['y'] = $x['y']
		$RECT['w'] = $x['w']
		$RECT['h'] = $x['h']
	Else
		$RECT['x'] = $x
		$RECT['y'] = $y
		$RECT['w'] = $w
		$RECT['h'] = $h
	EndIf
	Return $RECT
EndFunc   ;==>__UIBB_Rect

Func __UIBB_Padding($left = 0, $right = 0, $top = 0, $bot = 0)
	Local $PADDING[]
	If IsMap($left) Then
		$PADDING['l'] = $left['l']
		$PADDING['r'] = $left['r']
		$PADDING['t'] = $left['t']
		$PADDING['b'] = $left['b']
	Else
		$PADDING['l'] = $left
		$PADDING['r'] = $right
		$PADDING['t'] = $top
		$PADDING['b'] = $bot
	EndIf
	Return $PADDING
EndFunc   ;==>__UIBB_Padding
; ===================================================================