<commit_before>package h2d;
import hxd.Math;

class Scene extends Layers implements h3d.IDrawable {

	public var width(default,null) : Int;
	public var height(default, null) : Int;
	
	public var mouseX(get, null) : Float;
	public var mouseY(get, null) : Float;
	
	var fixedSize : Bool;
	var interactive : Array<Interactive>;
	var pendingEvents : Array<hxd.Event>;
	var ctx : RenderContext;
	var stage : hxd.Stage;
	
	@:allow(h2d.Interactive)
	var currentOver : Interactive;
	@:allow(h2d.Interactive)
	var currentFocus : Interactive;
		
	var pushList : Array<Interactive>;
	var currentDrag : { f : hxd.Event -> Void, onCancel : Void -> Void, ref : Null<Int> };
	var eventListeners : Array< hxd.Event -> Void >;
	
	public function new() {
		super(null);
		var e = h3d.Engine.getCurrent();
		ctx = new RenderContext();
		width = e.width;
		height = e.height;
		interactive = new Array();
		pushList = new Array();
		eventListeners = new Array();
		stage = hxd.Stage.getInstance();
		posChanged = true;
	}
	
	public function setFixedSize( w, h ) {
		width = w;
		height = h;
		fixedSize = true;
		posChanged = true;
	}

	override function onAlloc() {
		stage.addEventTarget(onEvent);
		super.onAlloc();
	}
	
	override function onDelete() {
		stage.removeEventTarget(onEvent);
		super.onDelete();
	}
	
	function onEvent( e : hxd.Event ) {
		if( pendingEvents != null ) {
			e.relX = screenXToLocal(e.relX);
			e.relY = screenYToLocal(e.relY);
			pendingEvents.push(e);
		}
	}
	
	function screenXToLocal(mx:Float) {
		return (mx - x) * width / (stage.width * scaleX);
	}

	function screenYToLocal(my:Float) {
		return (my - y) * height / (stage.height * scaleY);
	}
	
	function get_mouseX() {
		return screenXToLocal(stage.mouseX);
	}

	function get_mouseY() {
		return screenYToLocal(stage.mouseY);
	}
	
	function dispatchListeners( event : hxd.Event ) {
		event.propagate = true;
		event.cancel = false;
		for( l in eventListeners ) {
			l(event);
			if( !event.propagate ) break;
		}
	}

	function emitEvent( event : hxd.Event ) {
		var x = event.relX, y = event.relY;
		var rx = x * matA + y * matB + absX;
		var ry = x * matC + y * matD + absY;
		var r = height / width;
		var handled = false;
		var checkOver = false, checkPush = false, cancelFocus = false;
		switch( event.kind ) {
		case EMove: checkOver = true;
		case EPush: cancelFocus = true; checkPush = true;
		case ERelease: checkPush = true;
		case EKeyUp, EKeyDown, EWheel:
			if( currentFocus != null )
				currentFocus.handleEvent(event);
			else {
				if( currentOver != null ) {
					event.propagate = true;
					currentOver.handleEvent(event);
					if( !event.propagate ) return;
				}
				dispatchListeners(event);
			}
			return;
		default:
		}
		for( i in interactive ) {
			

			// TODO : we are not sure that the positions are correctly updated !
			
			// this is a bit tricky since we are not in the not-euclide viewport space
			// (r = ratio correction)
			var dx = rx - i.absX;
			var dy = ry - i.absY;
			
			var w1 = i.width * i.matA * r;
			var h1 = i.width * i.matC;
			var ky = h1 * dx - w1 * dy;
			// up line
			if( ky < 0 )
				continue;
				
			var w2 = i.height * i.matB * r;
			var h2 = i.height * i.matD;
			var kx = w2 * dy - h2 * dx;
				
			// left line
			if( kx < 0 )
				continue;
			
			var max = h1 * w2 - w1 * h2;
			// bottom/right
			if( ky >= max || kx * r >= max )
				continue;

			// check visibility
			var visible = true;
			var p : Sprite = i;
			while( p != null ) {
				if( !p.visible ) {
					visible = false;
					break;
				}
				p = p.parent;
			}
			if( !visible ) continue;
			
			event.relX = (kx * r / max) * i.width;
			event.relY = (ky / max) * i.height;
			
			i.handleEvent(event);
			
			if( event.cancel )
				event.cancel = false;
			else if( checkOver ) {
				if( currentOver != i ) {
					var old = event.propagate;
					if( currentOver != null ) {
						event.kind = EOut;
						// relX/relY is not correct here
						currentOver.handleEvent(event);
					}
					event.kind = EOver;
					event.cancel = false;
					i.handleEvent(event);
					if( event.cancel )
						currentOver = null;
					else {
						currentOver = i;
						checkOver = false;
					}
					event.kind = EMove;
					event.cancel = false;
					event.propagate = old;
				} else
					checkOver = false;
			} else {
				if( checkPush ) {
					if( event.kind == EPush )
						pushList.push(i);
					else
						pushList.remove(i);
				}
				if( cancelFocus && i == currentFocus )
					cancelFocus = false;
			}
				
			if( event.propagate ) {
				event.propagate = false;
				continue;
			}
			
			handled = true;
			break;
		}
		if( cancelFocus && currentFocus != null ) {
			event.kind = EFocusLost;
			currentFocus.handleEvent(event);
			event.kind = EPush;
		}
		if( checkOver && currentOver != null ) {
			event.kind = EOut;
			currentOver.handleEvent(event);
			event.kind = EMove;
			currentOver = null;
		}
		if( !handled ) {
			if( event.kind == EPush )
				pushList.push(null);
			dispatchListeners(event);
		}
	}
	
	function hasEvents() {
		return interactive.length > 0 || eventListeners.length > 0;
	}
	
	public function checkEvents() {
		if( pendingEvents == null ) {
			if( !hasEvents() )
				return;
			pendingEvents = new Array();
		}
		var old = pendingEvents;
		if( old.length == 0 )
			return;
		pendingEvents = null;
		var ox = 0., oy = 0.;
		for( e in old ) {
			var hasPos = switch( e.kind ) {
			case EKeyUp, EKeyDown: false;
			default: true;
			}
			
			if( hasPos ) {
				ox = e.relX;
				oy = e.relY;
			}
			
			if( currentDrag != null && (currentDrag.ref == null || currentDrag.ref == e.touchId) ) {
				currentDrag.f(e);
				if( e.cancel )
					continue;
			}
			emitEvent(e);
			if( e.kind == ERelease && pushList.length > 0 ) {
				for( i in pushList ) {
					// relX/relY is not correct here
					if( i == null )
						dispatchListeners(e);
					else
						i.handleEvent(e);
				}
				pushList = new Array();
			}
		}
		if( hasEvents() )
			pendingEvents = new Array();
	}
	
	public function addEventListener( f : hxd.Event -> Void ) {
		eventListeners.push(f);
	}

	public function removeEventListener( f : hxd.Event -> Void ) {
		return eventListeners.remove(f);
	}
	
	public function startDrag( f : hxd.Event -> Void, ?onCancel : Void -> Void, ?refEvent : hxd.Event ) {
		if( currentDrag != null && currentDrag.onCancel != null )<commit_msg>fixed click should not propagate<commit_after>package h2d;
import hxd.Math;

class Scene extends Layers implements h3d.IDrawable {

	public var width(default,null) : Int;
	public var height(default, null) : Int;
	
	public var mouseX(get, null) : Float;
	public var mouseY(get, null) : Float;
	
	var fixedSize : Bool;
	var interactive : Array<Interactive>;
	var pendingEvents : Array<hxd.Event>;
	var ctx : RenderContext;
	var stage : hxd.Stage;
	
	@:allow(h2d.Interactive)
	var currentOver : Interactive;
	@:allow(h2d.Interactive)
	var currentFocus : Interactive;
		
	var pushList : Array<Interactive>;
	var currentDrag : { f : hxd.Event -> Void, onCancel : Void -> Void, ref : Null<Int> };
	var eventListeners : Array< hxd.Event -> Void >;
	
	public function new() {
		super(null);
		var e = h3d.Engine.getCurrent();
		ctx = new RenderContext();
		width = e.width;
		height = e.height;
		interactive = new Array();
		pushList = new Array();
		eventListeners = new Array();
		stage = hxd.Stage.getInstance();
		posChanged = true;
	}
	
	public function setFixedSize( w, h ) {
		width = w;
		height = h;
		fixedSize = true;
		posChanged = true;
	}

	override function onAlloc() {
		stage.addEventTarget(onEvent);
		super.onAlloc();
	}
	
	override function onDelete() {
		stage.removeEventTarget(onEvent);
		super.onDelete();
	}
	
	function onEvent( e : hxd.Event ) {
		if( pendingEvents != null ) {
			e.relX = screenXToLocal(e.relX);
			e.relY = screenYToLocal(e.relY);
			pendingEvents.push(e);
		}
	}
	
	function screenXToLocal(mx:Float) {
		return (mx - x) * width / (stage.width * scaleX);
	}

	function screenYToLocal(my:Float) {
		return (my - y) * height / (stage.height * scaleY);
	}
	
	function get_mouseX() {
		return screenXToLocal(stage.mouseX);
	}

	function get_mouseY() {
		return screenYToLocal(stage.mouseY);
	}
	
	function dispatchListeners( event : hxd.Event ) {
		event.propagate = true;
		event.cancel = false;
		for( l in eventListeners ) {
			l(event);
			if( !event.propagate ) break;
		}
	}

	function emitEvent( event : hxd.Event ) {
		var x = event.relX, y = event.relY;
		var rx = x * matA + y * matB + absX;
		var ry = x * matC + y * matD + absY;
		var r = height / width;
		var handled = false;
		var checkOver = false, checkPush = false, cancelFocus = false;
		switch( event.kind ) {
		case EMove: checkOver = true;
		case EPush: cancelFocus = true; checkPush = true;
		case ERelease: checkPush = true;
		case EKeyUp, EKeyDown, EWheel:
			if( currentFocus != null )
				currentFocus.handleEvent(event);
			else {
				if( currentOver != null ) {
					event.propagate = true;
					currentOver.handleEvent(event);
					if( !event.propagate ) return;
				}
				dispatchListeners(event);
			}
			return;
		default:
		}
		for( i in interactive ) {
			

			// TODO : we are not sure that the positions are correctly updated !
			
			// this is a bit tricky since we are not in the not-euclide viewport space
			// (r = ratio correction)
			var dx = rx - i.absX;
			var dy = ry - i.absY;
			
			var w1 = i.width * i.matA * r;
			var h1 = i.width * i.matC;
			var ky = h1 * dx - w1 * dy;
			// up line
			if( ky < 0 )
				continue;
				
			var w2 = i.height * i.matB * r;
			var h2 = i.height * i.matD;
			var kx = w2 * dy - h2 * dx;
				
			// left line
			if( kx < 0 )
				continue;
			
			var max = h1 * w2 - w1 * h2;
			// bottom/right
			if( ky >= max || kx * r >= max )
				continue;

			// check visibility
			var visible = true;
			var p : Sprite = i;
			while( p != null ) {
				if( !p.visible ) {
					visible = false;
					break;
				}
				p = p.parent;
			}
			if( !visible ) continue;
			
			event.relX = (kx * r / max) * i.width;
			event.relY = (ky / max) * i.height;
			
			i.handleEvent(event);
			
			if( event.cancel )
				event.cancel = false;
			else if( checkOver ) {
				if( currentOver != i ) {
					var old = event.propagate;
					if( currentOver != null ) {
						event.kind = EOut;
						// relX/relY is not correct here
						currentOver.handleEvent(event);
					}
					event.kind = EOver;
					event.cancel = false;
					i.handleEvent(event);
					if( event.cancel )
						currentOver = null;
					else {
						currentOver = i;
						checkOver = false;
					}
					event.kind = EMove;
					event.cancel = false;
					event.propagate = old;
				} else
					checkOver = false;
			} else {
				if( checkPush ) {
					if( event.kind == EPush )
						pushList.push(i);
					else
						pushList.remove(i);
				}
				if( cancelFocus && i == currentFocus )
					cancelFocus = false;
			}
				
			if( event.propagate ) {
				event.propagate = false;
				continue;
			}
			
			handled = true;
			break;
		}
		if( cancelFocus && currentFocus != null ) {
			event.kind = EFocusLost;
			currentFocus.handleEvent(event);
			event.kind = EPush;
		}
		if( checkOver && currentOver != null ) {
			event.kind = EOut;
			currentOver.handleEvent(event);
			event.kind = EMove;
			currentOver = null;
		}
		if( !handled ) {
			if( event.kind == EPush )
				pushList.push(null);
			dispatchListeners(event);
		}
	}
	
	function hasEvents() {
		return interactive.length > 0 || eventListeners.length > 0;
	}
	
	public function checkEvents() {
		if( pendingEvents == null ) {
			if( !hasEvents() )
				return;
			pendingEvents = new Array();
		}
		var old = pendingEvents;
		if( old.length == 0 )
			return;
		pendingEvents = null;
		var ox = 0., oy = 0.;
		for( e in old ) {
			var hasPos = switch( e.kind ) {
			case EKeyUp, EKeyDown: false;
			default: true;
			}
			
			if( hasPos ) {
				ox = e.relX;
				oy = e.relY;
			}
			
			if( currentDrag != null && (currentDrag.ref == null || currentDrag.ref == e.touchId) ) {
				currentDrag.f(e);
				if( e.cancel )
					continue;
			}
			emitEvent(e);
			/*
				We want to make sure that after we have pushed, we send a release even if the mouse
				has been outside of the Interactive (release outside). We will reset the mouse button
				to prevent click to be generated
			*/
			if( e.kind == ERelease && pushList.length > 0 ) {
				for( i in pushList ) {
					// relX/relY is not correct here
					if( i == null )
						dispatchListeners(e);
					else {
						@:privateAccess i.isMouseDown = -1;
						i.handleEvent(e);
					}
				}
				pushList = new Array();
			}
		}
		if( hasEvents() )
			pendingEvents = new Array();
	}
	
	public function addEventListener( f : hxd.Event -> Void ) {
		eventListeners.push(f);
	}

	public function removeEventListener( f : hxd.Event -> Void ) {
		return eventListeners.remove(f);
	}
	
	public function startDrag( f : hxd.Event -> Void, ?onCancel : Void -> Void, ?refEvent : hxd.Event ) {
		if( currentDrag != null && currentDrag.onCancel != null )
{'fraction_non_alphanumeric': 0.1300176529280835, 'fraction_numerical': 0.00391434492286438, 'mean_word_length': 2.4755934915977593, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 37, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1941606', 'n_tokens_mistral': 5627, 'n_tokens_neox': 4540, 'n_words': 1235}