diff --git a/flixel/FlxSprite.hx b/flixel/FlxSprite.hx index a0daab5ef8..e11d8935de 100644 --- a/flixel/FlxSprite.hx +++ b/flixel/FlxSprite.hx @@ -803,26 +803,26 @@ class FlxSprite extends FlxObject */ override public function draw():Void { + checkClipRect(); + checkEmptyFrame(); - + if (alpha == 0 || _frame.type == FlxFrameType.EMPTY) return; - + if (dirty) // rarely calcFrame(useFramePixels); - checkClipRect(); - for (camera in getCamerasLegacy()) { if (!camera.visible || !camera.exists || !isOnScreen(camera)) continue; - + if (isSimpleRender(camera)) drawSimple(camera); else drawComplex(camera); - + #if FLX_DEBUG FlxBasic.visibleCount++; #end @@ -839,7 +839,8 @@ class FlxSprite extends FlxObject */ function checkClipRect() { - if ((clipRect == null && Math.isNaN(_lastClipRect.x)) + if (frames == null + || (clipRect == null && Math.isNaN(_lastClipRect.x)) || (clipRect != null && clipRect.equals(_lastClipRect))) return; diff --git a/flixel/graphics/frames/FlxFrame.hx b/flixel/graphics/frames/FlxFrame.hx index 63008918de..ec4bd00934 100644 --- a/flixel/graphics/frames/FlxFrame.hx +++ b/flixel/graphics/frames/FlxFrame.hx @@ -143,9 +143,7 @@ class FlxFrame implements IFlxDestroyable var blitMatrix:Vector; - @:allow(flixel.graphics.FlxGraphic) - @:allow(flixel.graphics.frames.FlxFramesCollection) - function new(parent:FlxGraphic, angle = FlxFrameAngle.ANGLE_0, flipX = false, flipY = false, duration = 0.0) + public function new(parent:FlxGraphic, angle = FlxFrameAngle.ANGLE_0, flipX = false, flipY = false, duration = 0.0) { this.parent = parent; this.angle = angle; @@ -584,7 +582,52 @@ class FlxFrame implements IFlxDestroyable copyTo(clippedFrame); return clippedFrame.clip(rect); } - + + /** + * Whether there is any overlap between this frame and the given rect. If clipping this frame to + * the given rect would result in an empty frame, the result is `false` + */ + public function overlaps(rect:FlxRect) + { + rect.x += frame.x - offset.x; + rect.y += frame.y - offset.y; + final result = rect.overlaps(frame); + rect.x -= frame.x - offset.x; + rect.y -= frame.y - offset.y; + return result; + } + + + /** + * Whether this frame fully contains the given rect. If clipping this frame to + * the given rect would result in a smaller frame, the result is `false` + * @since 6.1.0 + */ + public function contains(rect:FlxRect) + { + rect.x += frame.x - offset.x; + rect.y += frame.y - offset.y; + final result = frame.contains(rect); + rect.x -= frame.x - offset.x; + rect.y -= frame.y - offset.y; + return result; + } + + /** + * Whether this frame is fully contained by the given rect. If clipping this frame to + * the given rect would result in a smaller frame, the result is `false` + * @since 6.1.0 + */ + public function isContained(rect:FlxRect) + { + rect.x += frame.x - offset.x; + rect.y += frame.y - offset.y; + final result = rect.contains(frame); + rect.x -= frame.x - offset.x; + rect.y -= frame.y - offset.y; + return result; + } + /** * Clips this frame to the desired rect * diff --git a/flixel/math/FlxRect.hx b/flixel/math/FlxRect.hx index cb7715db0e..8c973a377f 100644 --- a/flixel/math/FlxRect.hx +++ b/flixel/math/FlxRect.hx @@ -285,6 +285,23 @@ class FlxRect implements IFlxPooled return result; } + /** + * Checks to see if this rectangle fully contains another + * + * @param rect The other rectangle + * @return Whether this rectangle contains the given rectangle + * @since 6.1.0 + */ + public inline function contains(rect:FlxRect):Bool + { + final result = rect.left >= left + && rect.right <= right + && rect.top >= top + && rect.bottom <= bottom; + rect.putWeak(); + return result; + } + /** * Returns true if this FlxRect contains the FlxPoint * diff --git a/flixel/text/FlxBitmapText.hx b/flixel/text/FlxBitmapText.hx index 4d0ec1551b..c334a9fd23 100644 --- a/flixel/text/FlxBitmapText.hx +++ b/flixel/text/FlxBitmapText.hx @@ -1,17 +1,19 @@ package flixel.text; -import openfl.display.BitmapData; import flixel.FlxBasic; import flixel.FlxG; import flixel.FlxSprite; import flixel.graphics.frames.FlxBitmapFont; import flixel.graphics.frames.FlxFrame; +import flixel.graphics.tile.FlxDrawBaseItem; +import flixel.math.FlxMatrix; import flixel.math.FlxPoint; import flixel.math.FlxRect; import flixel.text.FlxText.FlxTextAlign; import flixel.text.FlxText.FlxTextBorderStyle; import flixel.util.FlxColor; import flixel.util.FlxDestroyUtil; +import openfl.display.BitmapData; import openfl.geom.ColorTransform; using flixel.util.FlxColorTransformUtil; @@ -198,9 +200,7 @@ class FlxBitmapText extends FlxSprite var pendingTextBitmapChange:Bool = true; var pendingPixelsChange:Bool = true; - var textData:Array; - var textDrawData:Array; - var borderDrawData:Array; + var lineDrawData:TextLineList; /** * Helper bitmap buffer for text pixels but without any color transformations @@ -232,10 +232,7 @@ class FlxBitmapText extends FlxSprite } else { - textData = []; - - textDrawData = []; - borderDrawData = []; + lineDrawData = new TextLineList(this); } this.text = text; @@ -256,12 +253,7 @@ class FlxBitmapText extends FlxSprite _colorParams = null; - if (FlxG.renderTile) - { - textData = null; - textDrawData = null; - borderDrawData = null; - } + lineDrawData = FlxDestroyUtil.destroy(lineDrawData); super.destroy(); } @@ -313,7 +305,9 @@ class FlxBitmapText extends FlxSprite } } - override public function draw():Void + static final bgColorTransformDrawHelper = new ColorTransform(); + static final matrixDrawHelper = new FlxMatrix(); + override function draw() { if (FlxG.renderBlit) { @@ -323,80 +317,21 @@ class FlxBitmapText extends FlxSprite else { checkPendingChanges(true); - - var textLength:Int = Std.int(textDrawData.length / 3); - var borderLength:Int = Std.int(borderDrawData.length / 3); - - var dataPos:Int; - - var cr:Float = color.redFloat; - var cg:Float = color.greenFloat; - var cb:Float = color.blueFloat; - - var borderRed:Float = borderColor.redFloat * cr; - var borderGreen:Float = borderColor.greenFloat * cg; - var borderBlue:Float = borderColor.blueFloat * cb; - var bAlpha:Float = borderColor.alphaFloat * alpha; - - var textRed:Float = cr; - var textGreen:Float = cg; - var textBlue:Float = cb; - var tAlpha:Float = alpha; - - if (useTextColor) - { - textRed *= textColor.redFloat; - textGreen *= textColor.greenFloat; - textBlue *= textColor.blueFloat; - tAlpha *= textColor.alphaFloat; - } - - var bgRed:Float = cr; - var bgGreen:Float = cg; - var bgBlue:Float = cb; - var bgAlpha:Float = alpha; - - if (background) - { - bgRed *= backgroundColor.redFloat; - bgGreen *= backgroundColor.greenFloat; - bgBlue *= backgroundColor.blueFloat; - bgAlpha *= backgroundColor.alphaFloat; - } - - var drawItem; - var currFrame:FlxFrame = null; - var currTileX:Float = 0; - var currTileY:Float = 0; - var sx:Float = scale.x * _facingHorizontalMult; - var sy:Float = scale.y * _facingVerticalMult; - - var ox:Float = origin.x; - var oy:Float = origin.y; - - if (_facingHorizontalMult != 1) - { - ox = frameWidth - ox; - } - if (_facingVerticalMult != 1) - { - oy = frameHeight - oy; - } - - var clippedFrameRect; - - if (clipRect != null) - { - clippedFrameRect = clipRect.intersection(FlxRect.weak(0, 0, frameWidth, frameHeight)); - - if (clippedFrameRect.isEmpty) - return; - } - else - { - clippedFrameRect = FlxRect.get(0, 0, frameWidth, frameHeight); - } - + + final trimmedClipRect = getTrimmedRect(); + if (trimmedClipRect != null && trimmedClipRect.isEmpty) + return; + + final colorHelper = Std.int(alpha * 0xFF) << 24 | this.color; + + final scaleX:Float = scale.x * _facingHorizontalMult; + final scaleY:Float = scale.y * _facingVerticalMult; + + final originX:Float = _facingHorizontalMult != 1 ? frameWidth - origin.x : origin.x; + final originY:Float = _facingVerticalMult != 1 ? frameHeight - origin.y : origin.y; + + final screenPos = FlxPoint.get(); + final cameras = getCamerasLegacy(); for (camera in cameras) { @@ -405,11 +340,11 @@ class FlxBitmapText extends FlxSprite continue; } - getScreenPosition(_point, camera).subtractPoint(offset); + getScreenPosition(screenPos, camera).subtractPoint(offset); if (isPixelPerfectRender(camera)) { - _point.floor(); + screenPos.floor(); } updateTrig(); @@ -417,90 +352,62 @@ class FlxBitmapText extends FlxSprite if (background) { // backround tile transformations - currFrame = FlxG.bitmap.whitePixel; - _matrix.identity(); - _matrix.scale(0.1 * clippedFrameRect.width, 0.1 * clippedFrameRect.height); - _matrix.translate(clippedFrameRect.x - ox, clippedFrameRect.y - oy); - _matrix.scale(sx, sy); - - if (angle != 0) + final frame = FlxG.bitmap.whitePixel; + final matrix = matrixDrawHelper; + matrix.identity(); + if (trimmedClipRect != null) { - _matrix.rotateWithTrig(_cosAngle, _sinAngle); + matrix.scale(trimmedClipRect.width, trimmedClipRect.height); + matrix.translate(trimmedClipRect.x, trimmedClipRect.y); } - - _matrix.translate(_point.x + ox, _point.y + oy); - _colorParams.setMultipliers(bgRed, bgGreen, bgBlue, bgAlpha); - camera.drawPixels(currFrame, null, _matrix, _colorParams, blend, antialiasing); - } - - var hasColorOffsets:Bool = (colorTransform != null && colorTransform.hasRGBAOffsets()); - - drawItem = camera.startQuadBatch(font.parent, true, hasColorOffsets, blend, antialiasing, shader); - - for (j in 0...borderLength) - { - dataPos = j * 3; - - currFrame = font.getCharFrame(Std.int(borderDrawData[dataPos])); - - currTileX = borderDrawData[dataPos + 1]; - currTileY = borderDrawData[dataPos + 2]; - - if (clipRect != null) + else { - clippedFrameRect.copyFrom(clipRect).offset(-currTileX, -currTileY); - currFrame = currFrame.clipTo(clippedFrameRect); + matrix.scale(frameWidth, frameHeight); } + + matrix.scale(1 / frame.frame.width, 1 / frame.frame.height); + matrix.translate(-originX, -originY); + matrix.scale(scaleX, scaleY); - currFrame.prepareMatrix(_matrix); - _matrix.translate(currTileX - ox, currTileY - oy); - _matrix.scale(sx, sy); if (angle != 0) { - _matrix.rotateWithTrig(_cosAngle, _sinAngle); + matrix.rotateWithTrig(_cosAngle, _sinAngle); } - _matrix.translate(_point.x + ox, _point.y + oy); - _colorParams.setMultipliers(borderRed, borderGreen, borderBlue, bAlpha); - drawItem.addQuad(currFrame, _matrix, _colorParams); + matrix.translate(screenPos.x + originX, screenPos.y + originY); + final colorTransform = bgColorTransformDrawHelper.reset(); + colorTransform.setMultipliers(colorHelper).scaleMultipliers(backgroundColor); + camera.drawPixels(FlxG.bitmap.whitePixel, null, matrix, colorTransform, blend, antialiasing); } - - for (j in 0...textLength) + + final hasColorOffsets = (colorTransform != null && colorTransform.hasRGBAOffsets()); + final drawItem = camera.startQuadBatch(font.parent, true, hasColorOffsets, blend, antialiasing, shader); + function addQuad(frame:FlxFrame, x:Float, y:Float, color:ColorTransform, charIndex:Int, lineIndex:Int, isBorder:Bool) { - dataPos = j * 3; - - currFrame = font.getCharFrame(Std.int(textDrawData[dataPos])); - - currTileX = textDrawData[dataPos + 1]; - currTileY = textDrawData[dataPos + 2]; - - if (clipRect != null) - { - clippedFrameRect.copyFrom(clipRect).offset(-currTileX, -currTileY); - currFrame = currFrame.clipTo(clippedFrameRect); - } - - currFrame.prepareMatrix(_matrix); - _matrix.translate(currTileX - ox, currTileY - oy); - _matrix.scale(sx, sy); + final matrix = matrixDrawHelper; + frame.prepareMatrix(matrix); + matrix.translate(x - originX, y - originY); + matrix.scale(scaleX, scaleY); if (angle != 0) { - _matrix.rotateWithTrig(_cosAngle, _sinAngle); + matrix.rotateWithTrig(_cosAngle, _sinAngle); } - - _matrix.translate(_point.x + ox, _point.y + oy); - _colorParams.setMultipliers(textRed, textGreen, textBlue, tAlpha); - drawItem.addQuad(currFrame, _matrix, _colorParams); + + matrix.translate(screenPos.x + originX, screenPos.y + originY); + renderTileCharFrame(drawItem, frame, matrix, color, charIndex, lineIndex, isBorder); } - + + renderTileData(trimmedClipRect, padding, padding, addQuad); + #if FLX_DEBUG FlxBasic.visibleCount++; #end } - - // dispose clipRect helpers - clippedFrameRect.put(); - + + // dispose helpers + FlxDestroyUtil.put(trimmedClipRect); + screenPos.put(); + #if FLX_DEBUG if (FlxG.debugger.drawDebug) { @@ -509,7 +416,90 @@ class FlxBitmapText extends FlxSprite #end } } - + + function getTrimmedRect() + { + if (clipRect == null) + return null; + + final result = FlxRect.get(0, 0, frameWidth, frameHeight); + result.clipTo(clipRect); + + return result; + } + + static final borderColorTransformDrawHelper = new ColorTransform(); + static final textColorTransformDrawHelper = new ColorTransform(); + /** + * Finds every character in the given rect, starting with their borders calls drawFunc on + * every character, before rawing foreground text on top. Any character not fully contained + * by the rect is trimmed. If no frameRect is provided, all characters are fully drawn. + * + * **Note:** For performance reasons (and aesthetic preference) the border is trimmed so + * that it extends beyond it's foreground counterpart. I.E.: a border size of 1 will extend + * 1px beyond the trim rect. + * + * @param xOffset Offsets the text position, used for scrolling or padding + * @param yOffset Offsets the text position, used for scrolling or padding + */ + function renderTileData(?frameRect:FlxRect, xOffset:Float, yOffset:Float, drawFunc:CharDrawFunction) + { + final colorHelper = Std.int(alpha * 0xFF) << 24 | this.color; + + final textColorTransform = textColorTransformDrawHelper.reset(); + textColorTransform.setMultipliers(colorHelper); + if (useTextColor) + textColorTransform.scaleMultipliers(textColor); + + final borderColorTransform = borderColorTransformDrawHelper.reset(); + borderColorTransform.setMultipliers(borderColor).scaleMultipliers(colorHelper); + + if (frameRect != null) + { + lineDrawData.forEachTrimmedI(frameRect, xOffset, yOffset, function (frame, x, y, charIndex, lineIndex) + { + forEachBorder(function (xCharOffset, yCharOffset) + { + drawFunc(frame, x + xCharOffset, y + yCharOffset, borderColorTransform, charIndex, lineIndex, true); + }); + }); + + lineDrawData.forEachTrimmedI(frameRect, xOffset, yOffset, drawFunc.bind(_, _, _, textColorTransform, _, _, false)); + } + else + { + final spacing = lineHeight + lineSpacing; + forEachBorder(function (xCharOffset, yCharOffset) + { + lineDrawData.forEachI(function (char, x, charIndex, lineIndex) + drawFunc(font.getCharFrame(char), x + xOffset + xCharOffset, lineIndex * spacing + yOffset + yCharOffset, + borderColorTransform, charIndex, lineIndex, true) + ); + }); + + lineDrawData.forEachI(function (char, x, charIndex, lineIndex) + { + drawFunc(font.getCharFrame(char), x + xOffset, lineIndex * spacing + yOffset, textColorTransform, charIndex, lineIndex, false); + }); + } + } + + /** + * Used internally to render each character in renderTile mode. Can be overriden to + * create per-character effects, like shakey text, or multiple formats + * + * @param drawItem The triangle or quad batch that will render this character + * @param frame The character's frame + * @param matrix the transformation matrix determining the screen position of the character + * @param color The color transformation to apply to this character (`Note:` this instance is used on multiple chars ) + * @param charIndex + * @param lineIndex + */ + function renderTileCharFrame(drawItem:FlxDrawBaseItem, frame, matrix, color, charIndex:Int, lineIndex:Int, isBorder:Bool) + { + drawItem.addQuad(frame, matrix, color); + } + override function set_clipRect(Rect:FlxRect):FlxRect { super.set_clipRect(Rect); @@ -1084,7 +1074,7 @@ class FlxBitmapText extends FlxSprite } else if (FlxG.renderTile) { - textData.splice(0, textData.length); + lineDrawData.clear(); } _fieldWidth = frameWidth; @@ -1137,7 +1127,7 @@ class FlxBitmapText extends FlxSprite if (useTiles) { - tileLine(line, posX, posY); + tileLine(line, posX); } else { @@ -1147,35 +1137,30 @@ class FlxBitmapText extends FlxSprite function blitLine(line:UnicodeString, startX:Int, startY:Int):Void { - var data:Array = []; - addLineData(line, startX, startY, data); + final data:CharList = []; + addLineData(line, startX, data); - while (data.length > 0) + data.forEach(function (charCode, x) { - final charCode = Std.int(data.shift()); - final x = data.shift(); - final y = data.shift(); - final charFrame = font.getCharFrame(charCode); - _flashPoint.setTo(x, y); + _flashPoint.setTo(x, startY); charFrame.paint(textBitmap, _flashPoint, true); - } + }); } - function tileLine(line:UnicodeString, startX:Int, startY:Int) + function tileLine(line:UnicodeString, startX:Int) { if (!FlxG.renderTile) return; - addLineData(line, startX, startY, textData); + final lineData:CharList = []; + addLineData(line, startX, lineData); + lineDrawData.push(lineData); } - function addLineData(line:UnicodeString, startX:Int, startY:Int, data:Array) + function addLineData(line:UnicodeString, startX:Int, data:CharList) { - var pos:Int = data.length; - var curX:Float = startX; - var curY:Int = startY; final lineLength:Int = line.length; final textWidth:Int = this.textWidth; @@ -1197,11 +1182,7 @@ class FlxBitmapText extends FlxSprite final isSpace = isSpaceChar(charCode); final hasFrame = font.charExists(charCode); if (hasFrame && !isSpace) - { - data[pos++] = charCode; - data[pos++] = curX; - data[pos++] = curY; - } + data.push(charCode, curX); if (hasFrame || isSpace) { @@ -1274,8 +1255,7 @@ class FlxBitmapText extends FlxSprite } else { - textDrawData.splice(0, textDrawData.length); - borderDrawData.splice(0, borderDrawData.length); + return; } // use local var to avoid get_width and recursion @@ -1290,14 +1270,25 @@ class FlxBitmapText extends FlxSprite bitmap.lock(); } - var isFront:Bool = false; + forEachBorder(drawText.bind(_, _, false, bitmap, useTiles)); + drawText(0, 0, true, bitmap, useTiles); - var iterations:Int = Std.int(borderSize * borderQuality); - iterations = (iterations <= 0) ? 1 : iterations; + if (!useTiles) + { + bitmap.unlock(); + } - var delta:Int = Std.int(borderSize / iterations); + if (FlxG.renderBlit) + { + dirty = true; + } - // render border + if (pendingPixelsChange) + throw "pendingPixelsChange was changed to true while processing changed pixels"; + } + + function forEachBorder(func:(xOffset:Int, yOffset:Int)->Void) + { switch (borderStyle) { case SHADOW if (_shadowOffset.x != 1 || _shadowOffset.y != 1): @@ -1314,7 +1305,7 @@ class FlxBitmapText extends FlxSprite { for (iterX in 0...iterationsX) { - drawText(deltaX * (iterX + 1), deltaY * (iterY + 1), isFront, bitmap, useTiles); + func(deltaX * (iterX + 1), deltaY * (iterY + 1)); } } @@ -1324,7 +1315,7 @@ class FlxBitmapText extends FlxSprite var i = iterations + 1; while (i-- > 1) { - drawText(Std.int(delta * i), Std.int(delta * i), isFront, bitmap, useTiles); + func(Std.int(delta * i), Std.int(delta * i)); } case SHADOW_XY(shadowX, shadowY): @@ -1334,70 +1325,43 @@ class FlxBitmapText extends FlxSprite var i = iterations + 1; while (i-- > 1) { - drawText(Std.int(shadowX / iterations * i), Std.int(shadowY / iterations * i), isFront, bitmap, useTiles); + func(Std.int(shadowX / iterations * i), Std.int(shadowY / iterations * i)); } case OUTLINE: - // Render an outline around the text - // (do 8 offset draw calls) - var itd:Int = 0; + // Render an outline around the text (8 draws) + var iterations:Int = Std.int(borderSize * borderQuality); + iterations = (iterations <= 0) ? 1 : iterations; + final delta = Std.int(borderSize / iterations); for (iter in 0...iterations) { - itd = delta * (iter + 1); - // upper-left - drawText(-itd, -itd, isFront, bitmap, useTiles); - // upper-middle - drawText(0, -itd, isFront, bitmap, useTiles); - // upper-right - drawText(itd, -itd, isFront, bitmap, useTiles); - // middle-left - drawText(-itd, 0, isFront, bitmap, useTiles); - // middle-right - drawText(itd, 0, isFront, bitmap, useTiles); - // lower-left - drawText(-itd, itd, isFront, bitmap, useTiles); - // lower-middle - drawText(0, itd, isFront, bitmap, useTiles); - // lower-right - drawText(itd, itd, isFront, bitmap, useTiles); + final i = delta * (iter + 1); + func(-i, -i); // upper-left + func( 0, -i); // upper-middle + func( i, -i); // upper-right + func(-i, 0); // middle-left + func( i, 0); // middle-right + func(-i, i); // lower-left + func( 0, i); // lower-middle + func( i, i); // lower-right } case OUTLINE_FAST: - // Render an outline around the text - // (do 4 diagonal offset draw calls) - // (this method might not work with certain narrow fonts) - var itd:Int = 0; + // Render an outline around the text in each corner (4 draws) + var iterations:Int = Std.int(borderSize * borderQuality); + iterations = (iterations <= 0) ? 1 : iterations; + final delta = Std.int(borderSize / iterations); for (iter in 0...iterations) { - itd = delta * (iter + 1); - // upper-left - drawText(-itd, -itd, isFront, bitmap, useTiles); - // upper-right - drawText(itd, -itd, isFront, bitmap, useTiles); - // lower-left - drawText(-itd, itd, isFront, bitmap, useTiles); - // lower-right - drawText(itd, itd, isFront, bitmap, useTiles); + final i = delta * (iter + 1); + func(-i, -i); // upper-left + func( i, -i); // upper-right + func(-i, i); // lower-left + func( i, i); // lower-right } case NONE: } - - isFront = true; - drawText(0, 0, isFront, bitmap, useTiles); - - if (!useTiles) - { - bitmap.unlock(); - } - - if (FlxG.renderBlit) - { - dirty = true; - } - - if (pendingPixelsChange) - throw "pendingPixelsChange was changed to true while processing changed pixels"; } - + function drawText(posX:Int, posY:Int, isFront:Bool = true, ?bitmap:BitmapData, useTiles:Bool = false):Void { if (FlxG.renderBlit) @@ -1407,7 +1371,6 @@ class FlxBitmapText extends FlxSprite if (useTiles) { - tileText(posX, posY, isFront); } else { @@ -1444,42 +1407,6 @@ class FlxBitmapText extends FlxSprite } } - function tileText(posX:Int, posY:Int, isFront:Bool = true):Void - { - if (!FlxG.renderTile) - return; - - var data:Array = isFront ? textDrawData : borderDrawData; - - var pos:Int = data.length; - var textPos:Int; - var textLen:Int = Std.int(textData.length / 3); - var rect = FlxRect.get(); - var frameVisible; - - for (i in 0...textLen) - { - textPos = 3 * i; - - frameVisible = true; - - if (clipRect != null) - { - rect.copyFrom(clipRect).offset(-textData[textPos + 1] - posX, -textData[textPos + 2] - posY); - frameVisible = font.getCharFrame(Std.int(textData[textPos])).clipTo(rect).type != FlxFrameType.EMPTY; - } - - if (frameVisible) - { - data[pos++] = textData[textPos]; - data[pos++] = textData[textPos + 1] + posX; - data[pos++] = textData[textPos + 2] + posY; - } - } - - rect.put(); - } - /** * Set border's style (shadow, outline, etc), color, and size all in one go! * @@ -1820,6 +1747,260 @@ enum WordSplitConditions WIDTH(minPixels:Int); } +typedef CharDrawFunction = (frame:FlxFrame, x:Float, y:Float, color:ColorTransform, charIndex:Int, lineIndex:Int, isBorder:Bool)->Void; + +/** + * Used internally to track and iterate character rendering positions of multiple lines of text + */ +class TextLineList implements IFlxDestroyable +{ + static final trimmedRectHelper = FlxRect.get(); + + public final lines:Array = []; + + var target:FlxBitmapText; + + public function new (target:FlxBitmapText) { this.target = target; } + + public function destroy () + { + clear(); + target = null; + } + + /** + * removes all lines of text + */ + public inline function clear() + { + lines.resize(0); + } + + /** + * Adds a line of text + */ + public function push(line:CharList) + { + lines.push(line); + } + + /** + * Loops through each line of each character and calls `func` on every character + */ + public function forEach(func:(charCode:Int, x:Float, line:Int)->Void) + { + for (i=>line in lines) + line.forEach(func.bind(_, _, i)); + } + + /** + * Loops through each line of each character and calls `func` on every character. + * Similar to `forEach` but includes the character's index on it's line as well + */ + public function forEachI(func:(charCode:Int, x:Float, index:Int, line:Int)->Void) + { + for (i=>line in lines) + line.forEachI(func.bind(_, _, _, i)); + } + + /** + * Loops through each line of each character and calls `func` on every character touching the + * given rectangle. Unlike `forEach` this returns the character's frame, which is trimmed if + * the frame lies in the edge of the trimming rectangle + * + * @param rect The trimming rectangle + * @param xOffset Any offset to apply to each char before checking against the rect + * @param yOffset Any offset to apply to each char before checking against the rect + * @param func The operation to perform on each character inside the rect + * @return ->Void) + */ + public function forEachTrimmed(rect:FlxRect, xOffset:Float, yOffset:Float, func:(frame:FlxFrame, x:Float, y:Float)->Void) + { + function clamp(value:Int) + { + return value < 0 ? 0 : value > lines.length ? lines.length : value; + } + + final trimmedFrame = new FlxFrame(null); + + final start = clamp(Math.floor((rect.top - yOffset) / (target.lineHeight + target.lineSpacing))); + final end = clamp(Math.ceil((rect.bottom - yOffset) / (target.lineHeight + target.lineSpacing))); + for (i in start...end) + { + final y = i * (target.lineHeight + target.lineSpacing) + yOffset; + + lines[i].forEachInBounds(target.font, rect.left - xOffset, rect.right - xOffset, function (charCode, x) + { + final frame = target.font.getCharFrame(charCode); + trimmedRectHelper.copyFrom(rect).offset(-x - xOffset, -y - yOffset); + if (frame.isContained(trimmedRectHelper)) + { + // no trimming necessary + func(frame, x + xOffset, y); + } + else if (frame.overlaps(trimmedRectHelper)) + { + // copy the frame, trim it and draw + frame.clipTo(trimmedRectHelper, trimmedFrame); + func(trimmedFrame, x + xOffset, y); + } + }); + } + + trimmedFrame.destroy(); + } + + /** + * Loops through each line of each character and calls `func` on every character touching the + * given rectangle. Unlike `forEach` this returns the character's frame, which is trimmed if + * the frame lies in the edge of the trimming rectangle + * + * @param rect The trimming rectangle + * @param xOffset Any offset to apply to each char before checking against the rect + * @param yOffset Any offset to apply to each char before checking against the rect + * @param func The operation to perform on each character inside the rect + * @return ->Void) + */ + public function forEachTrimmedI(rect:FlxRect, xOffset:Float, yOffset:Float, func:(frame:FlxFrame, x:Float, y:Float, charIndex:Int, lineIndex:Int)->Void) + { + function clamp(value:Int) + { + return value < 0 ? 0 : value > lines.length ? lines.length : value; + } + + final trimmedFrame = new FlxFrame(null); + + final start = clamp(Math.floor((rect.top - yOffset) / (target.lineHeight + target.lineSpacing))); + final end = clamp(Math.ceil((rect.bottom - yOffset) / (target.lineHeight + target.lineSpacing))); + for (lineIndex in start...end) + { + final y = lineIndex * (target.lineHeight + target.lineSpacing) + yOffset; + + lines[lineIndex].forEachInBoundsI(target.font, rect.left - xOffset, rect.right - xOffset, function (charCode, x, charIndex) + { + final frame = target.font.getCharFrame(charCode); + trimmedRectHelper.copyFrom(rect).offset(-x - xOffset, -y); + if (frame.isContained(trimmedRectHelper)) + { + // no trimming necessary + func(frame, x + xOffset, y, charIndex, lineIndex); + } + else if (frame.overlaps(trimmedRectHelper)) + { + // copy the frame, trim it and draw + frame.clipTo(trimmedRectHelper, trimmedFrame); + func(trimmedFrame, x + xOffset, y, charIndex, lineIndex); + } + }); + } + + trimmedFrame.destroy(); + } +} + +/** + * Used internally to track and iterate character rendering positions of a line of text + */ +@:forward(length) +abstract CharList(Array) from Array +{ + static inline var LENGTH = 2; + static inline var CHAR = 0; + static inline var X = 1; + + public inline function new () + { + this = []; + } + + /** + * Adds a character + * @param charCode An int code representing the character + * @param x The x of the character relative to the `FlxBitmapText` + */ + overload public inline extern function push(charCode:Int, x:Float) + { + this.push(charCode); + this.push(x); + } + + /** + * Loops through and calls `func` on every character + */ + public function forEach(func:(charCode:Int, x:Float)->Void) + { + for (i in 0...Std.int(this.length / LENGTH)) + { + final pos = i * LENGTH; + func(Std.int(this[pos]), this[pos + 1]); + } + } + + /** + * Loops through and calls `func` on every character, similar to `forEach` but includes the + * character's index on it's line as well + */ + public function forEachI(func:(charCode:Int, x:Float, index:Int)->Void) + { + for (i in 0...Std.int(this.length / LENGTH)) + func(getChar(i), getX(i), i); + } + + /** + * Loops through and calls `func` on every character touching the given bounds + * + * @param font The `FlxBitmapFont`, used to get frame data + * @param left The left-most clipping bounds + * @param right The right-most clipping bounds + */ + public function forEachInBounds(font:FlxBitmapFont, left:Float, right:Float, func:(charCode:Int, x:Float)->Void) + { + for (i in 0...Std.int(this.length / LENGTH)) + { + final char = getChar(i); + final frame = font.getCharFrame(char); + final x = getX(i); + if (x + frame.frame.width >= left && x < right) + func(char, x); + } + } + + /** + * Loops through and calls `func` on every character touching the given bounds + * + * @param font The `FlxBitmapFont`, used to get frame data + * @param left The left-most clipping bounds + * @param right The right-most clipping bounds + */ + public function forEachInBoundsI(font:FlxBitmapFont, left:Float, right:Float, func:(charCode:Int, x:Float, index:Int)->Void) + { + for (i in 0...Std.int(this.length / LENGTH)) + { + final char = getChar(i); + final frame = font.getCharFrame(char); + final x = getX(i); + if (x + frame.frame.width >= left && x < right) + func(char, x, i); + } + } + + /** + * Returns the character at the given index + */ + public inline function getChar(index:Int):Int + { + return Std.int(this[index * LENGTH + CHAR]); + } + + /** + * Returns the c position at the given index + */ + public inline function getX(index:Int):Float + { + return this[index * LENGTH + X]; + } +} + /* * TODO - enum WordSplitMethod: determines how words look when split, ex: * * whether split words start on a new line diff --git a/flixel/util/FlxColor.hx b/flixel/util/FlxColor.hx index 8c6328b84e..e3fe2eb9fd 100644 --- a/flixel/util/FlxColor.hx +++ b/flixel/util/FlxColor.hx @@ -517,7 +517,7 @@ abstract FlxColor(Int) from Int from UInt to Int to UInt * @param Alpha How opaque the color should be, either between 0 and 1 or 0 and 255. * @return This color */ - public inline function setHSB(Hue:Float, Saturation:Float, Brightness:Float, Alpha:Float):FlxColor + public inline function setHSB(Hue:Float, Saturation:Float, Brightness:Float, Alpha = 1.0):FlxColor { var chroma = Brightness * Saturation; var match = Brightness - chroma; @@ -533,7 +533,7 @@ abstract FlxColor(Int) from Int from UInt to Int to UInt * @param Alpha How opaque the color should be, either between 0 and 1 or 0 and 255 * @return This color */ - public inline function setHSL(Hue:Float, Saturation:Float, Lightness:Float, Alpha:Float):FlxColor + public inline function setHSL(Hue:Float, Saturation:Float, Lightness:Float, Alpha = 1.0):FlxColor { var chroma = (1 - Math.abs(2 * Lightness - 1)) * Saturation; var match = Lightness - chroma / 2; diff --git a/flixel/util/FlxColorTransformUtil.hx b/flixel/util/FlxColorTransformUtil.hx index c13f7b4e01..8a50fad847 100644 --- a/flixel/util/FlxColorTransformUtil.hx +++ b/flixel/util/FlxColorTransformUtil.hx @@ -4,7 +4,96 @@ import openfl.geom.ColorTransform; class FlxColorTransformUtil { - public static function setMultipliers(transform:ColorTransform, red:Float, green:Float, blue:Float, alpha:Float):ColorTransform + /** + * Resets the transform to default values, multipliers become `1.0` and offsets become `0.0` + */ + public static inline function reset(transform:ColorTransform):ColorTransform + { + return set(transform, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 0.0, 0.0); + } + + /** + * Quick way to set all of a transform's values + * + * @param rMult The value for the red multiplier, ranges from 0 to 1 + * @param gMult The value for the green multiplier, ranges from 0 to 1 + * @param bMult The value for the blue multiplier, ranges from 0 to 1 + * @param aMult The value for the alpha transparency multiplier, ranges from 0 to 1 + * @param rOffset The offset value for the red color channel, ranges from -255 to 255 + * @param gOffset The offset value for the green color channel, ranges from -255 to 255 + * @param bOffset The offset for the blue color channel value, ranges from -255 to 255 + * @param aOffset The offset for alpha transparency channel value, ranges from -255 to 255 + */ + overload public static inline extern function set(transform:ColorTransform, + rMult, gMult, bMult, aMult = 1.0, + rOffset, gOffset, bOffset, aOffset = 0.0):ColorTransform + { + setMultipliers(transform, rMult, gMult, bMult, aMult); + setOffsets(transform, rOffset, gOffset, bOffset, aOffset); + + return transform; + } + + /** + * Quick way to set all of a transform's values + * + * @param colorMult A `FlxColor` whos `redFloat`, `greenFloat`, `blueFloat` and + * `alphaFloat` values determine the multipliers of this transform + * @param color A `FlxColor` whos `red`, `green`, `blue` and `alpha` values + * determine the offsets of this transform + */ + overload public static inline extern function set(transform:ColorTransform, colorMult = FlxColor.WHITE, colorOffset:FlxColor = 0x0):ColorTransform + { + return set(transform, + colorMult.redFloat, colorMult.greenFloat, colorMult.blueFloat, colorMult.alphaFloat, + colorOffset.red, colorOffset.green, colorOffset.blue, colorOffset.alpha + ); + } + + /** + * Scales each color's multiplier by the specifified amount + * + * @param rMult The amount to scale the red multiplier + * @param gMult The amount to scale the green multiplier + * @param bMult The amount to scale the blue multiplier + * @param aMult The amount to scale the alpha transparency multiplier + * @return ColorTransform + */ + overload public static inline extern function scaleMultipliers(transform:ColorTransform, rMult = 1.0, gMult = 1.0, bMult = 1.0, aMult = 1.0):ColorTransform + { + transform.redMultiplier *= rMult; + transform.greenMultiplier *= gMult; + transform.blueMultiplier *= bMult; + transform.alphaMultiplier *= aMult; + + return transform; + } + + /** + * Scales each color's multiplier by the color's specifified normal values + * + * @param color A `FlxColor` whos `redFloat`, `greenFloat`, `blueFloat` and + * `alphaFloat` values scale the multipliers of this transform + */ + overload public static inline extern function scaleMultipliers(transform:ColorTransform, color:FlxColor):ColorTransform + { + transform.redMultiplier *= color.redFloat; + transform.greenMultiplier *= color.greenFloat; + transform.blueMultiplier *= color.blueFloat; + transform.alphaMultiplier *= color.alphaFloat; + + return transform; + } + + /** + * Quick way to set all of a transform's multipliers + * + * @param rMult The value for the red multiplier, ranges from 0 to 1 + * @param gMult The value for the green multiplier, ranges from 0 to 1 + * @param bMult The value for the blue multiplier, ranges from 0 to 1 + * @param aMult The value for the alpha transparency multiplier, ranges from 0 to 1 + */ + overload public static inline extern function setMultipliers(transform:ColorTransform, red:Float, green:Float, blue:Float, alpha:Float):ColorTransform { transform.redMultiplier = red; transform.greenMultiplier = green; @@ -13,7 +102,31 @@ class FlxColorTransformUtil return transform; } + + /** + * Quick way to set all of a transform's multipliers with a single color + * + * @param color A `FlxColor` whos `redFloat`, `greenFloat`, `blueFloat` and + * `alphaFloat` values determine the multipliers of this transform + */ + overload public static inline extern function setMultipliers(transform:ColorTransform, color:FlxColor):ColorTransform + { + transform.redMultiplier = color.redFloat; + transform.greenMultiplier = color.greenFloat; + transform.blueMultiplier = color.blueFloat; + transform.alphaMultiplier = color.alphaFloat; + + return transform; + } + /** + * Quick way to set all of a transform's offsets + * + * @param red The value for the red offset, ranges from 0 to 255 + * @param green The value for the green offset, ranges from 0 to 255 + * @param blue The value for the blue offset, ranges from 0 to 255 + * @param alpha The value for the alpha transparency offset, ranges from 0 to 255 + */ public static function setOffsets(transform:ColorTransform, red:Float, green:Float, blue:Float, alpha:Float):ColorTransform { transform.redOffset = red;