Browse Source

Mask.maskWith API / h2d.Flow.overflow enum (#751)

Pavel Alexandrov 5 years ago
parent
commit
ee4b82c0cf
6 changed files with 170 additions and 49 deletions
  1. 42 6
      h2d/Flow.hx
  2. 43 33
      h2d/Mask.hx
  3. 47 5
      h2d/RenderContext.hx
  4. 2 2
      h2d/TextInput.hx
  5. 1 1
      samples/Flows.hx
  6. 35 2
      samples/Mask.hx

+ 42 - 6
h2d/Flow.hx

@@ -14,6 +14,25 @@ enum FlowLayout {
 	Stack;
 }
 
+enum FlowOverflow {
+	/**
+		Children larger than `maxWidth`/`maxHeight` will expand the flow size.
+	**/
+	Expand;
+	/**
+		Limits the bounds reported by the flow using `maxWidth` or `maxHeight`, if set.
+		This is means children larger than max size will draw outside of their parent bounds.
+	**/
+	Limit;
+	/**
+		Limits the bounds reported by the flow using `maxWidth` or `maxHeight`, if set.
+		Compared to `Limit` - Flow will mask out children that are outside of Flow bounds.
+	**/
+	Hidden;
+	// TODO: Scroll overflow, see #606
+	//Scroll;
+}
+
 @:allow(h2d.Flow)
 class FlowProperties {
 
@@ -99,7 +118,7 @@ class Flow extends Object {
 	/**
 		Enabling overflow will treat maxWidth/maxHeight and lineHeight/colWidth constraints as absolute : bigger elements will overflow instead of expanding the limit.
 	**/
-	public var overflow(default, set) : Bool = false;
+	public var overflow(default, set) : FlowOverflow = Expand;
 
 	/**
 		Will set all padding values at the same time.
@@ -500,6 +519,23 @@ class Flow extends Object {
 		super.sync(ctx);
 	}
 
+	override function drawRec(ctx:RenderContext)
+	{
+		if ( overflow == Hidden ) {
+			if ( posChanged ) {
+				calcAbsPos();
+				for ( c in children )
+					c.posChanged = true;
+				posChanged = false;
+			}
+			Mask.maskWith(ctx, this, hxd.Math.imax(outerWidth, maxWidth), hxd.Math.imax(outerHeight, maxHeight), 0, 0);
+			super.drawRec(ctx);
+			Mask.unmask(ctx);
+		} else {
+			super.drawRec(ctx);
+		}
+	}
+
 	function set_maxWidth(w) {
 		if( maxWidth == w )
 			return w;
@@ -682,7 +718,7 @@ class Flow extends Object {
 			inline function alignLine( maxIndex ) {
 				if( maxLineHeight < minLineHeight )
 					maxLineHeight = minLineHeight;
-				else if( overflow && minLineHeight != 0 )
+				else if( overflow != Expand && minLineHeight != 0 )
 					maxLineHeight = minLineHeight;
 				for( i in lastIndex...maxIndex ) {
 					var p = propAt(i);
@@ -827,7 +863,7 @@ class Flow extends Object {
 			inline function alignLine( maxIndex ) {
 				if( maxColWidth < minColWidth )
 					maxColWidth = minColWidth;
-				else if( overflow && minColWidth != 0 )
+				else if( overflow != Expand && minColWidth != 0 )
 					maxColWidth = minColWidth;
 				for( i in lastIndex...maxIndex ) {
 					var p = propAt(i);
@@ -992,9 +1028,9 @@ class Flow extends Object {
 
 			var xmin = paddingLeft + borderWidth;
 			var ymin = paddingTop + borderHeight;
-			var xmax = if(realMaxWidth > 0 && overflow) Math.floor(realMaxWidth - (paddingRight + borderWidth))
+			var xmax = if(realMaxWidth > 0 && overflow != Expand) Math.floor(realMaxWidth - (paddingRight + borderWidth))
 				else hxd.Math.imax(xmin + maxChildW, realMinWidth - (paddingRight + borderWidth));
-			var ymax = if(realMaxWidth > 0 && overflow) Math.floor(realMaxHeight - (paddingBottom + borderHeight))
+			var ymax = if(realMaxWidth > 0 && overflow != Expand) Math.floor(realMaxHeight - (paddingBottom + borderHeight))
 				else hxd.Math.imax(ymin + maxChildH, realMinHeight - (paddingBottom + borderHeight));
 			cw = xmax + paddingRight + borderWidth;
 			ch = ymax + paddingBottom + borderHeight;
@@ -1036,7 +1072,7 @@ class Flow extends Object {
 
 		if( realMinWidth >= 0 && cw < realMinWidth ) cw = realMinWidth;
 		if( realMinHeight >= 0 && ch < realMinHeight ) ch = realMinHeight;
-		if( overflow ) {
+		if( overflow != Expand ) {
 			if( isConstraintWidth && cw > maxTotWidth ) cw = maxTotWidth;
 			if( isConstraintHeight && ch > maxTotHeight ) ch = maxTotHeight;
 		}

+ 43 - 33
h2d/Mask.hx

@@ -1,7 +1,46 @@
 package h2d;
 
+
 class Mask extends Object {
 
+	/**
+		Masks render zone based off object position and given dimensions.
+		Should call `Mask.unmask()` afterwards.
+	**/
+	@:access(h2d.RenderContext)
+	public static function maskWith( ctx : RenderContext, object : Object, width : Int, height : Int, scrollX : Float = 0, scrollY : Float = 0) {
+
+		var x1 = object.absX + scrollX;
+		var y1 = object.absY + scrollY;
+
+		var x2 = width * object.matA + height * object.matC + x1;
+		var y2 = width * object.matB + height * object.matD + y1;
+
+		var tmp;
+		if (x1 > x2) {
+			tmp = x1;
+			x1 = x2;
+			x2 = tmp;
+		}
+
+		if (y1 > y2) {
+			tmp = y1;
+			y1 = y2;
+			y2 = tmp;
+		}
+
+		ctx.flush();
+		ctx.pushRenderZone(x1, y1, x2-x1, y2-y1);
+	}
+
+	/**
+		Unmasks prviously masked area from `Mask.maskWith`.
+	**/
+	public static function unmask( ctx : RenderContext ) {
+		ctx.flush();
+		ctx.popRenderZone();
+	}
+
 	public var width : Int;
 	public var height : Int;
 	var parentMask : Mask;
@@ -95,7 +134,7 @@ class Mask extends Object {
 				c.posChanged = true;
 			posChanged = false;
 		}
-		addBounds(relativeTo, out, 0, 0, width, height);
+		addBounds(relativeTo, out, scrollX, scrollY, width, height);
 		var bxMin = out.xMin, byMin = out.yMin, bxMax = out.xMax, byMax = out.yMax;
 		out.xMin = xMin;
 		out.xMax = xMax;
@@ -109,38 +148,9 @@ class Mask extends Object {
 	}
 
 	override function drawRec( ctx : h2d.RenderContext ) @:privateAccess {
-		var x1 = absX + scrollX;
-		var y1 = absY + scrollY;
-
-		var x2 = width * matA + height * matC + x1;
-		var y2 = width * matB + height * matD + y1;
-
-		var tmp;
-		if (x1 > x2) {
-			tmp = x1;
-			x1 = x2;
-			x2 = tmp;
-		}
-
-		if (y1 > y2) {
-			tmp = y1;
-			y1 = y2;
-			y2 = tmp;
-		}
-
-		ctx.flush();
-		if( ctx.hasRenderZone ) {
-			var oldX = ctx.renderX, oldY = ctx.renderY, oldW = ctx.renderW, oldH = ctx.renderH;
-			ctx.setRenderZone(x1, y1, x2-x1, y2-y1);
-			super.drawRec(ctx);
-			ctx.flush();
-			ctx.setRenderZone(oldX, oldY, oldW, oldH);
-		} else {
-			ctx.setRenderZone(x1, y1, x2-x1, y2-y1);
-			super.drawRec(ctx);
-			ctx.flush();
-			ctx.clearRenderZone();
-		}
+		maskWith(ctx, this, width, height, scrollX, scrollY);
+		super.drawRec(ctx);
+		unmask(ctx);
 	}
 
 }

+ 47 - 5
h2d/RenderContext.hx

@@ -1,5 +1,7 @@
 package h2d;
 
+private typedef RenderZoneStack = { hasRZ:Bool, x:Float, y:Float, w:Float, h:Float };
+
 class RenderContext extends h3d.impl.RenderContext {
 
 	static inline var BUFFERING = false;
@@ -30,6 +32,8 @@ class RenderContext extends h3d.impl.RenderContext {
 	var stride : Int;
 	var targetsStack : Array<{ t : h3d.mat.Texture, x : Int, y : Int, w : Int, h : Int, hasRZ : Bool, rzX:Float, rzY:Float, rzW:Float, rzH:Float }>;
 	var targetsStackIndex : Int;
+	var renderZoneStack:Array<RenderZoneStack> = [];
+	var renderZoneIndex:Int = 0;
 	var hasUVPos : Bool;
 	var filterStack : Array<h2d.Object>;
 	var inFilter : Object;
@@ -180,7 +184,7 @@ class RenderContext extends h3d.impl.RenderContext {
 		curWidth = width;
 		curHeight = height;
 		currentBlend = null;
-		if( hasRenderZone ) clearRenderZone();
+		if( hasRenderZone ) clearRZ();
 	}
 
 	public function pushTargets( texs : Array<h3d.mat.Texture> ) {
@@ -232,10 +236,38 @@ class RenderContext extends h3d.impl.RenderContext {
 			curHeight = height;
 		}
 
-		if( pinf.hasRZ ) setRenderZone(pinf.rzX, pinf.rzY, pinf.rzW, pinf.rzH);
+		if( pinf.hasRZ ) setRZ(pinf.rzX, pinf.rzY, pinf.rzW, pinf.rzH);
 	}
 
-	public function setRenderZone( x : Float, y : Float, w : Float, h : Float ) {
+	public function pushRenderZone( x : Float, y : Float, w : Float, h : Float ) {
+		var inf = renderZoneStack[renderZoneIndex++];
+		if ( inf == null ) {
+			inf = { hasRZ: hasRenderZone, x: renderX, y: renderY, w: renderW, h: renderH };
+			renderZoneStack[renderZoneIndex - 1] = inf;
+		} else if ( hasRenderZone ) {
+			inf.hasRZ = true;
+			inf.x = renderX;
+			inf.y = renderY;
+			inf.w = renderW;
+			inf.h = renderH;
+		} else {
+			inf.hasRZ = false;
+		}
+
+		setRZ(x, y, w, h);
+	}
+
+	public function popRenderZone() {
+		if (renderZoneIndex == 0) throw "Too many popRenderZone()";
+		var inf = renderZoneStack[--renderZoneIndex];
+		if (inf.hasRZ) {
+			setRZ(inf.x, inf.y, inf.w, inf.h);
+		} else {
+			clearRZ();
+		}
+	}
+
+	function setRZ( x : Float, y : Float, w : Float, h : Float ) {
 		hasRenderZone = true;
 		renderX = x;
 		renderY = y;
@@ -258,12 +290,22 @@ class RenderContext extends h3d.impl.RenderContext {
 		}
 		engine.setRenderZone(Std.int((x - curX + scene.viewportX) * scaleX + 1e-10), Std.int((y - curY + scene.viewportY) * scaleY + 1e-10), Std.int(w * scaleX + 1e-10), Std.int(h * scaleY + 1e-10));
 	}
-
-	public inline function clearRenderZone() {
+	
+	inline function clearRZ() {
 		hasRenderZone = false;
 		engine.setRenderZone();
 	}
 
+	@:deprecated("Use pushRenderZone")
+	public inline function setRenderZone( x : Float, y : Float, w : Float, h : Float ) {
+		pushRenderZone(x, y, w, h);
+	}
+
+	@:deprecated("Use popRenderZone")
+	public inline function clearRenderZone() {
+		popRenderZone();
+	}
+
 	function drawLayer( layer : Int ) {
 		@:privateAccess scene.drawLayer(this, layer);
 	}

+ 2 - 2
h2d/TextInput.hx

@@ -287,7 +287,7 @@ class TextInput extends Text {
 	override function draw(ctx:RenderContext) {
 		if( inputWidth != null ) {
 			var h = localToGlobal(new h2d.col.Point(inputWidth, font.lineHeight));
-			ctx.setRenderZone(absX, absY, h.x - absX, h.y - absY);
+			ctx.pushRenderZone(absX, absY, h.x - absX, h.y - absY);
 		}
 
 		if( cursorIndex >= 0 && (text != cursorText || cursorIndex != cursorXIndex) ) {
@@ -331,7 +331,7 @@ class TextInput extends Text {
 		}
 
 		if( inputWidth != null )
-			ctx.clearRenderZone();
+			ctx.popRenderZone();
 	}
 
 	public function focus() {

+ 1 - 1
samples/Flows.hx

@@ -436,7 +436,7 @@ class Flows extends hxd.App {
 			flow.maxWidth = 350;
 			flow.minHeight = flow.maxHeight = 350;
 			flow.layout = Stack;
-			flow.overflow = true;
+			flow.overflow = Limit;
 			currentFlows.push(flow);
 
 			var sub = new h2d.Flow(flow);

+ 35 - 2
samples/Mask.hx

@@ -1,13 +1,18 @@
+import h2d.RenderContext;
+import h2d.Object;
+
 class Mask extends hxd.App {
 
 	var obj : h2d.Object;
 	var mask : h2d.Mask;
 	var time : Float = 0.;
 
+	var apiMask : MaskWithSample;
+
 	override function init() {
 		mask = new h2d.Mask(160, 160, s2d);
-		mask.x = 200;
-		mask.y = 150;
+		mask.x = 20;
+		mask.y = 50;
 
 		// Mask-sized rectangle to display mask boundaries
 		// and make scroll movement more apparent.
@@ -27,11 +32,17 @@ class Mask extends hxd.App {
 		info.setPosition(5, 5);
 		info.text = "Arrows: move scrollX/Y\nSpace: reset scroll to 0,0";
 
+		// Content masking also possible with advanced `Mask.maskWith` and `Mask.unmask` methods.
+		apiMask = new MaskWithSample(s2d);
+		apiMask.setPosition(200, 50);
 	}
 
 	override function update(dt:Float) {
 		time += dt;
 		obj.rotation += 0.6 * dt;
+		apiMask.sx = Math.cos(time) * 100 + 100;
+		apiMask.sy = Math.sin(time) * 100 + 100;
+
 		if (hxd.Key.isDown(hxd.Key.LEFT)) mask.scrollX -= 100 * dt;
 		if (hxd.Key.isDown(hxd.Key.RIGHT)) mask.scrollX += 100 * dt;
 		if (hxd.Key.isDown(hxd.Key.UP)) mask.scrollY -= 100 * dt;
@@ -44,4 +55,26 @@ class Mask extends hxd.App {
 		new Mask();
 	}
 
+}
+
+class MaskWithSample extends Object {
+
+	public var sx:Float = 0;
+	public var sy:Float = 0;
+
+	public function new(?parent:Object) {
+		super(parent);
+		new h2d.Bitmap(hxd.Res.hxlogo.toTile(), this);
+	}
+
+	override function drawRec(ctx:RenderContext)
+	{
+		// Masking with advanced API allows to mask contents with objects that cannot use Mask as intermediary or extend from it.
+		// For practical usage sample see h2d.Flow Hidden overflow mode.
+		h2d.Mask.maskWith(ctx, this, 50, 50, sx, sy);
+		super.drawRec(ctx);
+		// Unmask should be called after `maskWith` in order to restore previous renderZone state.
+		h2d.Mask.unmask(ctx);
+	}
+
 }