400 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
		
		
			
		
	
	
			400 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			Haxe
		
	
	
	
	
	
|  | package lnx2d; | ||
|  | 
 | ||
|  | // Kha | ||
|  | import kha.math.Vector2; | ||
|  | import kha.input.KeyCode; | ||
|  | import kha.graphics2.Graphics; | ||
|  | using kha.graphics2.GraphicsExtension; | ||
|  | 
 | ||
|  | // Zui | ||
|  | import zui.Zui; | ||
|  | import leenkx.ui.Canvas; | ||
|  | 
 | ||
|  | // Editor | ||
|  | import lnx2d.tools.Math; | ||
|  | import lnx2d.ui.UIProperties; | ||
|  | import lnx2d.tools.CanvasTools; | ||
|  | 
 | ||
|  | class ElementController { | ||
|  | 
 | ||
|  | 	static var ui:Zui; | ||
|  | 	static var cui:Zui; | ||
|  | 
 | ||
|  | 	public static var isManipulating = false; | ||
|  | 	static var transformInitInput:Vector2; | ||
|  | 	static var transformInitPos:Vector2; | ||
|  | 	static var transformInitRot:Float; | ||
|  | 	static var transformInitSize:Vector2; | ||
|  | 	// Was the transformation editing started by dragging the mouse | ||
|  | 	static var transformStartedMouse = false; | ||
|  | 	static var drag = false; | ||
|  | 	static var dragLeft = false; | ||
|  | 	static var dragTop = false; | ||
|  | 	static var dragRight = false; | ||
|  | 	static var dragBottom = false; | ||
|  | 	static var grab = false; | ||
|  | 	static var grabX = false; | ||
|  | 	static var grabY = false; | ||
|  | 	static var rotate = false; | ||
|  | 	static var newElementSelected = false; | ||
|  | 
 | ||
|  | 	public static var handleSize(get, null):Int; | ||
|  | 	static inline function get_handleSize():Int { return Std.int(8 * ui.SCALE()); } | ||
|  | 
 | ||
|  | 	public static function initialize(ui: Zui, cui: Zui) { | ||
|  | 		ElementController.ui = ui; | ||
|  | 		ElementController.cui = cui; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function selectElement(canvas:TCanvas) { | ||
|  | 		if (ui == null) return; | ||
|  | 
 | ||
|  | 		var selectButton = Main.prefs.keyMap.selectMouseButton; | ||
|  | 		if (selectButton == "Left" && ui.inputStarted && ui.inputDown || | ||
|  | 				selectButton == "Right" && ui.inputStartedR && ui.inputDownR) { | ||
|  | 
 | ||
|  | 			// Deselect | ||
|  | 			var lastSelected = Editor.selectedElem; | ||
|  | 			Editor.selectedElem = null; | ||
|  | 
 | ||
|  | 			newElementSelected = false; | ||
|  | 
 | ||
|  | 			// Elements are sorted by z position (descending), so the topmost element will get | ||
|  | 			// selected if multiple elements overlap each other at the mouse position | ||
|  | 			var sorted_elements = canvas.elements.copy(); | ||
|  | 			sorted_elements.reverse(); | ||
|  | 			for (elem in sorted_elements) { | ||
|  | 				var anchorOffset = Canvas.getAnchorOffset(canvas, elem); | ||
|  | 				var ex = scaled(Math.absx(canvas, elem)) + anchorOffset[0]; | ||
|  | 				var ey = scaled(Math.absy(canvas, elem)) + anchorOffset[1]; | ||
|  | 				var ew = scaled(elem.width); | ||
|  | 				var eh = scaled(elem.height); | ||
|  | 				// Element center | ||
|  | 				var cx = canvas.x + ex + ew / 2; | ||
|  | 				var cy = canvas.y + ey + eh / 2; | ||
|  | 
 | ||
|  | 				var rotHandleX = cx - handleSize / 2; | ||
|  | 				var rotHandleY = canvas.y + ey - handleSize * 2 - handleSize / 2; | ||
|  | 				var rotHandleH = handleSize * 2 + handleSize / 2; | ||
|  | 
 | ||
|  | 				if (Math.hitbox(cui, canvas.x + ex - handleSize / 2, canvas.y + ey - handleSize / 2, ew + handleSize, eh + handleSize, elem.rotation) | ||
|  | 						|| (Math.hitbox(cui, rotHandleX, rotHandleY, handleSize, rotHandleH, elem.rotation, [cx, cy]) // Rotation handle hitbox | ||
|  | 							&& lastSelected == elem)) { // Don't select elements other than the currently selected by their rotation handle | ||
|  | 					Editor.selectedElem = elem; | ||
|  | 
 | ||
|  | 					if (lastSelected != elem) | ||
|  | 						newElementSelected = true; | ||
|  | 					break; | ||
|  | 				} | ||
|  | 			} | ||
|  | 			// force properties redraw to show selection | ||
|  | 			UIProperties.hwin.redraws = 2; | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function render(g:Graphics, canvas:TCanvas) { | ||
|  | 
 | ||
|  | 		// Outline selected elem | ||
|  | 		if (Editor.selectedElem != null) { | ||
|  | 			var anchorOffset = Canvas.getAnchorOffset(canvas, Editor.selectedElem); | ||
|  | 
 | ||
|  | 			// Resize rects | ||
|  | 			var ex = scaled(Math.absx(canvas, Editor.selectedElem)) + anchorOffset[0]; | ||
|  | 			var ey = scaled(Math.absy(canvas, Editor.selectedElem)) + anchorOffset[1]; | ||
|  | 			var ew = scaled(Editor.selectedElem.width); | ||
|  | 			var eh = scaled(Editor.selectedElem.height); | ||
|  | 			// Element center | ||
|  | 			var cx = canvas.x + ex + ew / 2; | ||
|  | 			var cy = canvas.y + ey + eh / 2; | ||
|  | 			g.pushRotation(Editor.selectedElem.rotation, cx, cy); | ||
|  | 
 | ||
|  | 			// Draw element outline | ||
|  | 			g.color = 0xffffffff; | ||
|  | 			g.drawRect(canvas.x + ex, canvas.y + ey, ew, eh); | ||
|  | 			g.color = 0xff000000; | ||
|  | 			g.drawRect(canvas.x + ex + 1, canvas.y + ey + 1, ew, eh); | ||
|  | 			g.color = 0xffffffff; | ||
|  | 
 | ||
|  | 			// Rotate mouse coords in opposite direction as the element | ||
|  | 			var rotatedInput:Vector2 = Math.rotatePoint(ui.inputX, ui.inputY, cx, cy, -Editor.selectedElem.rotation); | ||
|  | 
 | ||
|  | 			// Draw corner drag handles | ||
|  | 			for (handlePosX in 0...3) { | ||
|  | 				// 0 = Left, 0.5 = Center, 1 = Right | ||
|  | 				var handlePosX:Float = handlePosX / 2; | ||
|  | 
 | ||
|  | 				for (handlePosY in 0...3) { | ||
|  | 					// 0 = Top, 0.5 = Center, 1 = Bottom | ||
|  | 					var handlePosY:Float = handlePosY / 2; | ||
|  | 
 | ||
|  | 					if (handlePosX == 0.5 && handlePosY == 0.5) { | ||
|  | 						continue; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					var hX = canvas.x + ex + ew * handlePosX - handleSize / 2; | ||
|  | 					var hY = canvas.y + ey + eh * handlePosY - handleSize / 2; | ||
|  | 
 | ||
|  | 					// Check if the handle is currently dragged (not necessarily hovered!) | ||
|  | 					var dragged = false; | ||
|  | 
 | ||
|  | 					if (handlePosX == 0 && dragLeft) { | ||
|  | 						if (handlePosY == 0 && dragTop) dragged = true; | ||
|  | 						else if (handlePosY == 0.5 && !(dragTop || dragBottom)) dragged = true; | ||
|  | 						else if (handlePosY == 1 && dragBottom) dragged = true; | ||
|  | 					} else if (handlePosX == 0.5 && !(dragLeft || dragRight)) { | ||
|  | 						if (handlePosY == 0 && dragTop) dragged = true; | ||
|  | 						else if (handlePosY == 1 && dragBottom) dragged = true; | ||
|  | 					} else if (handlePosX == 1 && dragRight) { | ||
|  | 						if (handlePosY == 0 && dragTop) dragged = true; | ||
|  | 						else if (handlePosY == 0.5 && !(dragTop || dragBottom)) dragged = true; | ||
|  | 						else if (handlePosY == 1 && dragBottom) dragged = true; | ||
|  | 					} | ||
|  | 					dragged = dragged && drag; | ||
|  | 
 | ||
|  | 
 | ||
|  | 					// Hover | ||
|  | 					if (rotatedInput.x > hX && rotatedInput.x < hX + handleSize || dragged) { | ||
|  | 						if (rotatedInput.y > hY && rotatedInput.y < hY + handleSize || dragged) { | ||
|  | 							g.color = 0xff205d9c; | ||
|  | 							g.fillRect(hX, hY, handleSize, handleSize); | ||
|  | 							g.color = 0xffffffff; | ||
|  | 						} | ||
|  | 					} | ||
|  | 
 | ||
|  | 					g.drawRect(hX, hY, handleSize, handleSize); | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			// Draw rotation handle | ||
|  | 			g.drawLine(cx, canvas.y + ey, cx, canvas.y + ey - handleSize * 2); | ||
|  | 
 | ||
|  | 			var rotHandleCenter = new Vector2(cx, canvas.y + ey - handleSize * 2); | ||
|  | 			if (rotatedInput.sub(rotHandleCenter).length <= handleSize / 2 || rotate) { | ||
|  | 				g.color = 0xff205d9c; | ||
|  | 				g.fillCircle(rotHandleCenter.x, rotHandleCenter.y, handleSize / 2); | ||
|  | 				g.color = 0xffffffff; | ||
|  | 			} | ||
|  | 			g.drawCircle(rotHandleCenter.x, rotHandleCenter.y, handleSize / 2); | ||
|  | 
 | ||
|  | 			g.popTransformation(); | ||
|  | 		} | ||
|  | 
 | ||
|  | 	} | ||
|  | 
 | ||
|  | 	public static function update(ui:Zui, cui:Zui, canvas:TCanvas) { | ||
|  | 		lnx2d.ElementController.ui = ui; | ||
|  | 		lnx2d.ElementController.cui = cui; | ||
|  | 
 | ||
|  | 		if (newElementSelected) | ||
|  | 			return; | ||
|  | 
 | ||
|  | 		if (Editor.selectedElem != null) { | ||
|  | 			var elem = Editor.selectedElem; | ||
|  | 			var anchorOffset = Canvas.getAnchorOffset(canvas, elem); | ||
|  | 
 | ||
|  | 			var ex = scaled(Math.absx(canvas, elem)) + anchorOffset[0]; | ||
|  | 			var ey = scaled(Math.absy(canvas, elem)) + anchorOffset[1]; | ||
|  | 			var ew = scaled(elem.width); | ||
|  | 			var eh = scaled(elem.height); | ||
|  | 			var rotatedInput:Vector2 = Math.rotatePoint(ui.inputX, ui.inputY, canvas.x + ex + ew / 2, canvas.y + ey + eh / 2, -elem.rotation); | ||
|  | 
 | ||
|  | 			if (ui.inputStarted && ui.inputDown) { | ||
|  | 				// Drag selected element | ||
|  | 				if (Math.hitbox(ui, canvas.x + ex - handleSize / 2, canvas.y + ey - handleSize / 2, ew + handleSize, eh + handleSize, Editor.selectedElem.rotation)) { | ||
|  | 					drag = true; | ||
|  | 					// Resize | ||
|  | 					dragLeft = dragRight = dragTop = dragBottom = false; | ||
|  | 					if (rotatedInput.x > canvas.x + ex + ew - handleSize) dragRight = true; | ||
|  | 					else if (rotatedInput.x < canvas.x + ex + handleSize) dragLeft = true; | ||
|  | 					if (rotatedInput.y > canvas.y + ey + eh - handleSize) dragBottom = true; | ||
|  | 					else if (rotatedInput.y < canvas.y + ey + handleSize) dragTop = true; | ||
|  | 
 | ||
|  | 					startElementManipulation(true); | ||
|  | 
 | ||
|  | 				} else { | ||
|  | 					var rotHandleCenter = new Vector2(canvas.x + ex + ew / 2, canvas.y + ey - handleSize * 2); | ||
|  | 					var inputPos = rotatedInput.sub(rotHandleCenter); | ||
|  | 
 | ||
|  | 					// Rotate selected element | ||
|  | 					if (inputPos.length <= handleSize) { | ||
|  | 						rotate = true; | ||
|  | 						startElementManipulation(true); | ||
|  | 					} | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if (isManipulating) { | ||
|  | 				UIProperties.hwin.redraws = 2; | ||
|  | 
 | ||
|  | 				// Confirm | ||
|  | 				if ((transformStartedMouse && ui.inputReleased) || (!transformStartedMouse && ui.inputStarted)) { | ||
|  | 					endElementManipulation(); | ||
|  | 
 | ||
|  | 				// Reset | ||
|  | 				} else if ((ui.isKeyPressed && ui.isEscapeDown) || ui.inputStartedR) { | ||
|  | 					endElementManipulation(true); | ||
|  | 
 | ||
|  | 				} else if (drag) { | ||
|  | 					var transformDelta = new Vector2(ui.inputX, ui.inputY).sub(transformInitInput); | ||
|  | 
 | ||
|  | 					if (!transformStartedMouse) { | ||
|  | 						if (ui.isKeyPressed && ui.key == KeyCode.X) { | ||
|  | 							elem.width = Std.int(transformInitSize.x); | ||
|  | 							elem.height = Std.int(transformInitSize.y); | ||
|  | 							dragRight = true; | ||
|  | 							dragBottom = !dragBottom; | ||
|  | 						} | ||
|  | 						if (ui.isKeyPressed && ui.key == KeyCode.Y) { | ||
|  | 							elem.width = Std.int(transformInitSize.x); | ||
|  | 							elem.height = Std.int(transformInitSize.y); | ||
|  | 							dragBottom = true; | ||
|  | 							dragRight = !dragRight; | ||
|  | 						} | ||
|  | 					} | ||
|  | 
 | ||
|  | 					if (dragRight) { | ||
|  | 						transformDelta.x = Math.calculateTransformDelta(ui, Editor.gridSnapPos, Editor.gridUseRelative, Editor.gridSize, transformDelta.x, transformInitPos.x + transformInitSize.x); | ||
|  | 						elem.width = Std.int(transformInitSize.x + transformDelta.x); | ||
|  | 					} else if (dragLeft) { | ||
|  | 						transformDelta.x = Math.calculateTransformDelta(ui, Editor.gridSnapPos, Editor.gridUseRelative, Editor.gridSize, transformDelta.x, transformInitPos.x); | ||
|  | 						elem.x = transformInitPos.x + transformDelta.x; | ||
|  | 						elem.width = Std.int(transformInitSize.x - transformDelta.x); | ||
|  | 					} | ||
|  | 					if (dragBottom) { | ||
|  | 						transformDelta.y = Math.calculateTransformDelta(ui, Editor.gridSnapPos, Editor.gridUseRelative, Editor.gridSize, transformDelta.y, transformInitPos.y + transformInitSize.y); | ||
|  | 						elem.height = Std.int(transformInitSize.y + transformDelta.y); | ||
|  | 					} | ||
|  | 					else if (dragTop) { | ||
|  | 						transformDelta.y = Math.calculateTransformDelta(ui, Editor.gridSnapPos, Editor.gridUseRelative, Editor.gridSize, transformDelta.y, transformInitPos.y); | ||
|  | 						elem.y = transformInitPos.y + transformDelta.y; | ||
|  | 						elem.height = Std.int(transformInitSize.y - transformDelta.y); | ||
|  | 					} | ||
|  | 
 | ||
|  | 					if (elem.type != ElementType.Image) { | ||
|  | 						if (elem.width < 1) elem.width = 1; | ||
|  | 						if (elem.height < 1) elem.height = 1; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					if (!dragLeft && !dragRight && !dragBottom && !dragTop) { | ||
|  | 						grab = true; | ||
|  | 						grabX = true; | ||
|  | 						grabY = true; | ||
|  | 						drag = false; | ||
|  | 					} else { | ||
|  | 						// Ensure there the delta is 0 on unused axes | ||
|  | 						if (!dragBottom && !dragTop) transformDelta.y = 0; | ||
|  | 						else if (!dragLeft && !dragRight) transformDelta.y = 0; | ||
|  | 
 | ||
|  | 						Editor.currentOperation = ' x: ${elem.x}  y: ${elem.y}  w: ${elem.width}  h: ${elem.height}  (dx: ${transformDelta.x}  dy: ${transformDelta.y})'; | ||
|  | 					} | ||
|  | 
 | ||
|  | 				} else if (grab) { | ||
|  | 					var transformDelta = new Vector2(ui.inputX, ui.inputY).sub(transformInitInput); | ||
|  | 
 | ||
|  | 					if (ui.isKeyPressed && ui.key == KeyCode.X) { | ||
|  | 						elem.x = transformInitPos.x; | ||
|  | 						elem.y = transformInitPos.y; | ||
|  | 						grabX = true; | ||
|  | 						grabY = !grabY; | ||
|  | 					} | ||
|  | 					if (ui.isKeyPressed && ui.key == KeyCode.Y) { | ||
|  | 						elem.x = transformInitPos.x; | ||
|  | 						elem.y = transformInitPos.y; | ||
|  | 						grabY = true; | ||
|  | 						grabX = !grabX; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					if (grabX) { | ||
|  | 						transformDelta.x = Math.calculateTransformDelta(ui, Editor.gridSnapPos, Editor.gridUseRelative, Editor.gridSize, transformDelta.x, transformInitPos.x); | ||
|  | 						elem.x = Std.int(transformInitPos.x + transformDelta.x); | ||
|  | 					} | ||
|  | 					if (grabY) { | ||
|  | 						transformDelta.y = Math.calculateTransformDelta(ui, Editor.gridSnapPos, Editor.gridUseRelative, Editor.gridSize, transformDelta.y, transformInitPos.y); | ||
|  | 						elem.y = Std.int(transformInitPos.y + transformDelta.y); | ||
|  | 					} | ||
|  | 
 | ||
|  | 					// Ensure there the delta is 0 on unused axes | ||
|  | 					if (!grabX) transformDelta.x = 0; | ||
|  | 					else if (!grabY) transformDelta.y = 0; | ||
|  | 
 | ||
|  | 					Editor.currentOperation = ' x: ${elem.x}  y: ${elem.y}  (dx: ${transformDelta.x}  dy: ${transformDelta.y})'; | ||
|  | 
 | ||
|  | 				} else if (rotate) { | ||
|  | 					var elemCenter = new Vector2(canvas.x + ex + ew / 2, canvas.y + ey + eh / 2); | ||
|  | 					var inputPos = new Vector2(ui.inputX, ui.inputY).sub(elemCenter); | ||
|  | 
 | ||
|  | 					// inputPos.x and inputPos.y are both positive when the mouse is in the lower right | ||
|  | 					// corner of the elements center, so the positive x axis used for the angle calculation | ||
|  | 					// in atan2() is equal to the global negative y axis. That's why we have to invert the | ||
|  | 					// angle and add Pi to get the correct rotation. atan2() also returns an angle in the | ||
|  | 					// intervall (-PI, PI], so we don't have to calculate the angle % PI*2 anymore. | ||
|  | 					var inputAngle = -std.Math.atan2(inputPos.x, inputPos.y) + std.Math.PI; | ||
|  | 
 | ||
|  | 					// Ctrl toggles rotation step mode | ||
|  | 					if ((ui.isKeyDown && ui.key == Main.prefs.keyMap.gridInvert) != Editor.useRotationSteps) { | ||
|  | 						inputAngle = std.Math.round(inputAngle / Editor.rotationSteps) * Editor.rotationSteps; | ||
|  | 					} | ||
|  | 
 | ||
|  | 					elem.rotation = inputAngle; | ||
|  | 					Editor.currentOperation = " Rot: " + Math.roundPrecision(Math.toDegrees(inputAngle), 2) + "deg"; | ||
|  | 				} | ||
|  | 			} | ||
|  | 
 | ||
|  | 			if (ui.isKeyPressed && !ui.isTyping) { | ||
|  | 				if (!grab && ui.key == Main.prefs.keyMap.grabKey){startElementManipulation(); grab = true; grabX = true; grabY = true;} | ||
|  | 				if (!drag && ui.key == Main.prefs.keyMap.sizeKey) {startElementManipulation(); drag = true; dragLeft = false; dragTop = false; dragRight = true; dragBottom = true;} | ||
|  | 				if (!rotate && ui.key == Main.prefs.keyMap.rotateKey) {startElementManipulation(); rotate = true;} | ||
|  | 
 | ||
|  | 				if (!isManipulating) { | ||
|  | 					// Move with arrows | ||
|  | 					if (ui.key == KeyCode.Left) Editor.gridSnapPos ? elem.x -= Editor.gridSize : elem.x--; | ||
|  | 					if (ui.key == KeyCode.Right) Editor.gridSnapPos ? elem.x += Editor.gridSize : elem.x++; | ||
|  | 					if (ui.key == KeyCode.Up) Editor.gridSnapPos ? elem.y -= Editor.gridSize : elem.y--; | ||
|  | 					if (ui.key == KeyCode.Down) Editor.gridSnapPos ? elem.y += Editor.gridSize : elem.y++; | ||
|  | 
 | ||
|  | 					if (ui.isBackspaceDown || ui.isDeleteDown){ | ||
|  | 						CanvasTools.removeElem(canvas, Editor.selectedElem); | ||
|  | 						Editor.selectedElem = null; | ||
|  | 					} | ||
|  | 					else if (ui.key == KeyCode.D) Editor.selectedElem = CanvasTools.duplicateElem(canvas, elem); | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} else { | ||
|  | 			endElementManipulation(); | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	static function startElementManipulation(?mousePressed=false) { | ||
|  | 		if (isManipulating) endElementManipulation(true); | ||
|  | 
 | ||
|  | 		transformInitInput = new Vector2(ui.inputX, ui.inputY); | ||
|  | 		transformInitPos = new Vector2(Editor.selectedElem.x, Editor.selectedElem.y); | ||
|  | 		transformInitSize = new Vector2(Editor.selectedElem.width, Editor.selectedElem.height); | ||
|  | 		transformInitRot = Editor.selectedElem.rotation; | ||
|  | 		transformStartedMouse = mousePressed; | ||
|  | 
 | ||
|  | 		isManipulating = true; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	static function endElementManipulation(reset=false) { | ||
|  | 		if (reset) { | ||
|  | 			Editor.selectedElem.x = transformInitPos.x; | ||
|  | 			Editor.selectedElem.y = transformInitPos.y; | ||
|  | 			Editor.selectedElem.width = Std.int(transformInitSize.x); | ||
|  | 			Editor.selectedElem.height = Std.int(transformInitSize.y); | ||
|  | 			Editor.selectedElem.rotation = transformInitRot; | ||
|  | 		} | ||
|  | 
 | ||
|  | 		isManipulating = false; | ||
|  | 
 | ||
|  | 		grab = false; | ||
|  | 		drag = false; | ||
|  | 		rotate = false; | ||
|  | 
 | ||
|  | 		transformStartedMouse = false; | ||
|  | 		Editor.currentOperation = ""; | ||
|  | 	} | ||
|  | 
 | ||
|  | 	static inline function scaled(f: Float): Int { return Std.int(f * cui.SCALE()); } | ||
|  | } |