From 4a6ec181205b3c5cf485a13c59e6fb94c777b247 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Mon, 18 Nov 2019 16:24:19 +0800 Subject: [PATCH 01/28] Implement text shadow. --- Runtime/painting/text_style.cs | 32 +++++++++++++------ .../cmdbufferCanvas/rendering/canvas_impl.cs | 19 +++++++++++ Runtime/ui/text.cs | 32 ++++++++++++++----- 3 files changed, 65 insertions(+), 18 deletions(-) diff --git a/Runtime/painting/text_style.cs b/Runtime/painting/text_style.cs index 54fc3fbc..39f45edb 100644 --- a/Runtime/painting/text_style.cs +++ b/Runtime/painting/text_style.cs @@ -24,6 +24,7 @@ public class TextStyle : Diagnosticable, IEquatable { public readonly Paint foreground; public readonly Paint background; public readonly string fontFamily; + public readonly List shadows; public List fontFamilyFallback { get { return this._fontFamilyFallback; } @@ -58,6 +59,7 @@ public TextStyle(bool inherit = true, float? decorationThickness = null, string fontFamily = null, List fontFamilyFallback = null, + List shadows = null, string debugLabel = null) { D.assert(color == null || foreground == null, () => _kColorForegroundWarning); D.assert(backgroundColor == null || background == null, () => _kColorBackgroundWarning); @@ -80,22 +82,22 @@ public TextStyle(bool inherit = true, this.debugLabel = debugLabel; this.foreground = foreground; this.background = background; + this.shadows = shadows; } public RenderComparison compareTo(TextStyle other) { - if (this.inherit != other.inherit || this.fontFamily != other.fontFamily - || this.fontSize != other.fontSize || this.fontWeight != other.fontWeight - || this.fontStyle != other.fontStyle || - this.letterSpacing != other.letterSpacing - || this.wordSpacing != other.wordSpacing || - this.textBaseline != other.textBaseline - || this.height != other.height || this.background != other.background) { + if (this.inherit != other.inherit || this.fontFamily != other.fontFamily || + this.fontSize != other.fontSize || this.fontWeight != other.fontWeight || + this.fontStyle != other.fontStyle || this.letterSpacing != other.letterSpacing || + this.wordSpacing != other.wordSpacing || this.textBaseline != other.textBaseline || + this.height != other.height || this.background != other.background || + this.shadows.equalsList(other.shadows)) { return RenderComparison.layout; } if (this.color != other.color || this.decoration != other.decoration || - this.decorationColor != other.decorationColor - || this.decorationStyle != other.decorationStyle) { + this.decorationColor != other.decorationColor || + this.decorationStyle != other.decorationStyle) { return RenderComparison.paint; } @@ -122,6 +124,7 @@ public TextStyle apply( float decorationThicknessDelta = 0.0f, string fontFamily = null, List fontFamilyFallback = null, + List shadows = null, float fontSizeFactor = 1.0f, float fontSizeDelta = 0.0f, int fontWeightDelta = 0, @@ -172,6 +175,7 @@ public TextStyle apply( decorationThickness: this.decorationThickness == null ? null : this.decorationThickness * decorationThicknessFactor + decorationThicknessDelta, + shadows: shadows ?? this.shadows, debugLabel: modifiedDebugLabel ); } @@ -213,6 +217,7 @@ public TextStyle merge(TextStyle other) { decorationColor: other.decorationColor, decorationStyle: other.decorationStyle, decorationThickness: other.decorationThickness, + shadows: other.shadows, debugLabel: mergedDebugLabel ); } @@ -236,6 +241,7 @@ public TextStyle copyWith( Color decorationColor = null, TextDecorationStyle? decorationStyle = null, float? decorationThickness = null, + List shadows = null, string debugLabel = null) { D.assert(color == null || foreground == null, () => _kColorForegroundWarning); D.assert(backgroundColor == null || background == null, () => _kColorBackgroundWarning); @@ -267,6 +273,7 @@ public TextStyle copyWith( decorationThickness: decorationThickness ?? this.decorationThickness, foreground: foreground ?? this.foreground, background: background ?? this.background, + shadows: shadows ?? this.shadows, debugLabel: newDebugLabel ); } @@ -304,6 +311,7 @@ public static TextStyle lerp(TextStyle a, TextStyle b, float t) { decorationColor: Color.lerp(null, b.decorationColor, t), decorationStyle: t < 0.5f ? null : b.decorationStyle, decorationThickness: t < 0.5f ? null : b.decorationThickness, + shadows: t < 0.5f ? null : b.shadows, debugLabel: lerpDebugLabel ); } @@ -328,6 +336,7 @@ public static TextStyle lerp(TextStyle a, TextStyle b, float t) { decorationColor: Color.lerp(a.decorationColor, null, t), decorationStyle: t < 0.5f ? a.decorationStyle : null, decorationThickness: t < 0.5f ? a.decorationThickness : null, + shadows: t < 0.5f ? a.shadows : null, debugLabel: lerpDebugLabel ); } @@ -365,6 +374,7 @@ public static TextStyle lerp(TextStyle a, TextStyle b, float t) { decorationThickness: MathUtils.lerpFloat( a.decorationThickness ?? b.decorationThickness ?? 0.0f, b.decorationThickness ?? a.decorationThickness ?? 0.0f, t), + shadows: t < 0.5f ? a.shadows : b.shadows, debugLabel: lerpDebugLabel ); } @@ -471,7 +481,8 @@ public bool Equals(TextStyle other) { this.decorationThickness == other.decorationThickness && Equals(this.foreground, other.foreground) && Equals(this.background, other.background) && - CollectionUtils.equalsList(this.fontFamilyFallback, other.fontFamilyFallback) && + this.fontFamilyFallback.equalsList(other.fontFamilyFallback) && + this.shadows.equalsList(other.shadows) && string.Equals(this.fontFamily, other.fontFamily); } @@ -512,6 +523,7 @@ public override int GetHashCode() { hashCode = (hashCode * 397) ^ (this.fontFamily != null ? this.fontFamily.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (this.fontFamilyFallback != null ? this.fontFamilyFallback.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.shadows != null ? this.shadows.GetHashCode() : 0); return hashCode; } } diff --git a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs index b65a9c11..86cd6646 100644 --- a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs +++ b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs @@ -870,6 +870,7 @@ void _drawPicture(Picture picture, bool needsSave = true) { } case DrawTextBlob cmd: { + this._paintTextShadow(cmd.textBlob, cmd.offset); this._drawTextBlob(cmd.textBlob, (uiOffset.fromOffset(cmd.offset)).Value, uiPaint.fromPaint(cmd.paint)); break; @@ -994,6 +995,7 @@ void _drawUIPicture(uiPicture picture, bool needsSave = true) { } case uiDrawTextBlob cmd: { + this._paintTextShadow(cmd.textBlob, new Offset(cmd.offset.Value.dx, cmd.offset.Value.dy)); this._drawTextBlob(cmd.textBlob, cmd.offset.Value, cmd.paint); break; } @@ -1012,6 +1014,22 @@ void _drawUIPicture(uiPicture picture, bool needsSave = true) { } } + void _paintTextShadow(TextBlob? textBlob, Offset offset) { + D.assert(textBlob != null); + if (textBlob.Value.style.shadows != null && textBlob.Value.style.shadows.isNotEmpty()) { + textBlob.Value.style.shadows.ForEach(shadow => { + Paint paint = new Paint { + color = shadow.color, + maskFilter = shadow.blurRadius != 0 + ? MaskFilter.blur(BlurStyle.normal, shadow.blurRadius) + : null, + }; + this._drawTextBlob(textBlob, uiOffset.fromOffset(shadow.offset + offset).Value, + uiPaint.fromPaint(paint)); + }); + } + } + void _drawTextBlob(TextBlob? textBlob, uiOffset offset, uiPaint paint) { D.assert(textBlob != null); @@ -1043,6 +1061,7 @@ void _drawTextBlob(TextBlob? textBlob, uiOffset offset, uiPaint paint) { } this._drawTextDrawMeshCallback(paint, null, null, false, 0, 0, tex, textBlobBounds, mesh, notEmoji); + } public void flush(uiPicture picture) { diff --git a/Runtime/ui/text.cs b/Runtime/ui/text.cs index 995a158c..643ca9c9 100644 --- a/Runtime/ui/text.cs +++ b/Runtime/ui/text.cs @@ -1,5 +1,7 @@ using System; using System.Collections.Generic; +using System.Collections.Specialized; +using Unity.UIWidgets.foundation; using Unity.UIWidgets.painting; namespace Unity.UIWidgets.ui { @@ -99,6 +101,7 @@ class TextStyle : IEquatable { public readonly string fontFamily = kDefaultFontFamily; public readonly Paint foreground; public readonly Paint background; + public readonly List shadows; internal UnityEngine.Color UnityColor { get { return this.color.toColor(); } @@ -142,7 +145,8 @@ public static TextStyle applyStyle(TextStyle currentStyle, painting.TextStyle st decorationColor: style.decorationColor ?? currentStyle.decorationColor, fontFamily: style.fontFamily ?? currentStyle.fontFamily, foreground: style.foreground ?? currentStyle.foreground, - background: style.background ?? currentStyle.background + background: style.background ?? currentStyle.background, + shadows: style.shadows ?? currentStyle.shadows ); } @@ -159,7 +163,8 @@ public static TextStyle applyStyle(TextStyle currentStyle, painting.TextStyle st decorationColor: style.decorationColor, fontFamily: style.fontFamily, foreground: style.foreground, - background: style.background + background: style.background, + shadows: style.shadows ); } @@ -179,7 +184,8 @@ public bool Equals(TextStyle other) { Equals(this.decoration, other.decoration) && Equals(this.decorationColor, other.decorationColor) && this.decorationStyle == other.decorationStyle && - string.Equals(this.fontFamily, other.fontFamily); + string.Equals(this.fontFamily, other.fontFamily) && + this.shadows.equalsList(other.shadows); } public override bool Equals(object obj) { @@ -212,6 +218,7 @@ public override int GetHashCode() { hashCode = (hashCode * 397) ^ (this.decorationColor != null ? this.decorationColor.GetHashCode() : 0); hashCode = (hashCode * 397) ^ this.decorationStyle.GetHashCode(); hashCode = (hashCode * 397) ^ (this.fontFamily != null ? this.fontFamily.GetHashCode() : 0); + hashCode = (hashCode * 397) ^ (this.shadows != null ? this.shadows.GetHashCode() : 0); return hashCode; } } @@ -225,13 +232,21 @@ public override int GetHashCode() { } - public TextStyle(Color color = null, float? fontSize = null, - FontWeight fontWeight = null, FontStyle? fontStyle = null, float? letterSpacing = null, - float? wordSpacing = null, TextBaseline? textBaseline = null, float? height = null, - TextDecoration decoration = null, TextDecorationStyle? decorationStyle = null, Color decorationColor = null, + public TextStyle(Color color = null, + float? fontSize = null, + FontWeight fontWeight = null, + FontStyle? fontStyle = null, + float? letterSpacing = null, + float? wordSpacing = null, + TextBaseline? textBaseline = null, + float? height = null, + TextDecoration decoration = null, + TextDecorationStyle? decorationStyle = null, + Color decorationColor = null, string fontFamily = null, Paint foreground = null, - Paint background = null + Paint background = null, + List shadows = null ) { this.color = color ?? this.color; this.fontSize = fontSize ?? this.fontSize; @@ -248,6 +263,7 @@ public TextStyle(Color color = null, float? fontSize = null, this.fontFamily = fontFamily ?? this.fontFamily; this.foreground = foreground ?? this.foreground; this.background = background ?? this.background; + this.shadows = shadows ?? this.shadows; } } From 8157c99f1b153382d452b357e6d32e3d3db9ec08 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Mon, 18 Nov 2019 16:27:06 +0800 Subject: [PATCH 02/28] Cleanup. --- Runtime/ui/text.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/Runtime/ui/text.cs b/Runtime/ui/text.cs index 643ca9c9..4afeee1c 100644 --- a/Runtime/ui/text.cs +++ b/Runtime/ui/text.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.Specialized; using Unity.UIWidgets.foundation; using Unity.UIWidgets.painting; From 9ba87f1b624cd2bccd744d37ff05f0e1d83f1567 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Mon, 18 Nov 2019 16:55:40 +0800 Subject: [PATCH 03/28] Fix text shadow on emoji. --- Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs index 86cd6646..9235d296 100644 --- a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs +++ b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs @@ -1055,8 +1055,9 @@ void _drawTextBlob(TextBlob? textBlob, uiOffset offset, uiPaint paint) { } if (paint.maskFilter != null && paint.maskFilter.Value.sigma != 0) { - this._drawWithMaskFilter(textBlobBounds, paint, paint.maskFilter.Value, null, null, false, 0, 0, tex, - textBlobBounds, mesh, notEmoji, this.___drawTextDrawMeshCallback); + this._drawWithMaskFilter(textBlobBounds, paint, paint.maskFilter.Value, null, null, false, 0, 0, + notEmoji ? tex : EmojiUtils.image.texture, + textBlobBounds, mesh, true, this.___drawTextDrawMeshCallback); return; } From dec572c672d024c8851ecd8f92702e31a41b4e78 Mon Sep 17 00:00:00 2001 From: "xingwei.zhu" Date: Tue, 19 Nov 2019 11:27:14 +0800 Subject: [PATCH 04/28] fix editable ios bug when typing --- Runtime/rendering/editable.cs | 34 +++++++++++++++++++++++----------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/Runtime/rendering/editable.cs b/Runtime/rendering/editable.cs index 483e5fe0..fa406549 100644 --- a/Runtime/rendering/editable.cs +++ b/Runtime/rendering/editable.cs @@ -1320,7 +1320,7 @@ Rect _getCaretPrototype { get { switch (Application.platform) { case RuntimePlatform.IPhonePlayer: - return Rect.fromLTWH(0.0f, -EditableUtils._kCaretHeightOffset + 0.5f, this.cursorWidth, + return Rect.fromLTWH(0.0f, 0.0f, this.cursorWidth, this.preferredLineHeight + 2.0f); default: return Rect.fromLTWH(0.0f, EditableUtils._kCaretHeightOffset, this.cursorWidth, @@ -1363,17 +1363,29 @@ void _paintCaret(Canvas canvas, Offset effectiveOffset, TextPosition textPositio if (this._cursorOffset != null) { caretRect = caretRect.shift(this._cursorOffset); } - -#if !UNITY_IOS - if (this._textPainter.getFullHeightForCaret(textPosition, this._caretPrototype) != null) { - caretRect = Rect.fromLTWH( - caretRect.left, - caretRect.top - EditableUtils._kCaretHeightOffset, - caretRect.width, - this._textPainter.getFullHeightForCaret(textPosition, this._caretPrototype).Value - ); + + float? caretHeight = this._textPainter.getFullHeightForCaret(textPosition, this._caretPrototype); + if (caretHeight != null) { + switch (Application.platform) { + case RuntimePlatform.IPhonePlayer: + float heightDiff = caretHeight.Value - caretRect.height; + caretRect = Rect.fromLTWH( + caretRect.left, + caretRect.top + heightDiff / 2f, + caretRect.width, + caretRect.height + ); + break; + default: + caretRect = Rect.fromLTWH( + caretRect.left, + caretRect.top - EditableUtils._kCaretHeightOffset, + caretRect.width, + caretHeight.Value + ); + break; + } } -#endif caretRect = caretRect.shift(this._getPixelPerfectCursorOffset(caretRect)); From fa678e3db7aa94cc0435de904da99e1f90ac202b Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Tue, 19 Nov 2019 16:44:18 +0800 Subject: [PATCH 05/28] Check canvas clipping bounds and record bounds. --- Runtime/ui/painting/path.cs | 8 +++ .../cmdbufferCanvas/rendering/canvas_impl.cs | 64 +++++++++++++++++++ .../ui/renderer/common/geometry/path/path.cs | 8 +++ Runtime/ui/txt/paragraph.cs | 3 +- 4 files changed, 82 insertions(+), 1 deletion(-) diff --git a/Runtime/ui/painting/path.cs b/Runtime/ui/painting/path.cs index 1c157ab1..b30c96da 100644 --- a/Runtime/ui/painting/path.cs +++ b/Runtime/ui/painting/path.cs @@ -226,6 +226,14 @@ public Rect getBounds() { return Rect.fromLTRB(this._minX, this._minY, this._maxX, this._maxY); } + public Rect getBoundsWithMargin(float margin) { + if (this._minX - margin >= this._maxX + margin || this._minY - margin >= this._maxY + margin) { + return Rect.zero; + } + + return Rect.fromLTRB(this._minX - margin, this._minY - margin, this._maxX + margin, this._maxY + margin); + } + public static Path combine(PathOperation operation, Path path1, Path path2) { D.assert(path1 != null); D.assert(path2 != null); diff --git a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs index 9235d296..252b69a8 100644 --- a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs +++ b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs @@ -766,6 +766,9 @@ void _drawPicture(Picture picture, bool needsSave = true) { int saveCount = 0; var drawCmds = picture.drawCmds; + var queryBound = this._currentLayer.currentState.matrix?.invert().Value + .mapRect(this._currentLayer.layerBounds) ?? + this._currentLayer.layerBounds; foreach (var drawCmd in drawCmds) { switch (drawCmd) { case DrawSave _: @@ -839,6 +842,10 @@ void _drawPicture(Picture picture, bool needsSave = true) { } case DrawPath cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromRect( + cmd.path.getBoundsWithMargin(5)).Value, queryBound).isEmpty) { + continue; + } var uipath = uiPath.fromPath(cmd.path); this._drawPath(uipath, uiPaint.fromPaint(cmd.paint)); uiPathCacheManager.putToCache(uipath); @@ -846,18 +853,35 @@ void _drawPicture(Picture picture, bool needsSave = true) { } case DrawImage cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromLTWH( + cmd.offset.dx, cmd.offset.dy, + cmd.image.width / this._devicePixelRatio, + cmd.image.height / this._devicePixelRatio), queryBound).isEmpty) { + continue; + } this._drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, uiPaint.fromPaint(cmd.paint)); break; } case DrawImageRect cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromRect(cmd.dst).Value, queryBound).isEmpty) { + continue; + } this._drawImageRect(cmd.image, uiRectHelper.fromRect(cmd.src), uiRectHelper.fromRect(cmd.dst).Value, uiPaint.fromPaint(cmd.paint)); break; } case DrawImageNine cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromLTRB( + cmd.dst.left + ((cmd.center.left - cmd.src.left) * cmd.src.width), + cmd.dst.top + ((cmd.center.top - cmd.src.top) * cmd.src.height), + cmd.dst.right - ((cmd.src.right - cmd.center.right) * cmd.src.width), + cmd.dst.bottom - ((cmd.src.bottom - cmd.center.bottom) * cmd.src.height) + ), queryBound).isEmpty) { + continue; + } this._drawImageNine(cmd.image, uiRectHelper.fromRect(cmd.src), uiRectHelper.fromRect(cmd.center).Value, uiRectHelper.fromRect(cmd.dst).Value, uiPaint.fromPaint(cmd.paint)); @@ -865,11 +889,19 @@ void _drawPicture(Picture picture, bool needsSave = true) { } case DrawPicture cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromRect( + cmd.picture.paintBounds).Value, queryBound).isEmpty) { + continue; + } this._drawPicture(cmd.picture); break; } case DrawTextBlob cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromRect( + cmd.textBlob.Value.boundsInText.translate(cmd.offset.dx, cmd.offset.dy)).Value, queryBound).isEmpty) { + continue; + } this._paintTextShadow(cmd.textBlob, cmd.offset); this._drawTextBlob(cmd.textBlob, (uiOffset.fromOffset(cmd.offset)).Value, uiPaint.fromPaint(cmd.paint)); @@ -899,6 +931,9 @@ void _drawUIPicture(uiPicture picture, bool needsSave = true) { int saveCount = 0; var drawCmds = picture.drawCmds; + var queryBound = this._currentLayer.currentState.matrix?.invert().Value + .mapRect(this._currentLayer.layerBounds) ?? + this._currentLayer.layerBounds; foreach (var drawCmd in drawCmds) { switch (drawCmd) { case uiDrawSave _: @@ -970,31 +1005,60 @@ void _drawUIPicture(uiPicture picture, bool needsSave = true) { } case uiDrawPath cmd: { + if (uiRectHelper.intersect(cmd.path.getBoundsWithMargin(5), queryBound).isEmpty) { + continue; + } this._drawPath(cmd.path, cmd.paint); break; } case uiDrawImage cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromLTWH( + cmd.offset.Value.dx, cmd.offset.Value.dy, + cmd.image.width / this._devicePixelRatio, + cmd.image.height / this._devicePixelRatio), queryBound).isEmpty) { + continue; + } this._drawImage(cmd.image, cmd.offset.Value, cmd.paint); break; } case uiDrawImageRect cmd: { + if (uiRectHelper.intersect(cmd.dst.Value, queryBound).isEmpty) { + continue; + } this._drawImageRect(cmd.image, cmd.src, cmd.dst.Value, cmd.paint); break; } case uiDrawImageNine cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromLTRB( + cmd.dst.Value.left + ((cmd.center.Value.left - cmd.src.Value.left) * cmd.src.Value.width), + cmd.dst.Value.top + ((cmd.center.Value.top - cmd.src.Value.top) * cmd.src.Value.height), + cmd.dst.Value.right - ((cmd.src.Value.right - cmd.center.Value.right) * cmd.src.Value.width), + cmd.dst.Value.bottom - ((cmd.src.Value.bottom - cmd.center.Value.bottom) * cmd.src.Value.height) + ), queryBound).isEmpty) { + continue; + } this._drawImageNine(cmd.image, cmd.src, cmd.center.Value, cmd.dst.Value, cmd.paint); break; } case uiDrawPicture cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromRect( + cmd.picture.paintBounds).Value, queryBound).isEmpty) { + continue; + } this._drawPicture(cmd.picture); break; } case uiDrawTextBlob cmd: { + if (uiRectHelper.intersect(uiRectHelper.fromRect( + cmd.textBlob.Value.boundsInText.translate(cmd.offset.Value.dx, cmd.offset.Value.dy)).Value, + queryBound).isEmpty) { + continue; + } this._paintTextShadow(cmd.textBlob, new Offset(cmd.offset.Value.dx, cmd.offset.Value.dy)); this._drawTextBlob(cmd.textBlob, cmd.offset.Value, cmd.paint); break; diff --git a/Runtime/ui/renderer/common/geometry/path/path.cs b/Runtime/ui/renderer/common/geometry/path/path.cs index 19141c9b..e4e25742 100644 --- a/Runtime/ui/renderer/common/geometry/path/path.cs +++ b/Runtime/ui/renderer/common/geometry/path/path.cs @@ -170,6 +170,14 @@ public uiRect getBounds() { return uiRectHelper.fromLTRB(this._minX, this._minY, this._maxX, this._maxY); } + public uiRect getBoundsWithMargin(float margin) { + if (this._minX - margin >= this._maxX + margin || this._minY - margin >= this._maxY + margin) { + return uiRectHelper.zero; + } + + return uiRectHelper.fromLTRB(this._minX - margin, this._minY - margin, this._maxX + margin, this._maxY + margin); + } + void _appendMoveTo(float x, float y) { this._commands.Add((float) uiPathCommand.moveTo); this._commands.Add(x); diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index 77ae2045..0c765351 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -472,7 +472,8 @@ public void layout(ParagraphConstraints constraints) { // If all the positions have not changed, use the cached ellipsized text // else update the cache - if (!(this._ellipsizedLength == textStart + textCount - truncateCount && + if (!(this._ellipsizedText != null && + this._ellipsizedLength == textStart + textCount - truncateCount && this._ellipsizedText.Length == this._ellipsizedLength + ellipsis.Length && this._ellipsizedText.EndsWith(ellipsis))) { this._ellipsizedText = From 15d57265d3f8a4b4c4b28ab049e5ab0ab99fed36 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Tue, 19 Nov 2019 16:52:12 +0800 Subject: [PATCH 06/28] Split SplayTree in separate file. --- Runtime/external/SplayTree.cs | 493 ++++++++++++++++++++++ Runtime/external/SplayTree.cs.meta | 11 + Runtime/painting/gradient.cs | 1 + Runtime/ui/txt/paragraph.cs | 488 +-------------------- Runtime/widgets/list_wheel_scroll_view.cs | 1 + Runtime/widgets/sliver.cs | 1 + 6 files changed, 508 insertions(+), 487 deletions(-) create mode 100644 Runtime/external/SplayTree.cs create mode 100644 Runtime/external/SplayTree.cs.meta diff --git a/Runtime/external/SplayTree.cs b/Runtime/external/SplayTree.cs new file mode 100644 index 00000000..f35515ac --- /dev/null +++ b/Runtime/external/SplayTree.cs @@ -0,0 +1,493 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace Unity.UIWidgets.Runtime.external +{ class SplayTree : IDictionary where TKey : IComparable { + SplayTreeNode root; + int count; + int version = 0; + + public void Add(TKey key, TValue value) { + this.Set(key, value, throwOnExisting: true); + } + + public void Add(KeyValuePair item) { + this.Set(item.Key, item.Value, throwOnExisting: true); + } + + public void AddAll(IEnumerable list) { + foreach (var key in list) { + this.Add(new KeyValuePair(key, default)); + } + } + + void Set(TKey key, TValue value, bool throwOnExisting) { + if (this.count == 0) { + this.version++; + this.root = new SplayTreeNode(key, value); + this.count = 1; + return; + } + + this.Splay(key); + + var c = key.CompareTo(this.root.Key); + if (c == 0) { + if (throwOnExisting) { + throw new ArgumentException("An item with the same key already exists in the tree."); + } + + this.version++; + this.root.Value = value; + return; + } + + var n = new SplayTreeNode(key, value); + if (c < 0) { + n.LeftChild = this.root.LeftChild; + n.RightChild = this.root; + this.root.LeftChild = null; + } + else { + n.RightChild = this.root.RightChild; + n.LeftChild = this.root; + this.root.RightChild = null; + } + + this.root = n; + this.count++; + this.Splay(key); + this.version++; + } + + public void Clear() { + this.root = null; + this.count = 0; + this.version++; + } + + public bool ContainsKey(TKey key) { + if (this.count == 0) { + return false; + } + + this.Splay(key); + + return key.CompareTo(this.root.Key) == 0; + } + + public bool Contains(KeyValuePair item) { + if (this.count == 0) { + return false; + } + + this.Splay(item.Key); + + return item.Key.CompareTo(this.root.Key) == 0 && + (ReferenceEquals(this.root.Value, item.Value) || + (!ReferenceEquals(item.Value, null) && item.Value.Equals(this.root.Value))); + } + + public KeyValuePair? First() { + SplayTreeNode t = this.root; + if (t == null) { + return null; + } + + while (t.LeftChild != null) { + t = t.LeftChild; + } + + return new KeyValuePair(t.Key, t.Value); + } + + public KeyValuePair FirstOrDefault() { + SplayTreeNode t = this.root; + if (t == null) { + return new KeyValuePair(default(TKey), default(TValue)); + } + + while (t.LeftChild != null) { + t = t.LeftChild; + } + + return new KeyValuePair(t.Key, t.Value); + } + + public KeyValuePair? Last() { + SplayTreeNode t = this.root; + if (t == null) { + return null; + } + + while (t.RightChild != null) { + t = t.RightChild; + } + + return new KeyValuePair(t.Key, t.Value); + } + + public KeyValuePair LastOrDefault() { + SplayTreeNode t = this.root; + if (t == null) { + return new KeyValuePair(default(TKey), default(TValue)); + } + + while (t.RightChild != null) { + t = t.RightChild; + } + + return new KeyValuePair(t.Key, t.Value); + } + + void Splay(TKey key) { + SplayTreeNode l, r, t, y, header; + l = r = header = new SplayTreeNode(default(TKey), default(TValue)); + t = this.root; + while (true) { + var c = key.CompareTo(t.Key); + if (c < 0) { + if (t.LeftChild == null) { + break; + } + + if (key.CompareTo(t.LeftChild.Key) < 0) { + y = t.LeftChild; + t.LeftChild = y.RightChild; + y.RightChild = t; + t = y; + if (t.LeftChild == null) { + break; + } + } + + r.LeftChild = t; + r = t; + t = t.LeftChild; + } + else if (c > 0) { + if (t.RightChild == null) { + break; + } + + if (key.CompareTo(t.RightChild.Key) > 0) { + y = t.RightChild; + t.RightChild = y.LeftChild; + y.LeftChild = t; + t = y; + if (t.RightChild == null) { + break; + } + } + + l.RightChild = t; + l = t; + t = t.RightChild; + } + else { + break; + } + } + + l.RightChild = t.LeftChild; + r.LeftChild = t.RightChild; + t.LeftChild = header.RightChild; + t.RightChild = header.LeftChild; + this.root = t; + } + + public bool Remove(TKey key) { + if (this.count == 0) { + return false; + } + + this.Splay(key); + + if (key.CompareTo(this.root.Key) != 0) { + return false; + } + + if (this.root.LeftChild == null) { + this.root = this.root.RightChild; + } + else { + var swap = this.root.RightChild; + this.root = this.root.LeftChild; + this.Splay(key); + this.root.RightChild = swap; + } + + this.version++; + this.count--; + return true; + } + + public bool TryGetValue(TKey key, out TValue value) { + if (this.count == 0) { + value = default(TValue); + return false; + } + + this.Splay(key); + if (key.CompareTo(this.root.Key) != 0) { + value = default(TValue); + return false; + } + + value = this.root.Value; + return true; + } + + public TValue this[TKey key] { + get { + if (this.count == 0) { + throw new KeyNotFoundException("The key was not found in the tree."); + } + + this.Splay(key); + if (key.CompareTo(this.root.Key) != 0) { + throw new KeyNotFoundException("The key was not found in the tree."); + } + + return this.root.Value; + } + + set { this.Set(key, value, throwOnExisting: false); } + } + + public int Count { + get { return this.count; } + } + + public bool IsReadOnly { + get { return false; } + } + + public bool Remove(KeyValuePair item) { + if (this.count == 0) { + return false; + } + + this.Splay(item.Key); + + if (item.Key.CompareTo(this.root.Key) == 0 && (ReferenceEquals(this.root.Value, item.Value) || + (!ReferenceEquals(item.Value, null) && + item.Value.Equals(this.root.Value)))) { + return false; + } + + if (this.root.LeftChild == null) { + this.root = this.root.RightChild; + } + else { + var swap = this.root.RightChild; + this.root = this.root.LeftChild; + this.Splay(item.Key); + this.root.RightChild = swap; + } + + this.version++; + this.count--; + return true; + } + + public void Trim(int depth) { + if (depth < 0) { + throw new ArgumentOutOfRangeException("depth", "The trim depth must not be negative."); + } + + if (this.count == 0) { + return; + } + + if (depth == 0) { + this.Clear(); + } + else { + var prevCount = this.count; + this.count = this.Trim(this.root, depth - 1); + if (prevCount != this.count) { + this.version++; + } + } + } + + int Trim(SplayTreeNode node, int depth) { + if (depth == 0) { + node.LeftChild = null; + node.RightChild = null; + return 1; + } + else { + int count = 1; + + if (node.LeftChild != null) { + count += this.Trim(node.LeftChild, depth - 1); + } + + if (node.RightChild != null) { + count += this.Trim(node.RightChild, depth - 1); + } + + return count; + } + } + + public ICollection Keys { + get { return new TiedList(this, this.version, this.AsList(node => node.Key)); } + } + + public ICollection Values { + get { return new TiedList(this, this.version, this.AsList(node => node.Value)); } + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) { + this.AsList(node => new KeyValuePair(node.Key, node.Value)).CopyTo(array, arrayIndex); + } + + public IEnumerator> GetEnumerator() { + return new TiedList>(this, this.version, + this.AsList(node => new KeyValuePair(node.Key, node.Value))).GetEnumerator(); + } + + IList AsList(Func selector) { + if (this.root == null) { + return new TEnumerator[0]; + } + + var result = new List(this.count); + this.PopulateList(this.root, result, selector); + return result; + } + + void PopulateList(SplayTreeNode node, List list, + Func selector) { + if (node.LeftChild != null) { + this.PopulateList(node.LeftChild, list, selector); + } + + list.Add(selector(node)); + if (node.RightChild != null) { + this.PopulateList(node.RightChild, list, selector); + } + } + + IEnumerator IEnumerable.GetEnumerator() { + return this.GetEnumerator(); + } + + sealed class SplayTreeNode { + public readonly TKey Key; + + public TValue Value; + public SplayTreeNode LeftChild; + public SplayTreeNode RightChild; + + public SplayTreeNode(TKey key, TValue value) { + this.Key = key; + this.Value = value; + } + } + + sealed class TiedList : IList { + readonly SplayTree tree; + readonly int version; + readonly IList backingList; + + public TiedList(SplayTree tree, int version, IList backingList) { + if (tree == null) { + throw new ArgumentNullException("tree"); + } + + if (backingList == null) { + throw new ArgumentNullException("backingList"); + } + + this.tree = tree; + this.version = version; + this.backingList = backingList; + } + + public int IndexOf(T item) { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + return this.backingList.IndexOf(item); + } + + public void Insert(int index, T item) { + throw new NotSupportedException(); + } + + public void RemoveAt(int index) { + throw new NotSupportedException(); + } + + public T this[int index] { + get { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + return this.backingList[index]; + } + set { throw new NotSupportedException(); } + } + + public void Add(T item) { + throw new NotSupportedException(); + } + + public void Clear() { + throw new NotSupportedException(); + } + + public bool Contains(T item) { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + return this.backingList.Contains(item); + } + + public void CopyTo(T[] array, int arrayIndex) { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + this.backingList.CopyTo(array, arrayIndex); + } + + public int Count { + get { return this.tree.count; } + } + + public bool IsReadOnly { + get { return true; } + } + + public bool Remove(T item) { + throw new NotSupportedException(); + } + + public IEnumerator GetEnumerator() { + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + + foreach (var item in this.backingList) { + yield return item; + if (this.tree.version != this.version) { + throw new InvalidOperationException("The collection has been modified."); + } + } + } + + IEnumerator IEnumerable.GetEnumerator() { + return this.GetEnumerator(); + } + } + } +} \ No newline at end of file diff --git a/Runtime/external/SplayTree.cs.meta b/Runtime/external/SplayTree.cs.meta new file mode 100644 index 00000000..21a69192 --- /dev/null +++ b/Runtime/external/SplayTree.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 600f58b77585e6845aa9f0924a5af554 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/painting/gradient.cs b/Runtime/painting/gradient.cs index 289ec8b7..a4eafd51 100644 --- a/Runtime/painting/gradient.cs +++ b/Runtime/painting/gradient.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; using Unity.UIWidgets.ui; using UnityEngine; using Color = Unity.UIWidgets.ui.Color; diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index 0c765351..8304485f 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -1,7 +1,7 @@ using System; -using System.Collections; using System.Collections.Generic; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; using UnityEngine; namespace Unity.UIWidgets.ui { @@ -1121,491 +1121,5 @@ float getLineXOffset(float lineTotalAdvance) { } } - class SplayTree : IDictionary where TKey : IComparable { - SplayTreeNode root; - int count; - int version = 0; - public void Add(TKey key, TValue value) { - this.Set(key, value, throwOnExisting: true); - } - - public void Add(KeyValuePair item) { - this.Set(item.Key, item.Value, throwOnExisting: true); - } - - public void AddAll(IEnumerable list) { - foreach (var key in list) { - this.Add(new KeyValuePair(key, default)); - } - } - - void Set(TKey key, TValue value, bool throwOnExisting) { - if (this.count == 0) { - this.version++; - this.root = new SplayTreeNode(key, value); - this.count = 1; - return; - } - - this.Splay(key); - - var c = key.CompareTo(this.root.Key); - if (c == 0) { - if (throwOnExisting) { - throw new ArgumentException("An item with the same key already exists in the tree."); - } - - this.version++; - this.root.Value = value; - return; - } - - var n = new SplayTreeNode(key, value); - if (c < 0) { - n.LeftChild = this.root.LeftChild; - n.RightChild = this.root; - this.root.LeftChild = null; - } - else { - n.RightChild = this.root.RightChild; - n.LeftChild = this.root; - this.root.RightChild = null; - } - - this.root = n; - this.count++; - this.Splay(key); - this.version++; - } - - public void Clear() { - this.root = null; - this.count = 0; - this.version++; - } - - public bool ContainsKey(TKey key) { - if (this.count == 0) { - return false; - } - - this.Splay(key); - - return key.CompareTo(this.root.Key) == 0; - } - - public bool Contains(KeyValuePair item) { - if (this.count == 0) { - return false; - } - - this.Splay(item.Key); - - return item.Key.CompareTo(this.root.Key) == 0 && - (ReferenceEquals(this.root.Value, item.Value) || - (!ReferenceEquals(item.Value, null) && item.Value.Equals(this.root.Value))); - } - - public KeyValuePair? First() { - SplayTreeNode t = this.root; - if (t == null) { - return null; - } - - while (t.LeftChild != null) { - t = t.LeftChild; - } - - return new KeyValuePair(t.Key, t.Value); - } - - public KeyValuePair FirstOrDefault() { - SplayTreeNode t = this.root; - if (t == null) { - return new KeyValuePair(default(TKey), default(TValue)); - } - - while (t.LeftChild != null) { - t = t.LeftChild; - } - - return new KeyValuePair(t.Key, t.Value); - } - - public KeyValuePair? Last() { - SplayTreeNode t = this.root; - if (t == null) { - return null; - } - - while (t.RightChild != null) { - t = t.RightChild; - } - - return new KeyValuePair(t.Key, t.Value); - } - - public KeyValuePair LastOrDefault() { - SplayTreeNode t = this.root; - if (t == null) { - return new KeyValuePair(default(TKey), default(TValue)); - } - - while (t.RightChild != null) { - t = t.RightChild; - } - - return new KeyValuePair(t.Key, t.Value); - } - - void Splay(TKey key) { - SplayTreeNode l, r, t, y, header; - l = r = header = new SplayTreeNode(default(TKey), default(TValue)); - t = this.root; - while (true) { - var c = key.CompareTo(t.Key); - if (c < 0) { - if (t.LeftChild == null) { - break; - } - - if (key.CompareTo(t.LeftChild.Key) < 0) { - y = t.LeftChild; - t.LeftChild = y.RightChild; - y.RightChild = t; - t = y; - if (t.LeftChild == null) { - break; - } - } - - r.LeftChild = t; - r = t; - t = t.LeftChild; - } - else if (c > 0) { - if (t.RightChild == null) { - break; - } - - if (key.CompareTo(t.RightChild.Key) > 0) { - y = t.RightChild; - t.RightChild = y.LeftChild; - y.LeftChild = t; - t = y; - if (t.RightChild == null) { - break; - } - } - - l.RightChild = t; - l = t; - t = t.RightChild; - } - else { - break; - } - } - - l.RightChild = t.LeftChild; - r.LeftChild = t.RightChild; - t.LeftChild = header.RightChild; - t.RightChild = header.LeftChild; - this.root = t; - } - - public bool Remove(TKey key) { - if (this.count == 0) { - return false; - } - - this.Splay(key); - - if (key.CompareTo(this.root.Key) != 0) { - return false; - } - - if (this.root.LeftChild == null) { - this.root = this.root.RightChild; - } - else { - var swap = this.root.RightChild; - this.root = this.root.LeftChild; - this.Splay(key); - this.root.RightChild = swap; - } - - this.version++; - this.count--; - return true; - } - - public bool TryGetValue(TKey key, out TValue value) { - if (this.count == 0) { - value = default(TValue); - return false; - } - - this.Splay(key); - if (key.CompareTo(this.root.Key) != 0) { - value = default(TValue); - return false; - } - - value = this.root.Value; - return true; - } - - public TValue this[TKey key] { - get { - if (this.count == 0) { - throw new KeyNotFoundException("The key was not found in the tree."); - } - - this.Splay(key); - if (key.CompareTo(this.root.Key) != 0) { - throw new KeyNotFoundException("The key was not found in the tree."); - } - - return this.root.Value; - } - - set { this.Set(key, value, throwOnExisting: false); } - } - - public int Count { - get { return this.count; } - } - - public bool IsReadOnly { - get { return false; } - } - - public bool Remove(KeyValuePair item) { - if (this.count == 0) { - return false; - } - - this.Splay(item.Key); - - if (item.Key.CompareTo(this.root.Key) == 0 && (ReferenceEquals(this.root.Value, item.Value) || - (!ReferenceEquals(item.Value, null) && - item.Value.Equals(this.root.Value)))) { - return false; - } - - if (this.root.LeftChild == null) { - this.root = this.root.RightChild; - } - else { - var swap = this.root.RightChild; - this.root = this.root.LeftChild; - this.Splay(item.Key); - this.root.RightChild = swap; - } - - this.version++; - this.count--; - return true; - } - - public void Trim(int depth) { - if (depth < 0) { - throw new ArgumentOutOfRangeException("depth", "The trim depth must not be negative."); - } - - if (this.count == 0) { - return; - } - - if (depth == 0) { - this.Clear(); - } - else { - var prevCount = this.count; - this.count = this.Trim(this.root, depth - 1); - if (prevCount != this.count) { - this.version++; - } - } - } - - int Trim(SplayTreeNode node, int depth) { - if (depth == 0) { - node.LeftChild = null; - node.RightChild = null; - return 1; - } - else { - int count = 1; - - if (node.LeftChild != null) { - count += this.Trim(node.LeftChild, depth - 1); - } - - if (node.RightChild != null) { - count += this.Trim(node.RightChild, depth - 1); - } - - return count; - } - } - - public ICollection Keys { - get { return new TiedList(this, this.version, this.AsList(node => node.Key)); } - } - - public ICollection Values { - get { return new TiedList(this, this.version, this.AsList(node => node.Value)); } - } - - public void CopyTo(KeyValuePair[] array, int arrayIndex) { - this.AsList(node => new KeyValuePair(node.Key, node.Value)).CopyTo(array, arrayIndex); - } - - public IEnumerator> GetEnumerator() { - return new TiedList>(this, this.version, - this.AsList(node => new KeyValuePair(node.Key, node.Value))).GetEnumerator(); - } - - IList AsList(Func selector) { - if (this.root == null) { - return new TEnumerator[0]; - } - - var result = new List(this.count); - this.PopulateList(this.root, result, selector); - return result; - } - - void PopulateList(SplayTreeNode node, List list, - Func selector) { - if (node.LeftChild != null) { - this.PopulateList(node.LeftChild, list, selector); - } - - list.Add(selector(node)); - if (node.RightChild != null) { - this.PopulateList(node.RightChild, list, selector); - } - } - - IEnumerator IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - - sealed class SplayTreeNode { - public readonly TKey Key; - - public TValue Value; - public SplayTreeNode LeftChild; - public SplayTreeNode RightChild; - - public SplayTreeNode(TKey key, TValue value) { - this.Key = key; - this.Value = value; - } - } - - sealed class TiedList : IList { - readonly SplayTree tree; - readonly int version; - readonly IList backingList; - - public TiedList(SplayTree tree, int version, IList backingList) { - if (tree == null) { - throw new ArgumentNullException("tree"); - } - - if (backingList == null) { - throw new ArgumentNullException("backingList"); - } - - this.tree = tree; - this.version = version; - this.backingList = backingList; - } - - public int IndexOf(T item) { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - return this.backingList.IndexOf(item); - } - - public void Insert(int index, T item) { - throw new NotSupportedException(); - } - - public void RemoveAt(int index) { - throw new NotSupportedException(); - } - - public T this[int index] { - get { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - return this.backingList[index]; - } - set { throw new NotSupportedException(); } - } - - public void Add(T item) { - throw new NotSupportedException(); - } - - public void Clear() { - throw new NotSupportedException(); - } - - public bool Contains(T item) { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - return this.backingList.Contains(item); - } - - public void CopyTo(T[] array, int arrayIndex) { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - this.backingList.CopyTo(array, arrayIndex); - } - - public int Count { - get { return this.tree.count; } - } - - public bool IsReadOnly { - get { return true; } - } - - public bool Remove(T item) { - throw new NotSupportedException(); - } - - public IEnumerator GetEnumerator() { - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - - foreach (var item in this.backingList) { - yield return item; - if (this.tree.version != this.version) { - throw new InvalidOperationException("The collection has been modified."); - } - } - } - - IEnumerator IEnumerable.GetEnumerator() { - return this.GetEnumerator(); - } - } - } } \ No newline at end of file diff --git a/Runtime/widgets/list_wheel_scroll_view.cs b/Runtime/widgets/list_wheel_scroll_view.cs index bb050600..9aae4085 100644 --- a/Runtime/widgets/list_wheel_scroll_view.cs +++ b/Runtime/widgets/list_wheel_scroll_view.cs @@ -6,6 +6,7 @@ using Unity.UIWidgets.painting; using Unity.UIWidgets.physics; using Unity.UIWidgets.rendering; +using Unity.UIWidgets.Runtime.external; using Unity.UIWidgets.scheduler; using Unity.UIWidgets.ui; using UnityEngine; diff --git a/Runtime/widgets/sliver.cs b/Runtime/widgets/sliver.cs index cbd9bad0..8a52d4eb 100644 --- a/Runtime/widgets/sliver.cs +++ b/Runtime/widgets/sliver.cs @@ -5,6 +5,7 @@ using Unity.UIWidgets.foundation; using Unity.UIWidgets.painting; using Unity.UIWidgets.rendering; +using Unity.UIWidgets.Runtime.external; using Unity.UIWidgets.ui; namespace Unity.UIWidgets.widgets { From 91cd837fdf83eb140c9ee8186f98642851ed2a73 Mon Sep 17 00:00:00 2001 From: "xingwei.zhu" Date: Tue, 19 Nov 2019 18:39:31 +0800 Subject: [PATCH 07/28] fix aa shapehint bug when shapeHint doesn't update when rrect -> non-rrect changed --- Runtime/ui/renderer/common/geometry/path/path.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Runtime/ui/renderer/common/geometry/path/path.cs b/Runtime/ui/renderer/common/geometry/path/path.cs index 19141c9b..771e918a 100644 --- a/Runtime/ui/renderer/common/geometry/path/path.cs +++ b/Runtime/ui/renderer/common/geometry/path/path.cs @@ -30,10 +30,8 @@ void _updateRRectFlag(bool isNaiveRRect, uiPathShapeHint shapeHint = uiPathShape return; } this._isNaiveRRect = isNaiveRRect && this._hasOnlyMoveTos(); - if (this._isNaiveRRect) { - this._shapeHint = shapeHint; - this._rRectCorner = corner; - } + this._shapeHint = shapeHint; + this._rRectCorner = corner; } bool _hasOnlyMoveTos() { From 48065735dac6d31799dc38a234e76a1bc088124a Mon Sep 17 00:00:00 2001 From: "xingwei.zhu" Date: Tue, 19 Nov 2019 18:46:57 +0800 Subject: [PATCH 08/28] fix aa shapehint bug when shapeHint doesn't update when rrect -> non-rrect changed 2 --- Runtime/ui/renderer/common/geometry/path/path.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Runtime/ui/renderer/common/geometry/path/path.cs b/Runtime/ui/renderer/common/geometry/path/path.cs index 771e918a..adcac101 100644 --- a/Runtime/ui/renderer/common/geometry/path/path.cs +++ b/Runtime/ui/renderer/common/geometry/path/path.cs @@ -78,6 +78,8 @@ public override void clear() { this.needCache = false; this.pathKey = 0; this._isNaiveRRect = false; + this._shapeHint = uiPathShapeHint.Other; + this._rRectCorner = 0; } void _reset() { From f941f660bd9cb896109b610980b31a8c08a4323c Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 13:43:08 +0800 Subject: [PATCH 09/28] Add RTree. --- Runtime/external/RTree.cs | 496 +++++++++++++++++++++++++++++++++ Runtime/external/RTree.cs.meta | 11 + Runtime/ui/geometry.cs | 8 + 3 files changed, 515 insertions(+) create mode 100644 Runtime/external/RTree.cs create mode 100644 Runtime/external/RTree.cs.meta diff --git a/Runtime/external/RTree.cs b/Runtime/external/RTree.cs new file mode 100644 index 00000000..5b16f172 --- /dev/null +++ b/Runtime/external/RTree.cs @@ -0,0 +1,496 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Unity.UIWidgets.ui; + +namespace Unity.UIWidgets.Runtime.external +{ + public interface ISpatialData + { + ref readonly Rect Rect { get; } + } + + + /// + /// Non-generic class to produce instances of the generic class, + /// optionally using type inference. + /// + public static class ProjectionComparer + { + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// + /// Type parameter for the elements to be compared + /// Type parameter for the keys to be compared, after being projected from the elements + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create(Func projection) + { + return new ProjectionComparer(projection); + } + + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// The ignored parameter is solely present to aid type inference. + /// + /// Type parameter for the elements to be compared + /// Type parameter for the keys to be compared, after being projected from the elements + /// Value is ignored - type may be used by type inference + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create + (TSource ignored, + Func projection) + { + return new ProjectionComparer(projection); + } + + } + + /// + /// Class generic in the source only to produce instances of the + /// doubly generic class, optionally using type inference. + /// + public static class ProjectionComparer + { + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// + /// Type parameter for the keys to be compared, after being projected from the elements + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create(Func projection) + { + return new ProjectionComparer(projection); + } + } + + /// + /// Comparer which projects each element of the comparison to a key, and then compares + /// those keys using the specified (or default) comparer for the key type. + /// + /// Type of elements which this comparer will be asked to compare + /// Type of the key projected from the element + public class ProjectionComparer : IComparer + { + readonly Func projection; + readonly IComparer comparer; + + /// + /// Creates a new instance using the specified projection, which must not be null. + /// The default comparer for the projected type is used. + /// + /// Projection to use during comparisons + public ProjectionComparer(Func projection) + : this(projection, null) + { + } + + /// + /// Creates a new instance using the specified projection, which must not be null. + /// + /// Projection to use during comparisons + /// The comparer to use on the keys. May be null, in + /// which case the default comparer will be used. + public ProjectionComparer(Func projection, IComparer comparer) + { + this.comparer = comparer ?? Comparer.Default; + this.projection = projection; + } + + /// + /// Compares x and y by projecting them to keys and then comparing the keys. + /// Null values are not projected; they obey the + /// standard comparer contract such that two null values are equal; any null value is + /// less than any non-null value. + /// + public int Compare(TSource x, TSource y) + { + // Don't want to project from nullity + if (x == null && y == null) + { + return 0; + } + if (x == null) + { + return -1; + } + if (y == null) + { + return 1; + } + return comparer.Compare(projection(x), projection(y)); + } + } + + public partial class RBush + { + public class Node : ISpatialData + { + private Rect _Rect; + + internal Node(List items, int height) + { + this.Height = height; + this.children = items; + ResetRect(); + } + + internal void Add(ISpatialData node) + { + children.Add(node); + _Rect = Rect.expandToInclude(node.Rect); + } + + internal void Remove(ISpatialData node) + { + children.Remove(node); + ResetRect(); + } + + internal void RemoveRange(int index, int count) + { + children.RemoveRange(index, count); + ResetRect(); + } + + internal void ResetRect() + { + _Rect = GetEnclosingRect(children); + } + + internal readonly List children; + + public IReadOnlyList Children => children; + public int Height { get; } + public bool IsLeaf => Height == 1; + public ref readonly Rect Rect => ref _Rect; + } + } + public partial class RBush + { + #region Sort Functions + private static readonly IComparer CompareMinX = + ProjectionComparer.Create(d => d.Rect.left); + private static readonly IComparer CompareMinY = + ProjectionComparer.Create(d => d.Rect.top); + #endregion + + #region Search + private List DoSearch(in Rect boundingBox) + { + if (!Root.Rect.overlaps(boundingBox)) + return new List(); + + var intersections = new List(); + var queue = new Queue(); + queue.Enqueue(Root); + + while (queue.Count != 0) + { + var item = queue.Dequeue(); + if (item.IsLeaf) + { + foreach (T leafChildItem in item.children.Cast()) + if (leafChildItem.Rect.overlaps(boundingBox)) + intersections.Add(leafChildItem); + } + else + { + foreach (var child in item.children.Cast()) + if (child.Rect.overlaps(boundingBox)) + queue.Enqueue(child); + } + } + + return intersections; + } + #endregion + + #region Insert + private List FindCoveringArea(in Rect area, int depth) + { + var path = new List(); + var node = this.Root; + var _area = area; //FIX CS1628 + + while (true) + { + path.Add(node); + if (node.IsLeaf || path.Count == depth) return path; + + node = node.children + .Select(c => new { EnlargedArea = c.Rect.expandToInclude(_area).area, c.Rect.area, Node = c as Node, }) + .OrderBy(x => x.EnlargedArea) + .ThenBy(x => x.area) + .Select(x => x.Node) + .First(); + } + } + + private void Insert(ISpatialData data, int depth) + { + var path = FindCoveringArea(data.Rect, depth); + + var insertNode = path.Last(); + insertNode.Add(data); + + while (--depth >= 0) + { + if (path[depth].children.Count > maxEntries) + { + var newNode = SplitNode(path[depth]); + if (depth == 0) + SplitRoot(newNode); + else + path[depth - 1].Add(newNode); + } + else + path[depth].ResetRect(); + } + } + + #region SplitNode + private void SplitRoot(Node newNode) => + this.Root = new Node(new List { this.Root, newNode }, this.Root.Height + 1); + + private Node SplitNode(Node node) + { + SortChildren(node); + + var splitPoint = GetBestSplitIndex(node.children); + var newChildren = node.children.Skip(splitPoint).ToList(); + node.RemoveRange(splitPoint, node.children.Count - splitPoint); + return new Node(newChildren, node.Height); + } + + #region SortChildren + private void SortChildren(Node node) + { + node.children.Sort(CompareMinX); + var splitsByX = GetPotentialSplitMargins(node.children); + node.children.Sort(CompareMinY); + var splitsByY = GetPotentialSplitMargins(node.children); + + if (splitsByX < splitsByY) + node.children.Sort(CompareMinX); + } + + private double GetPotentialSplitMargins(List children) => + GetPotentialEnclosingMargins(children) + + GetPotentialEnclosingMargins(children.AsEnumerable().Reverse().ToList()); + + private double GetPotentialEnclosingMargins(List children) + { + var rect = Rect.zero; + int i = 0; + for (; i < minEntries; i++) + { + rect = rect.expandToInclude(children[i].Rect); + } + + var totalMargin = rect.margin; + for (; i < children.Count - minEntries; i++) + { + rect = rect.expandToInclude(children[i].Rect); + totalMargin += rect.margin; + } + + return totalMargin; + } + #endregion + + private int GetBestSplitIndex(List children) + { + return Enumerable.Range(minEntries, children.Count - minEntries) + .Select(i => + { + var leftRect = GetEnclosingRect(children.Take(i)); + var rightRect = GetEnclosingRect(children.Skip(i)); + + var overlap = leftRect.intersect(rightRect).area; + var totalArea = leftRect.area + rightRect.area; + return new { i, overlap, totalArea }; + }) + .OrderBy(x => x.overlap) + .ThenBy(x => x.totalArea) + .Select(x => x.i) + .First(); + } + #endregion + #endregion + + #region BuildTree + private Node BuildTree(List data) + { + var treeHeight = GetDepth(data.Count); + var rootMaxEntries = (int)Math.Ceiling(data.Count / Math.Pow(this.maxEntries, treeHeight - 1)); + return BuildNodes(data, 0, data.Count - 1, treeHeight, rootMaxEntries); + } + + private int GetDepth(int numNodes) => + (int)Math.Ceiling(Math.Log(numNodes) / Math.Log(this.maxEntries)); + + private Node BuildNodes(List data, int left, int right, int height, int maxEntries) + { + var num = right - left + 1; + if (num <= maxEntries) + { + return height == 1 + ? new Node(data.GetRange(left, num), height) + : new Node( + new List + { + BuildNodes(data, left, right, height - 1, this.maxEntries), + }, + height); + } + + data.Sort(left, num, CompareMinX); + + var nodeSize = (num + (maxEntries - 1)) / maxEntries; + var subSortLength = nodeSize * (int)Math.Ceiling(Math.Sqrt(maxEntries)); + + var children = new List(maxEntries); + for (int subCounter = left; subCounter <= right; subCounter += subSortLength) + { + var subRight = Math.Min(subCounter + subSortLength - 1, right); + data.Sort(subCounter, subRight - subCounter + 1, CompareMinY); + + for (int nodeCounter = subCounter; nodeCounter <= subRight; nodeCounter += nodeSize) + { + children.Add( + BuildNodes( + data, + nodeCounter, + Math.Min(nodeCounter + nodeSize - 1, subRight), + height - 1, + this.maxEntries)); + } + } + + return new Node(children, height); + } + #endregion + + private static Rect GetEnclosingRect(IEnumerable items) + { + var rect = Rect.zero; + foreach (var data in items) + { + rect = rect.expandToInclude(data.Rect); + } + return rect; + } + + private List GetAllChildren(List list, Node n) + { + if (n.IsLeaf) + { + list.AddRange( + n.children.Cast()); + } + else + { + foreach (var node in n.children.Cast()) + GetAllChildren(list, node); + } + + return list; + } + + } + public partial class RBush where T : ISpatialData + { + private const int DefaultMaxEntries = 9; + private const int MinimumMaxEntries = 4; + private const int MinimumMinEntries = 2; + private const double DefaultFillFactor = 0.4; + + private readonly EqualityComparer comparer; + private readonly int maxEntries; + private readonly int minEntries; + + public Node Root { get; private set; } + public ref readonly Rect Rect => ref Root.Rect; + + public RBush() : this(DefaultMaxEntries) { } + public RBush(int maxEntries) + : this(maxEntries, EqualityComparer.Default) { } + public RBush(int maxEntries, EqualityComparer comparer) + { + this.comparer = comparer; + this.maxEntries = Math.Max(MinimumMaxEntries, maxEntries); + this.minEntries = Math.Max(MinimumMinEntries, (int)Math.Ceiling(this.maxEntries * DefaultFillFactor)); + + this.Clear(); + } + + public int Count { get; private set; } + + public void Clear() + { + this.Root = new Node(new List(), 1); + this.Count = 0; + } + + public IReadOnlyList Search() => GetAllChildren(new List(), this.Root); + + public IReadOnlyList Search(in Rect boundingBox) => + DoSearch(boundingBox); + + public void Insert(T item) + { + Insert(item, this.Root.Height); + this.Count++; + } + + public void BulkLoad(IEnumerable items) + { + var data = items.Cast().ToList(); + if (data.Count == 0) return; + + if (this.Root.IsLeaf && + this.Root.children.Count + data.Count < maxEntries) + { + foreach (var i in data) + Insert((T)i); + return; + } + + if (data.Count < this.minEntries) + { + foreach (var i in data) + Insert((T)i); + return; + } + + var dataRoot = BuildTree(data); + this.Count += data.Count; + + if (this.Root.children.Count == 0) + this.Root = dataRoot; + else if (this.Root.Height == dataRoot.Height) + { + if (this.Root.children.Count + dataRoot.children.Count <= this.maxEntries) + { + foreach (var isd in dataRoot.children) + this.Root.Add(isd); + } + else + SplitRoot(dataRoot); + } + else + { + if (this.Root.Height < dataRoot.Height) + { + var tmp = this.Root; + this.Root = dataRoot; + dataRoot = tmp; + } + + this.Insert(dataRoot, this.Root.Height - dataRoot.Height); + } + } + } +} \ No newline at end of file diff --git a/Runtime/external/RTree.cs.meta b/Runtime/external/RTree.cs.meta new file mode 100644 index 00000000..30f3da65 --- /dev/null +++ b/Runtime/external/RTree.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0a93ee633aad3dc4395851f56b650cc2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ui/geometry.cs b/Runtime/ui/geometry.cs index 9bea0fbb..c009d238 100644 --- a/Runtime/ui/geometry.cs +++ b/Runtime/ui/geometry.cs @@ -541,6 +541,14 @@ public Size size { get { return new Size(this.width, this.height); } } + public float area { + get { return this.width * this.height; } + } + + public float margin { + get { return this.width * this.height; } + } + public static readonly Rect zero = new Rect(0, 0, 0, 0); public static readonly Rect one = new Rect(0, 0, 1, 1); From 0bf499f04fbf3f7db51801f0ded47ae4c7d9fe87 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 13:47:22 +0800 Subject: [PATCH 10/28] Refactor RTree. --- Runtime/external/RTree.cs | 977 +++++++++++++++++++------------------- 1 file changed, 493 insertions(+), 484 deletions(-) diff --git a/Runtime/external/RTree.cs b/Runtime/external/RTree.cs index 5b16f172..42c0a09a 100644 --- a/Runtime/external/RTree.cs +++ b/Runtime/external/RTree.cs @@ -9,488 +9,497 @@ public interface ISpatialData { ref readonly Rect Rect { get; } } - - - /// - /// Non-generic class to produce instances of the generic class, - /// optionally using type inference. - /// - public static class ProjectionComparer - { - /// - /// Creates an instance of ProjectionComparer using the specified projection. - /// - /// Type parameter for the elements to be compared - /// Type parameter for the keys to be compared, after being projected from the elements - /// Projection to use when determining the key of an element - /// A comparer which will compare elements by projecting each element to its key, and comparing keys - public static ProjectionComparer Create(Func projection) - { - return new ProjectionComparer(projection); - } - - /// - /// Creates an instance of ProjectionComparer using the specified projection. - /// The ignored parameter is solely present to aid type inference. - /// - /// Type parameter for the elements to be compared - /// Type parameter for the keys to be compared, after being projected from the elements - /// Value is ignored - type may be used by type inference - /// Projection to use when determining the key of an element - /// A comparer which will compare elements by projecting each element to its key, and comparing keys - public static ProjectionComparer Create - (TSource ignored, - Func projection) - { - return new ProjectionComparer(projection); - } - - } - - /// - /// Class generic in the source only to produce instances of the - /// doubly generic class, optionally using type inference. - /// - public static class ProjectionComparer - { - /// - /// Creates an instance of ProjectionComparer using the specified projection. - /// - /// Type parameter for the keys to be compared, after being projected from the elements - /// Projection to use when determining the key of an element - /// A comparer which will compare elements by projecting each element to its key, and comparing keys - public static ProjectionComparer Create(Func projection) - { - return new ProjectionComparer(projection); - } - } - - /// - /// Comparer which projects each element of the comparison to a key, and then compares - /// those keys using the specified (or default) comparer for the key type. - /// - /// Type of elements which this comparer will be asked to compare - /// Type of the key projected from the element - public class ProjectionComparer : IComparer - { - readonly Func projection; - readonly IComparer comparer; - - /// - /// Creates a new instance using the specified projection, which must not be null. - /// The default comparer for the projected type is used. - /// - /// Projection to use during comparisons - public ProjectionComparer(Func projection) - : this(projection, null) - { - } - - /// - /// Creates a new instance using the specified projection, which must not be null. - /// - /// Projection to use during comparisons - /// The comparer to use on the keys. May be null, in - /// which case the default comparer will be used. - public ProjectionComparer(Func projection, IComparer comparer) - { - this.comparer = comparer ?? Comparer.Default; - this.projection = projection; - } - - /// - /// Compares x and y by projecting them to keys and then comparing the keys. - /// Null values are not projected; they obey the - /// standard comparer contract such that two null values are equal; any null value is - /// less than any non-null value. - /// - public int Compare(TSource x, TSource y) - { - // Don't want to project from nullity - if (x == null && y == null) - { - return 0; - } - if (x == null) - { - return -1; - } - if (y == null) - { - return 1; - } - return comparer.Compare(projection(x), projection(y)); - } - } - - public partial class RBush - { - public class Node : ISpatialData - { - private Rect _Rect; - - internal Node(List items, int height) - { - this.Height = height; - this.children = items; - ResetRect(); - } - - internal void Add(ISpatialData node) - { - children.Add(node); - _Rect = Rect.expandToInclude(node.Rect); - } - - internal void Remove(ISpatialData node) - { - children.Remove(node); - ResetRect(); - } - - internal void RemoveRange(int index, int count) - { - children.RemoveRange(index, count); - ResetRect(); - } - - internal void ResetRect() - { - _Rect = GetEnclosingRect(children); - } - - internal readonly List children; - - public IReadOnlyList Children => children; - public int Height { get; } - public bool IsLeaf => Height == 1; - public ref readonly Rect Rect => ref _Rect; - } - } - public partial class RBush - { - #region Sort Functions - private static readonly IComparer CompareMinX = - ProjectionComparer.Create(d => d.Rect.left); - private static readonly IComparer CompareMinY = - ProjectionComparer.Create(d => d.Rect.top); - #endregion - - #region Search - private List DoSearch(in Rect boundingBox) - { - if (!Root.Rect.overlaps(boundingBox)) - return new List(); - - var intersections = new List(); - var queue = new Queue(); - queue.Enqueue(Root); - - while (queue.Count != 0) - { - var item = queue.Dequeue(); - if (item.IsLeaf) - { - foreach (T leafChildItem in item.children.Cast()) - if (leafChildItem.Rect.overlaps(boundingBox)) - intersections.Add(leafChildItem); - } - else - { - foreach (var child in item.children.Cast()) - if (child.Rect.overlaps(boundingBox)) - queue.Enqueue(child); - } - } - - return intersections; - } - #endregion - - #region Insert - private List FindCoveringArea(in Rect area, int depth) - { - var path = new List(); - var node = this.Root; - var _area = area; //FIX CS1628 - - while (true) - { - path.Add(node); - if (node.IsLeaf || path.Count == depth) return path; - - node = node.children - .Select(c => new { EnlargedArea = c.Rect.expandToInclude(_area).area, c.Rect.area, Node = c as Node, }) - .OrderBy(x => x.EnlargedArea) - .ThenBy(x => x.area) - .Select(x => x.Node) - .First(); - } - } - - private void Insert(ISpatialData data, int depth) - { - var path = FindCoveringArea(data.Rect, depth); - - var insertNode = path.Last(); - insertNode.Add(data); - - while (--depth >= 0) - { - if (path[depth].children.Count > maxEntries) - { - var newNode = SplitNode(path[depth]); - if (depth == 0) - SplitRoot(newNode); - else - path[depth - 1].Add(newNode); - } - else - path[depth].ResetRect(); - } - } - - #region SplitNode - private void SplitRoot(Node newNode) => - this.Root = new Node(new List { this.Root, newNode }, this.Root.Height + 1); - - private Node SplitNode(Node node) - { - SortChildren(node); - - var splitPoint = GetBestSplitIndex(node.children); - var newChildren = node.children.Skip(splitPoint).ToList(); - node.RemoveRange(splitPoint, node.children.Count - splitPoint); - return new Node(newChildren, node.Height); - } - - #region SortChildren - private void SortChildren(Node node) - { - node.children.Sort(CompareMinX); - var splitsByX = GetPotentialSplitMargins(node.children); - node.children.Sort(CompareMinY); - var splitsByY = GetPotentialSplitMargins(node.children); - - if (splitsByX < splitsByY) - node.children.Sort(CompareMinX); - } - - private double GetPotentialSplitMargins(List children) => - GetPotentialEnclosingMargins(children) + - GetPotentialEnclosingMargins(children.AsEnumerable().Reverse().ToList()); - - private double GetPotentialEnclosingMargins(List children) - { - var rect = Rect.zero; - int i = 0; - for (; i < minEntries; i++) - { - rect = rect.expandToInclude(children[i].Rect); - } - - var totalMargin = rect.margin; - for (; i < children.Count - minEntries; i++) - { - rect = rect.expandToInclude(children[i].Rect); - totalMargin += rect.margin; - } - - return totalMargin; - } - #endregion - - private int GetBestSplitIndex(List children) - { - return Enumerable.Range(minEntries, children.Count - minEntries) - .Select(i => - { - var leftRect = GetEnclosingRect(children.Take(i)); - var rightRect = GetEnclosingRect(children.Skip(i)); - - var overlap = leftRect.intersect(rightRect).area; - var totalArea = leftRect.area + rightRect.area; - return new { i, overlap, totalArea }; - }) - .OrderBy(x => x.overlap) - .ThenBy(x => x.totalArea) - .Select(x => x.i) - .First(); - } - #endregion - #endregion - - #region BuildTree - private Node BuildTree(List data) - { - var treeHeight = GetDepth(data.Count); - var rootMaxEntries = (int)Math.Ceiling(data.Count / Math.Pow(this.maxEntries, treeHeight - 1)); - return BuildNodes(data, 0, data.Count - 1, treeHeight, rootMaxEntries); - } - - private int GetDepth(int numNodes) => - (int)Math.Ceiling(Math.Log(numNodes) / Math.Log(this.maxEntries)); - - private Node BuildNodes(List data, int left, int right, int height, int maxEntries) - { - var num = right - left + 1; - if (num <= maxEntries) - { - return height == 1 - ? new Node(data.GetRange(left, num), height) - : new Node( - new List - { - BuildNodes(data, left, right, height - 1, this.maxEntries), - }, - height); - } - - data.Sort(left, num, CompareMinX); - - var nodeSize = (num + (maxEntries - 1)) / maxEntries; - var subSortLength = nodeSize * (int)Math.Ceiling(Math.Sqrt(maxEntries)); - - var children = new List(maxEntries); - for (int subCounter = left; subCounter <= right; subCounter += subSortLength) - { - var subRight = Math.Min(subCounter + subSortLength - 1, right); - data.Sort(subCounter, subRight - subCounter + 1, CompareMinY); - - for (int nodeCounter = subCounter; nodeCounter <= subRight; nodeCounter += nodeSize) - { - children.Add( - BuildNodes( - data, - nodeCounter, - Math.Min(nodeCounter + nodeSize - 1, subRight), - height - 1, - this.maxEntries)); - } - } - - return new Node(children, height); - } - #endregion - - private static Rect GetEnclosingRect(IEnumerable items) - { - var rect = Rect.zero; - foreach (var data in items) - { - rect = rect.expandToInclude(data.Rect); - } - return rect; - } - - private List GetAllChildren(List list, Node n) - { - if (n.IsLeaf) - { - list.AddRange( - n.children.Cast()); - } - else - { - foreach (var node in n.children.Cast()) - GetAllChildren(list, node); - } - - return list; - } - - } - public partial class RBush where T : ISpatialData - { - private const int DefaultMaxEntries = 9; - private const int MinimumMaxEntries = 4; - private const int MinimumMinEntries = 2; - private const double DefaultFillFactor = 0.4; - - private readonly EqualityComparer comparer; - private readonly int maxEntries; - private readonly int minEntries; - - public Node Root { get; private set; } - public ref readonly Rect Rect => ref Root.Rect; - - public RBush() : this(DefaultMaxEntries) { } - public RBush(int maxEntries) - : this(maxEntries, EqualityComparer.Default) { } - public RBush(int maxEntries, EqualityComparer comparer) - { - this.comparer = comparer; - this.maxEntries = Math.Max(MinimumMaxEntries, maxEntries); - this.minEntries = Math.Max(MinimumMinEntries, (int)Math.Ceiling(this.maxEntries * DefaultFillFactor)); - - this.Clear(); - } - - public int Count { get; private set; } - - public void Clear() - { - this.Root = new Node(new List(), 1); - this.Count = 0; - } - - public IReadOnlyList Search() => GetAllChildren(new List(), this.Root); - - public IReadOnlyList Search(in Rect boundingBox) => - DoSearch(boundingBox); - - public void Insert(T item) - { - Insert(item, this.Root.Height); - this.Count++; - } - - public void BulkLoad(IEnumerable items) - { - var data = items.Cast().ToList(); - if (data.Count == 0) return; - - if (this.Root.IsLeaf && - this.Root.children.Count + data.Count < maxEntries) - { - foreach (var i in data) - Insert((T)i); - return; - } - - if (data.Count < this.minEntries) - { - foreach (var i in data) - Insert((T)i); - return; - } - - var dataRoot = BuildTree(data); - this.Count += data.Count; - - if (this.Root.children.Count == 0) - this.Root = dataRoot; - else if (this.Root.Height == dataRoot.Height) - { - if (this.Root.children.Count + dataRoot.children.Count <= this.maxEntries) - { - foreach (var isd in dataRoot.children) - this.Root.Add(isd); - } - else - SplitRoot(dataRoot); - } - else - { - if (this.Root.Height < dataRoot.Height) - { - var tmp = this.Root; - this.Root = dataRoot; - dataRoot = tmp; - } - - this.Insert(dataRoot, this.Root.Height - dataRoot.Height); - } - } - } + + + /// + /// Non-generic class to produce instances of the generic class, + /// optionally using type inference. + /// + public static class ProjectionComparer + { + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// + /// Type parameter for the elements to be compared + /// Type parameter for the keys to be compared, after being projected from the elements + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create(Func projection) + { + return new ProjectionComparer(projection); + } + + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// The ignored parameter is solely present to aid type inference. + /// + /// Type parameter for the elements to be compared + /// Type parameter for the keys to be compared, after being projected from the elements + /// Value is ignored - type may be used by type inference + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create + (TSource ignored, + Func projection) + { + return new ProjectionComparer(projection); + } + } + + /// + /// Class generic in the source only to produce instances of the + /// doubly generic class, optionally using type inference. + /// + public static class ProjectionComparer + { + /// + /// Creates an instance of ProjectionComparer using the specified projection. + /// + /// Type parameter for the keys to be compared, after being projected from the elements + /// Projection to use when determining the key of an element + /// A comparer which will compare elements by projecting each element to its key, and comparing keys + public static ProjectionComparer Create(Func projection) + { + return new ProjectionComparer(projection); + } + } + + /// + /// Comparer which projects each element of the comparison to a key, and then compares + /// those keys using the specified (or default) comparer for the key type. + /// + /// Type of elements which this comparer will be asked to compare + /// Type of the key projected from the element + public class ProjectionComparer : IComparer + { + private readonly IComparer comparer; + private readonly Func projection; + + /// + /// Creates a new instance using the specified projection, which must not be null. + /// The default comparer for the projected type is used. + /// + /// Projection to use during comparisons + public ProjectionComparer(Func projection) + : this(projection, null) + { + } + + /// + /// Creates a new instance using the specified projection, which must not be null. + /// + /// Projection to use during comparisons + /// + /// The comparer to use on the keys. May be null, in + /// which case the default comparer will be used. + /// + public ProjectionComparer(Func projection, IComparer comparer) + { + this.comparer = comparer ?? Comparer.Default; + this.projection = projection; + } + + /// + /// Compares x and y by projecting them to keys and then comparing the keys. + /// Null values are not projected; they obey the + /// standard comparer contract such that two null values are equal; any null value is + /// less than any non-null value. + /// + public int Compare(TSource x, TSource y) + { + // Don't want to project from nullity + if (x == null && y == null) return 0; + if (x == null) return -1; + if (y == null) return 1; + return comparer.Compare(projection(x), projection(y)); + } + } + + public partial class RTree + { + public class RTreeNode : ISpatialData + { + internal readonly List children; + private Rect _Rect; + + internal RTreeNode(List items, int height) + { + Height = height; + children = items; + ResetRect(); + } + + public IReadOnlyList Children => children; + public int Height { get; } + public bool IsLeaf => Height == 1; + public ref readonly Rect Rect => ref _Rect; + + internal void Add(ISpatialData node) + { + children.Add(node); + _Rect = Rect.expandToInclude(node.Rect); + } + + internal void Remove(ISpatialData node) + { + children.Remove(node); + ResetRect(); + } + + internal void RemoveRange(int index, int count) + { + children.RemoveRange(index, count); + ResetRect(); + } + + internal void ResetRect() + { + _Rect = GetEnclosingRect(children); + } + } + } + + public partial class RTree + { + #region Search + + private List DoSearch(in Rect boundingBox) + { + if (!Root.Rect.overlaps(boundingBox)) + return new List(); + + var intersections = new List(); + var queue = new Queue(); + queue.Enqueue(Root); + + while (queue.Count != 0) + { + var item = queue.Dequeue(); + if (item.IsLeaf) + { + foreach (var leafChildItem in item.children.Cast()) + if (leafChildItem.Rect.overlaps(boundingBox)) + intersections.Add(leafChildItem); + } + else + { + foreach (var child in item.children.Cast()) + if (child.Rect.overlaps(boundingBox)) + queue.Enqueue(child); + } + } + + return intersections; + } + + #endregion + + private static Rect GetEnclosingRect(IEnumerable items) + { + var rect = Rect.zero; + foreach (var data in items) rect = rect.expandToInclude(data.Rect); + return rect; + } + + private List GetAllChildren(List list, RTreeNode n) + { + if (n.IsLeaf) + list.AddRange( + n.children.Cast()); + else + foreach (var node in n.children.Cast()) + GetAllChildren(list, node); + + return list; + } + + #region Sort Functions + + private static readonly IComparer CompareMinX = + ProjectionComparer.Create(d => d.Rect.left); + + private static readonly IComparer CompareMinY = + ProjectionComparer.Create(d => d.Rect.top); + + #endregion + + #region Insert + + private List FindCoveringArea(in Rect area, int depth) + { + var path = new List(); + var node = Root; + var _area = area; //FIX CS1628 + + while (true) + { + path.Add(node); + if (node.IsLeaf || path.Count == depth) return path; + + node = node.children + .Select(c => new + {EnlargedArea = c.Rect.expandToInclude(_area).area, c.Rect.area, Node = c as RTreeNode}) + .OrderBy(x => x.EnlargedArea) + .ThenBy(x => x.area) + .Select(x => x.Node) + .First(); + } + } + + private void Insert(ISpatialData data, int depth) + { + var path = FindCoveringArea(data.Rect, depth); + + var insertNode = path.Last(); + insertNode.Add(data); + + while (--depth >= 0) + if (path[depth].children.Count > maxEntries) + { + var newNode = SplitNode(path[depth]); + if (depth == 0) + SplitRoot(newNode); + else + path[depth - 1].Add(newNode); + } + else + { + path[depth].ResetRect(); + } + } + + #region SplitNode + + private void SplitRoot(RTreeNode newRTreeNode) + { + Root = new RTreeNode(new List {Root, newRTreeNode}, Root.Height + 1); + } + + private RTreeNode SplitNode(RTreeNode rTreeNode) + { + SortChildren(rTreeNode); + + var splitPoint = GetBestSplitIndex(rTreeNode.children); + var newChildren = rTreeNode.children.Skip(splitPoint).ToList(); + rTreeNode.RemoveRange(splitPoint, rTreeNode.children.Count - splitPoint); + return new RTreeNode(newChildren, rTreeNode.Height); + } + + #region SortChildren + + private void SortChildren(RTreeNode rTreeNode) + { + rTreeNode.children.Sort(CompareMinX); + var splitsByX = GetPotentialSplitMargins(rTreeNode.children); + rTreeNode.children.Sort(CompareMinY); + var splitsByY = GetPotentialSplitMargins(rTreeNode.children); + + if (splitsByX < splitsByY) + rTreeNode.children.Sort(CompareMinX); + } + + private double GetPotentialSplitMargins(List children) + { + return GetPotentialEnclosingMargins(children) + + GetPotentialEnclosingMargins(children.AsEnumerable().Reverse().ToList()); + } + + private double GetPotentialEnclosingMargins(List children) + { + var rect = Rect.zero; + var i = 0; + for (; i < minEntries; i++) rect = rect.expandToInclude(children[i].Rect); + + var totalMargin = rect.margin; + for (; i < children.Count - minEntries; i++) + { + rect = rect.expandToInclude(children[i].Rect); + totalMargin += rect.margin; + } + + return totalMargin; + } + + #endregion + + private int GetBestSplitIndex(List children) + { + return Enumerable.Range(minEntries, children.Count - minEntries) + .Select(i => + { + var leftRect = GetEnclosingRect(children.Take(i)); + var rightRect = GetEnclosingRect(children.Skip(i)); + + var overlap = leftRect.intersect(rightRect).area; + var totalArea = leftRect.area + rightRect.area; + return new {i, overlap, totalArea}; + }) + .OrderBy(x => x.overlap) + .ThenBy(x => x.totalArea) + .Select(x => x.i) + .First(); + } + + #endregion + + #endregion + + #region BuildTree + + private RTreeNode BuildTree(List data) + { + var treeHeight = GetDepth(data.Count); + var rootMaxEntries = (int) Math.Ceiling(data.Count / Math.Pow(maxEntries, treeHeight - 1)); + return BuildNodes(data, 0, data.Count - 1, treeHeight, rootMaxEntries); + } + + private int GetDepth(int numNodes) + { + return (int) Math.Ceiling(Math.Log(numNodes) / Math.Log(maxEntries)); + } + + private RTreeNode BuildNodes(List data, int left, int right, int height, int maxEntries) + { + var num = right - left + 1; + if (num <= maxEntries) + return height == 1 + ? new RTreeNode(data.GetRange(left, num), height) + : new RTreeNode( + new List + { + BuildNodes(data, left, right, height - 1, this.maxEntries) + }, + height); + + data.Sort(left, num, CompareMinX); + + var nodeSize = (num + (maxEntries - 1)) / maxEntries; + var subSortLength = nodeSize * (int) Math.Ceiling(Math.Sqrt(maxEntries)); + + var children = new List(maxEntries); + for (var subCounter = left; subCounter <= right; subCounter += subSortLength) + { + var subRight = Math.Min(subCounter + subSortLength - 1, right); + data.Sort(subCounter, subRight - subCounter + 1, CompareMinY); + + for (var nodeCounter = subCounter; nodeCounter <= subRight; nodeCounter += nodeSize) + children.Add( + BuildNodes( + data, + nodeCounter, + Math.Min(nodeCounter + nodeSize - 1, subRight), + height - 1, + this.maxEntries)); + } + + return new RTreeNode(children, height); + } + + #endregion + } + + public partial class RTree where T : ISpatialData + { + private const int DefaultMaxEntries = 9; + private const int MinimumMaxEntries = 4; + private const int MinimumMinEntries = 2; + private const double DefaultFillFactor = 0.4; + + private readonly EqualityComparer comparer; + private readonly int maxEntries; + private readonly int minEntries; + + public RTree() : this(DefaultMaxEntries) + { + } + + public RTree(int maxEntries) + : this(maxEntries, EqualityComparer.Default) + { + } + + public RTree(int maxEntries, EqualityComparer comparer) + { + this.comparer = comparer; + this.maxEntries = Math.Max(MinimumMaxEntries, maxEntries); + minEntries = Math.Max(MinimumMinEntries, (int) Math.Ceiling(this.maxEntries * DefaultFillFactor)); + + Clear(); + } + + public RTreeNode Root { get; private set; } + public ref readonly Rect Rect => ref Root.Rect; + + public int Count { get; private set; } + + public void Clear() + { + Root = new RTreeNode(new List(), 1); + Count = 0; + } + + public IReadOnlyList Search() + { + return GetAllChildren(new List(), Root); + } + + public IReadOnlyList Search(in Rect boundingBox) + { + return DoSearch(boundingBox); + } + + public void Insert(T item) + { + Insert(item, Root.Height); + Count++; + } + + public void BulkLoad(IEnumerable items) + { + var data = items.Cast().ToList(); + if (data.Count == 0) return; + + if (Root.IsLeaf && + Root.children.Count + data.Count < maxEntries) + { + foreach (var i in data) + Insert((T) i); + return; + } + + if (data.Count < minEntries) + { + foreach (var i in data) + Insert((T) i); + return; + } + + var dataRoot = BuildTree(data); + Count += data.Count; + + if (Root.children.Count == 0) + { + Root = dataRoot; + } + else if (Root.Height == dataRoot.Height) + { + if (Root.children.Count + dataRoot.children.Count <= maxEntries) + foreach (var isd in dataRoot.children) + Root.Add(isd); + else + SplitRoot(dataRoot); + } + else + { + if (Root.Height < dataRoot.Height) + { + var tmp = Root; + Root = dataRoot; + dataRoot = tmp; + } + + Insert(dataRoot, Root.Height - dataRoot.Height); + } + } + } } \ No newline at end of file From c97b4e4fb4b106cb6eeb1b13abe2a10f7011df9b Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 13:51:34 +0800 Subject: [PATCH 11/28] Refactor RTree. --- Runtime/external/RTree.cs | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/Runtime/external/RTree.cs b/Runtime/external/RTree.cs index 42c0a09a..9226cebd 100644 --- a/Runtime/external/RTree.cs +++ b/Runtime/external/RTree.cs @@ -115,7 +115,7 @@ public int Compare(TSource x, TSource y) } } - public partial class RTree + public class RTree where T : ISpatialData { public class RTreeNode : ISpatialData { @@ -157,10 +157,6 @@ internal void ResetRect() _Rect = GetEnclosingRect(children); } } - } - - public partial class RTree - { #region Search private List DoSearch(in Rect boundingBox) @@ -298,13 +294,13 @@ private void SortChildren(RTreeNode rTreeNode) rTreeNode.children.Sort(CompareMinX); } - private double GetPotentialSplitMargins(List children) + private float GetPotentialSplitMargins(List children) { return GetPotentialEnclosingMargins(children) + GetPotentialEnclosingMargins(children.AsEnumerable().Reverse().ToList()); } - private double GetPotentialEnclosingMargins(List children) + private float GetPotentialEnclosingMargins(List children) { var rect = Rect.zero; var i = 0; @@ -396,14 +392,10 @@ private RTreeNode BuildNodes(List data, int left, int right, int h } #endregion - } - - public partial class RTree where T : ISpatialData - { private const int DefaultMaxEntries = 9; private const int MinimumMaxEntries = 4; private const int MinimumMinEntries = 2; - private const double DefaultFillFactor = 0.4; + private const float DefaultFillFactor = 0.4f; private readonly EqualityComparer comparer; private readonly int maxEntries; From 4802de1eba39c25bb5b7d8e64bcff7c6d6172f59 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 16:24:40 +0800 Subject: [PATCH 12/28] Filter records with r tree. --- Runtime/external/RTree.cs | 73 +++++++++------ Runtime/ui/painting/draw_cmd.cs | 73 ++++++++++++--- Runtime/ui/painting/picture.cs | 31 ++++++- .../cmdbufferCanvas/rendering/canvas_impl.cs | 90 +++++++------------ Runtime/ui/renderer/common/draw_cmd.cs | 69 +++++++++++--- Runtime/ui/renderer/common/geometry/rect.cs | 8 ++ Runtime/ui/renderer/common/picture.cs | 26 +++++- 7 files changed, 254 insertions(+), 116 deletions(-) diff --git a/Runtime/external/RTree.cs b/Runtime/external/RTree.cs index 9226cebd..58a07b12 100644 --- a/Runtime/external/RTree.cs +++ b/Runtime/external/RTree.cs @@ -7,9 +7,26 @@ namespace Unity.UIWidgets.Runtime.external { public interface ISpatialData { - ref readonly Rect Rect { get; } + uiRect bounds { get; } } + public class IndexedRect : ISpatialData + { + private uiRect _bounds; + + public uiRect bounds + { + get { return _bounds; } + } + + public readonly int index; + + public IndexedRect(uiRect bounds, int index) + { + this._bounds = bounds; + this.index = index; + } + } /// /// Non-generic class to produce instances of the generic class, @@ -115,12 +132,18 @@ public int Compare(TSource x, TSource y) } } - public class RTree where T : ISpatialData + public interface BBoxHierarchy where T : ISpatialData + { + IReadOnlyList Search(in uiRect boundingBox); + void BulkLoad(IEnumerable items); + } + + public class RTree : BBoxHierarchy where T : ISpatialData { public class RTreeNode : ISpatialData { internal readonly List children; - private Rect _Rect; + private uiRect _Rect; internal RTreeNode(List items, int height) { @@ -132,12 +155,12 @@ internal RTreeNode(List items, int height) public IReadOnlyList Children => children; public int Height { get; } public bool IsLeaf => Height == 1; - public ref readonly Rect Rect => ref _Rect; + public uiRect bounds => _Rect; internal void Add(ISpatialData node) { children.Add(node); - _Rect = Rect.expandToInclude(node.Rect); + _Rect = bounds.expandToInclude(node.bounds); } internal void Remove(ISpatialData node) @@ -159,9 +182,9 @@ internal void ResetRect() } #region Search - private List DoSearch(in Rect boundingBox) + private List DoSearch(in uiRect boundingBox) { - if (!Root.Rect.overlaps(boundingBox)) + if (!uiRectHelper.overlaps(Root.bounds, boundingBox)) return new List(); var intersections = new List(); @@ -174,13 +197,13 @@ private List DoSearch(in Rect boundingBox) if (item.IsLeaf) { foreach (var leafChildItem in item.children.Cast()) - if (leafChildItem.Rect.overlaps(boundingBox)) + if (uiRectHelper.overlaps(leafChildItem.bounds, boundingBox)) intersections.Add(leafChildItem); } else { foreach (var child in item.children.Cast()) - if (child.Rect.overlaps(boundingBox)) + if (uiRectHelper.overlaps(child.bounds, boundingBox)) queue.Enqueue(child); } } @@ -190,11 +213,11 @@ private List DoSearch(in Rect boundingBox) #endregion - private static Rect GetEnclosingRect(IEnumerable items) + private static uiRect GetEnclosingRect(IEnumerable items) { - var rect = Rect.zero; - foreach (var data in items) rect = rect.expandToInclude(data.Rect); - return rect; + var uiRect = uiRectHelper.zero; + foreach (var data in items) uiRect = uiRect.expandToInclude(data.bounds); + return uiRect; } private List GetAllChildren(List list, RTreeNode n) @@ -212,16 +235,16 @@ private List GetAllChildren(List list, RTreeNode n) #region Sort Functions private static readonly IComparer CompareMinX = - ProjectionComparer.Create(d => d.Rect.left); + ProjectionComparer.Create(d => d.bounds.left); private static readonly IComparer CompareMinY = - ProjectionComparer.Create(d => d.Rect.top); + ProjectionComparer.Create(d => d.bounds.top); #endregion #region Insert - private List FindCoveringArea(in Rect area, int depth) + private List FindCoveringArea(in uiRect area, int depth) { var path = new List(); var node = Root; @@ -234,7 +257,7 @@ private List FindCoveringArea(in Rect area, int depth) node = node.children .Select(c => new - {EnlargedArea = c.Rect.expandToInclude(_area).area, c.Rect.area, Node = c as RTreeNode}) + {EnlargedArea = c.bounds.expandToInclude(_area).area, c.bounds.area, Node = c as RTreeNode}) .OrderBy(x => x.EnlargedArea) .ThenBy(x => x.area) .Select(x => x.Node) @@ -244,7 +267,7 @@ private List FindCoveringArea(in Rect area, int depth) private void Insert(ISpatialData data, int depth) { - var path = FindCoveringArea(data.Rect, depth); + var path = FindCoveringArea(data.bounds, depth); var insertNode = path.Last(); insertNode.Add(data); @@ -302,15 +325,15 @@ private float GetPotentialSplitMargins(List children) private float GetPotentialEnclosingMargins(List children) { - var rect = Rect.zero; + var uiRect = uiRectHelper.zero; var i = 0; - for (; i < minEntries; i++) rect = rect.expandToInclude(children[i].Rect); + for (; i < minEntries; i++) uiRect = uiRect.expandToInclude(children[i].bounds); - var totalMargin = rect.margin; + var totalMargin = uiRect.margin; for (; i < children.Count - minEntries; i++) { - rect = rect.expandToInclude(children[i].Rect); - totalMargin += rect.margin; + uiRect = uiRect.expandToInclude(children[i].bounds); + totalMargin += uiRect.margin; } return totalMargin; @@ -420,7 +443,7 @@ public RTree(int maxEntries, EqualityComparer comparer) } public RTreeNode Root { get; private set; } - public ref readonly Rect Rect => ref Root.Rect; + public uiRect uiRect => Root.bounds; public int Count { get; private set; } @@ -435,7 +458,7 @@ public IReadOnlyList Search() return GetAllChildren(new List(), Root); } - public IReadOnlyList Search(in Rect boundingBox) + public IReadOnlyList Search(in uiRect boundingBox) { return DoSearch(boundingBox); } diff --git a/Runtime/ui/painting/draw_cmd.cs b/Runtime/ui/painting/draw_cmd.cs index 622dfd84..5fee3176 100644 --- a/Runtime/ui/painting/draw_cmd.cs +++ b/Runtime/ui/painting/draw_cmd.cs @@ -1,70 +1,93 @@ -namespace Unity.UIWidgets.ui { +using UnityEngine; + +namespace Unity.UIWidgets.ui { public abstract class DrawCmd { + public abstract uiRect bounds(float margin); + } + + public abstract class StateUpdateDrawCmd : DrawCmd { + public override uiRect bounds(float margin) { + return uiRectHelper.zero; + } } - public class DrawSave : DrawCmd { + public class DrawSave : StateUpdateDrawCmd { } - public class DrawSaveLayer : DrawCmd { + public class DrawSaveLayer : StateUpdateDrawCmd { public Rect rect; public Paint paint; } - public class DrawRestore : DrawCmd { + public class DrawRestore : StateUpdateDrawCmd { } - public class DrawTranslate : DrawCmd { + public class DrawTranslate : StateUpdateDrawCmd { public float dx; public float dy; } - public class DrawScale : DrawCmd { + public class DrawScale : StateUpdateDrawCmd { public float sx; public float? sy; } - public class DrawRotate : DrawCmd { + public class DrawRotate : StateUpdateDrawCmd { public float radians; public Offset offset; } - public class DrawSkew : DrawCmd { + public class DrawSkew : StateUpdateDrawCmd { public float sx; public float sy; } - public class DrawConcat : DrawCmd { + public class DrawConcat : StateUpdateDrawCmd { public Matrix3 matrix; } - public class DrawResetMatrix : DrawCmd { + public class DrawResetMatrix : StateUpdateDrawCmd { } - public class DrawSetMatrix : DrawCmd { + public class DrawSetMatrix : StateUpdateDrawCmd { public Matrix3 matrix; } - public class DrawClipRect : DrawCmd { + public class DrawClipRect : StateUpdateDrawCmd { public Rect rect; } - public class DrawClipRRect : DrawCmd { + public class DrawClipRRect : StateUpdateDrawCmd { public RRect rrect; } - public class DrawClipPath : DrawCmd { + public class DrawClipPath : StateUpdateDrawCmd { public Path path; } public class DrawPath : DrawCmd { public Path path; public Paint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.fromRect(this.path.getBoundsWithMargin(margin)).Value; + } } public class DrawImage : DrawCmd { public Image image; public Offset offset; public Paint paint; + + // TODO: Should divide by device pixel ratio here, which is not available as + // this DrawCmd is created. This bounds should only used as an upper bound, + // assuming that device pixel ratio is always >= 1 + public override uiRect bounds(float margin) { + return uiRectHelper.fromLTWH( + this.offset.dx - margin, this.offset.dy - margin, + this.image.width + 2 * margin, + this.image.height + 2 * margin); + } } public class DrawImageRect : DrawCmd { @@ -72,6 +95,10 @@ public class DrawImageRect : DrawCmd { public Rect src; public Rect dst; public Paint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.fromRect(this.dst.inflate(margin)).Value; + } } public class DrawImageNine : DrawCmd { @@ -80,15 +107,33 @@ public class DrawImageNine : DrawCmd { public Rect center; public Rect dst; public Paint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.fromLTRB( + this.dst.left + ((this.center.left - this.src.left) * this.src.width) - margin, + this.dst.top + ((this.center.top - this.src.top) * this.src.height) - margin, + this.dst.right - ((this.src.right - this.center.right) * this.src.width) + margin, + this.dst.bottom - ((this.src.bottom - this.center.bottom) * this.src.height) + margin + ); + } } public class DrawPicture : DrawCmd { public Picture picture; + public override uiRect bounds(float margin) { + return uiRectHelper.fromRect(this.picture.paintBounds.inflate(margin)).Value; + } } public class DrawTextBlob : DrawCmd { public TextBlob? textBlob; public Offset offset; public Paint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.fromRect(this.textBlob.Value.boundsInText + .translate(this.offset.dx, this.offset.dy) + .inflate(margin)).Value; + } } } \ No newline at end of file diff --git a/Runtime/ui/painting/picture.cs b/Runtime/ui/painting/picture.cs index 350a33f2..b8a130eb 100644 --- a/Runtime/ui/painting/picture.cs +++ b/Runtime/ui/painting/picture.cs @@ -1,17 +1,26 @@ using System; using System.Collections.Generic; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; namespace Unity.UIWidgets.ui { public class Picture { - public Picture(List drawCmds, Rect paintBounds, bool isDynamic = false) { + public Picture(List drawCmds, + Rect paintBounds, + bool isDynamic = false, + BBoxHierarchy bbh = null, + List stateUpdatesIndices = null) { this.drawCmds = drawCmds; this.paintBounds = paintBounds; this._isDynamic = isDynamic; + this.bbh = bbh; + this.stateUpdatesIndices = stateUpdatesIndices; } public readonly List drawCmds; public readonly Rect paintBounds; + public readonly BBoxHierarchy bbh; + public readonly List stateUpdatesIndices; public bool isDynamic { get { return this._isDynamic; } @@ -58,8 +67,26 @@ public Picture endRecording() { throw new Exception("unmatched save/restore commands"); } + int index = 0; + RTree bbh = new RTree(); + List stateUpdateIndices = new List(); + foreach (var cmd in this._drawCmds) { + if (cmd is StateUpdateDrawCmd) { + stateUpdateIndices.Add(index); + } + else { + bbh.Insert(new IndexedRect(cmd.bounds(5), index)); + } + index++; + } + var state = this._getState(); - return new Picture(new List(this._drawCmds), state.paintBounds, this._isDynamic); + return new Picture( + new List(this._drawCmds), + state.paintBounds, + this._isDynamic, + bbh, + stateUpdateIndices); } public void addDrawCmd(DrawCmd drawCmd) { diff --git a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs index 252b69a8..eefe5e1b 100644 --- a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs +++ b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; +using System.Linq; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; using UnityEngine; using UnityEngine.Rendering; @@ -769,6 +771,18 @@ void _drawPicture(Picture picture, bool needsSave = true) { var queryBound = this._currentLayer.currentState.matrix?.invert().Value .mapRect(this._currentLayer.layerBounds) ?? this._currentLayer.layerBounds; + + if (!uiRectHelper.contains(queryBound, uiRectHelper.fromRect(picture.paintBounds).Value)) { + var indices = picture.bbh.Search(queryBound).Select(bound => bound.index); + List cmdIndices = indices.ToList(); + cmdIndices.AddRange(picture.stateUpdatesIndices); + cmdIndices.Sort(); + drawCmds = new List(); + for (int i = 0; i < cmdIndices.Count; i++) { + drawCmds.Add(picture.drawCmds[cmdIndices[i]]); + } + } + foreach (var drawCmd in drawCmds) { switch (drawCmd) { case DrawSave _: @@ -842,46 +856,24 @@ void _drawPicture(Picture picture, bool needsSave = true) { } case DrawPath cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromRect( - cmd.path.getBoundsWithMargin(5)).Value, queryBound).isEmpty) { - continue; - } var uipath = uiPath.fromPath(cmd.path); this._drawPath(uipath, uiPaint.fromPaint(cmd.paint)); uiPathCacheManager.putToCache(uipath); break; } - case DrawImage cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromLTWH( - cmd.offset.dx, cmd.offset.dy, - cmd.image.width / this._devicePixelRatio, - cmd.image.height / this._devicePixelRatio), queryBound).isEmpty) { - continue; - } - this._drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, + case DrawImage cmd: {_drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, uiPaint.fromPaint(cmd.paint)); break; } case DrawImageRect cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromRect(cmd.dst).Value, queryBound).isEmpty) { - continue; - } this._drawImageRect(cmd.image, uiRectHelper.fromRect(cmd.src), uiRectHelper.fromRect(cmd.dst).Value, uiPaint.fromPaint(cmd.paint)); break; } case DrawImageNine cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromLTRB( - cmd.dst.left + ((cmd.center.left - cmd.src.left) * cmd.src.width), - cmd.dst.top + ((cmd.center.top - cmd.src.top) * cmd.src.height), - cmd.dst.right - ((cmd.src.right - cmd.center.right) * cmd.src.width), - cmd.dst.bottom - ((cmd.src.bottom - cmd.center.bottom) * cmd.src.height) - ), queryBound).isEmpty) { - continue; - } this._drawImageNine(cmd.image, uiRectHelper.fromRect(cmd.src), uiRectHelper.fromRect(cmd.center).Value, uiRectHelper.fromRect(cmd.dst).Value, uiPaint.fromPaint(cmd.paint)); @@ -889,19 +881,11 @@ void _drawPicture(Picture picture, bool needsSave = true) { } case DrawPicture cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromRect( - cmd.picture.paintBounds).Value, queryBound).isEmpty) { - continue; - } this._drawPicture(cmd.picture); break; } case DrawTextBlob cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromRect( - cmd.textBlob.Value.boundsInText.translate(cmd.offset.dx, cmd.offset.dy)).Value, queryBound).isEmpty) { - continue; - } this._paintTextShadow(cmd.textBlob, cmd.offset); this._drawTextBlob(cmd.textBlob, (uiOffset.fromOffset(cmd.offset)).Value, uiPaint.fromPaint(cmd.paint)); @@ -934,6 +918,21 @@ void _drawUIPicture(uiPicture picture, bool needsSave = true) { var queryBound = this._currentLayer.currentState.matrix?.invert().Value .mapRect(this._currentLayer.layerBounds) ?? this._currentLayer.layerBounds; + + if (!uiRectHelper.contains(queryBound, picture.paintBounds)) { + var indices = picture.bbh.Search(queryBound).Select(bound => bound.index); + List cmdIndices = indices.ToList(); + cmdIndices.Capacity += picture.stateUpdatesIndices.Count; + for (int i = 0; i < picture.stateUpdatesIndices.Count; i++) { + cmdIndices.Add(picture.stateUpdatesIndices[i]); + } + cmdIndices.Sort(); + drawCmds = new List(); + for (int i = 0; i < cmdIndices.Count; i++) { + drawCmds.Add(picture.drawCmds[cmdIndices[i]]); + } + } + foreach (var drawCmd in drawCmds) { switch (drawCmd) { case uiDrawSave _: @@ -1005,60 +1004,31 @@ void _drawUIPicture(uiPicture picture, bool needsSave = true) { } case uiDrawPath cmd: { - if (uiRectHelper.intersect(cmd.path.getBoundsWithMargin(5), queryBound).isEmpty) { - continue; - } this._drawPath(cmd.path, cmd.paint); break; } case uiDrawImage cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromLTWH( - cmd.offset.Value.dx, cmd.offset.Value.dy, - cmd.image.width / this._devicePixelRatio, - cmd.image.height / this._devicePixelRatio), queryBound).isEmpty) { - continue; - } this._drawImage(cmd.image, cmd.offset.Value, cmd.paint); break; } case uiDrawImageRect cmd: { - if (uiRectHelper.intersect(cmd.dst.Value, queryBound).isEmpty) { - continue; - } this._drawImageRect(cmd.image, cmd.src, cmd.dst.Value, cmd.paint); break; } case uiDrawImageNine cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromLTRB( - cmd.dst.Value.left + ((cmd.center.Value.left - cmd.src.Value.left) * cmd.src.Value.width), - cmd.dst.Value.top + ((cmd.center.Value.top - cmd.src.Value.top) * cmd.src.Value.height), - cmd.dst.Value.right - ((cmd.src.Value.right - cmd.center.Value.right) * cmd.src.Value.width), - cmd.dst.Value.bottom - ((cmd.src.Value.bottom - cmd.center.Value.bottom) * cmd.src.Value.height) - ), queryBound).isEmpty) { - continue; - } this._drawImageNine(cmd.image, cmd.src, cmd.center.Value, cmd.dst.Value, cmd.paint); break; } case uiDrawPicture cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromRect( - cmd.picture.paintBounds).Value, queryBound).isEmpty) { - continue; - } this._drawPicture(cmd.picture); break; } case uiDrawTextBlob cmd: { - if (uiRectHelper.intersect(uiRectHelper.fromRect( - cmd.textBlob.Value.boundsInText.translate(cmd.offset.Value.dx, cmd.offset.Value.dy)).Value, - queryBound).isEmpty) { - continue; - } this._paintTextShadow(cmd.textBlob, new Offset(cmd.offset.Value.dx, cmd.offset.Value.dy)); this._drawTextBlob(cmd.textBlob, cmd.offset.Value, cmd.paint); break; diff --git a/Runtime/ui/renderer/common/draw_cmd.cs b/Runtime/ui/renderer/common/draw_cmd.cs index 91255b91..63723a57 100644 --- a/Runtime/ui/renderer/common/draw_cmd.cs +++ b/Runtime/ui/renderer/common/draw_cmd.cs @@ -1,9 +1,16 @@ namespace Unity.UIWidgets.ui { public abstract class uiDrawCmd : PoolObject { public abstract void release(); + public abstract uiRect bounds(float margin); } - public class uiDrawSave : uiDrawCmd { + public abstract class uiStateUpdateDrawCmd : uiDrawCmd { + public override uiRect bounds(float margin) { + return uiRectHelper.zero; + } + } + + public class uiDrawSave : uiStateUpdateDrawCmd { public uiDrawSave() { } @@ -17,7 +24,7 @@ public override void release() { } } - public class uiDrawSaveLayer : uiDrawCmd { + public class uiDrawSaveLayer : uiStateUpdateDrawCmd { public uiDrawSaveLayer() { } @@ -40,7 +47,7 @@ public override void clear() { public uiPaint paint; } - public class uiDrawRestore : uiDrawCmd { + public class uiDrawRestore : uiStateUpdateDrawCmd { public uiDrawRestore() { } @@ -54,7 +61,7 @@ public override void release() { } } - public class uiDrawTranslate : uiDrawCmd { + public class uiDrawTranslate : uiStateUpdateDrawCmd { public uiDrawTranslate() { } @@ -73,7 +80,7 @@ public override void release() { public float dy; } - public class uiDrawScale : uiDrawCmd { + public class uiDrawScale : uiStateUpdateDrawCmd { public uiDrawScale() { } @@ -92,7 +99,7 @@ public override void release() { public float? sy; } - public class uiDrawRotate : uiDrawCmd { + public class uiDrawRotate : uiStateUpdateDrawCmd { public uiDrawRotate() { } @@ -115,7 +122,7 @@ public override void clear() { public uiOffset? offset; } - public class uiDrawSkew : uiDrawCmd { + public class uiDrawSkew : uiStateUpdateDrawCmd { public uiDrawSkew() { } @@ -134,7 +141,7 @@ public override void release() { public float sy; } - public class uiDrawConcat : uiDrawCmd { + public class uiDrawConcat : uiStateUpdateDrawCmd { public uiDrawConcat() { } @@ -155,7 +162,7 @@ public override void clear() { public uiMatrix3? matrix; } - public class uiDrawResetMatrix : uiDrawCmd { + public class uiDrawResetMatrix : uiStateUpdateDrawCmd { public uiDrawResetMatrix() { } @@ -169,7 +176,7 @@ public override void release() { } } - public class uiDrawSetMatrix : uiDrawCmd { + public class uiDrawSetMatrix : uiStateUpdateDrawCmd { public uiDrawSetMatrix() { } @@ -190,7 +197,7 @@ public override void clear() { public uiMatrix3? matrix; } - public class uiDrawClipRect : uiDrawCmd { + public class uiDrawClipRect : uiStateUpdateDrawCmd { public uiDrawClipRect() { } @@ -211,7 +218,7 @@ public override void clear() { public uiRect? rect; } - public class uiDrawClipRRect : uiDrawCmd { + public class uiDrawClipRRect : uiStateUpdateDrawCmd { public uiDrawClipRRect() { } @@ -232,7 +239,7 @@ public override void clear() { public RRect rrect; } - public class uiDrawClipPath : uiDrawCmd { + public class uiDrawClipPath : uiStateUpdateDrawCmd { public uiDrawClipPath() { } @@ -278,6 +285,10 @@ public override void clear() { public uiPath path; public uiPaint paint; + + public override uiRect bounds(float margin) { + return this.path.getBoundsWithMargin(margin); + } } public class uiDrawImage : uiDrawCmd { @@ -304,6 +315,17 @@ public override void clear() { public Image image; public uiOffset? offset; public uiPaint paint; + + + // TODO: Should divide by device pixel ratio here, which is not available as + // this DrawCmd is created. This bounds should only used as an upper bound, + // assuming that device pixel ratio is always >= 1 + public override uiRect bounds(float margin) { + return uiRectHelper.fromLTWH( + this.offset.Value.dx - margin, this.offset.Value.dy - margin, + this.image.width + 2 * margin, + this.image.height + 2 * margin); + } } public class uiDrawImageRect : uiDrawCmd { @@ -333,6 +355,10 @@ public override void clear() { public uiRect? src; public uiRect? dst; public uiPaint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.inflate(this.dst.Value, margin); + } } public class uiDrawImageNine : uiDrawCmd { @@ -365,6 +391,15 @@ public override void clear() { public uiRect? center; public uiRect? dst; public uiPaint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.fromLTRB( + this.dst.Value.left + ((this.center.Value.left - this.src.Value.left) * this.src.Value.width) - margin, + this.dst.Value.top + ((this.center.Value.top - this.src.Value.top) * this.src.Value.height) - margin, + this.dst.Value.right - ((this.src.Value.right - this.center.Value.right) * this.src.Value.width) + margin, + this.dst.Value.bottom - ((this.src.Value.bottom - this.center.Value.bottom) * this.src.Value.height) + margin + ); + } } public class uiDrawPicture : uiDrawCmd { @@ -386,6 +421,9 @@ public override void clear() { } public Picture picture; + public override uiRect bounds(float margin) { + return uiRectHelper.fromRect(this.picture.paintBounds.inflate(margin)).Value; + } } public class uiDrawTextBlob : uiDrawCmd { @@ -412,5 +450,10 @@ public override void clear() { public TextBlob? textBlob; public uiOffset? offset; public uiPaint paint; + + public override uiRect bounds(float margin) { + return uiRectHelper.fromRect(this.textBlob.Value.boundsInText.translate( + this.offset.Value.dx, this.offset.Value.dy).inflate(margin)).Value; + } } } \ No newline at end of file diff --git a/Runtime/ui/renderer/common/geometry/rect.cs b/Runtime/ui/renderer/common/geometry/rect.cs index 07a68b51..aa7ea7cb 100644 --- a/Runtime/ui/renderer/common/geometry/rect.cs +++ b/Runtime/ui/renderer/common/geometry/rect.cs @@ -26,6 +26,14 @@ public float height { get { return this.bottom - this.top; } } + public float area { + get { return this.width * this.height; } + } + + public float margin { + get { return this.width + this.height; } + } + public uiOffset topLeft { get { return new uiOffset(this.left, this.top); } } diff --git a/Runtime/ui/renderer/common/picture.cs b/Runtime/ui/renderer/common/picture.cs index 8581deab..0a64729e 100644 --- a/Runtime/ui/renderer/common/picture.cs +++ b/Runtime/ui/renderer/common/picture.cs @@ -1,25 +1,34 @@ using System; using System.Collections.Generic; using Unity.UIWidgets.foundation; +using Unity.UIWidgets.Runtime.external; namespace Unity.UIWidgets.ui { public class uiPicture : PoolObject { public uiPicture() { } - public static uiPicture create(List drawCmds, uiRect paintBounds) { + public static uiPicture create(List drawCmds, + uiRect paintBounds, + BBoxHierarchy bbh = null, + uiList stateUpdateIndices = null) { var picture = ObjectPool.alloc(); picture.drawCmds = drawCmds; picture.paintBounds = paintBounds; + picture.bbh = bbh; + picture.stateUpdatesIndices = stateUpdateIndices; return picture; } public List drawCmds; public uiRect paintBounds; + public BBoxHierarchy bbh; + public uiList stateUpdatesIndices; public override void clear() { //the recorder will dispose the draw commands this.drawCmds = null; + ObjectPool>.release(this.stateUpdatesIndices); } } @@ -65,9 +74,22 @@ public uiPicture endRecording() { if (this._states.Count > 1) { throw new Exception("unmatched save/restore commands"); } + + int index = 0; + RTree bbh = new RTree(); + uiList stateUpdateIndices = ObjectPool>.alloc(); + foreach (var cmd in this._drawCmds) { + if (cmd is uiStateUpdateDrawCmd) { + stateUpdateIndices.Add(index); + } + else { + bbh.Insert(new IndexedRect(cmd.bounds(5), index)); + } + index++; + } var state = this._getState(); - return uiPicture.create(this._drawCmds, state.paintBounds); + return uiPicture.create(this._drawCmds, state.paintBounds, bbh: bbh, stateUpdateIndices: stateUpdateIndices); } public void addDrawCmd(uiDrawCmd drawCmd) { From 51415fbc7d03ab2814c1acd10a4a615da3690f80 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 16:25:56 +0800 Subject: [PATCH 13/28] Fix typo. --- Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs index eefe5e1b..f0c8b1cc 100644 --- a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs +++ b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs @@ -862,7 +862,7 @@ void _drawPicture(Picture picture, bool needsSave = true) { break; } - case DrawImage cmd: {_drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, + case DrawImage cmd: {this._drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, uiPaint.fromPaint(cmd.paint)); break; } From 23dee55e0d51c6431fbaed72a22ab71ac8b26e6f Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 17:04:05 +0800 Subject: [PATCH 14/28] Optimize layout: request character only at begining. --- Runtime/ui/txt/layout.cs | 4 ++-- Runtime/ui/txt/paragraph.cs | 11 ++++++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/Runtime/ui/txt/layout.cs b/Runtime/ui/txt/layout.cs index e13c8bff..3e65ce38 100644 --- a/Runtime/ui/txt/layout.cs +++ b/Runtime/ui/txt/layout.cs @@ -18,7 +18,7 @@ public static float measureText(string text, TextStyle style) { } else { Font font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; - font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); + // font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); for (int i = 0; i < text.Length; i++) { char ch = text[i]; if (font.getGlyphInfo(ch, out var glyphInfo, style.UnityFontSize, style.UnityFontStyle)) { @@ -100,7 +100,7 @@ public static float computeCharWidths(float offset, string text, int start, int else { Font font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; // TODO: it is kind of a waste to require the entire string for this style, but SubString causes alloc - font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); + // font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); for (int i = 0; i < count; i++) { char ch = text[start + i]; if (ch == '\t') { diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index 8304485f..65c3d0f6 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -662,10 +662,19 @@ internal int totalCodeUnitsInLine(int lineNumber) { return nextLineStart - lineStart; } - internal void setText(string text, StyledRuns runs) { + internal void setText(string text, StyledRuns runs, bool skipRequestCharacters = false) { this._text = text; this._runs = runs; this._needsLayout = true; + if (!skipRequestCharacters) { + for (int i = 0; i < runs.size; i++) { + var style = runs.getStyle(i); + var run = runs.getRun(i); + var font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; + font.RequestCharactersInTextureSafe(text.Substring(run.start, run.end - run.start), + Mathf.CeilToInt(style.fontSize), style.UnityFontStyle); + } + } } public void setParagraphStyle(ParagraphStyle style) { From 295b29cae2c89ab5dc9b85216b1f11a162e07ba3 Mon Sep 17 00:00:00 2001 From: "xingwei.zhu" Date: Wed, 20 Nov 2019 17:32:40 +0800 Subject: [PATCH 15/28] fix error: won't adjust fill area size when canskipAAhairline is true --- Runtime/ui/renderer/common/geometry/path/path_cache.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Runtime/ui/renderer/common/geometry/path/path_cache.cs b/Runtime/ui/renderer/common/geometry/path/path_cache.cs index 1cfd08f1..1ba5d357 100644 --- a/Runtime/ui/renderer/common/geometry/path/path_cache.cs +++ b/Runtime/ui/renderer/common/geometry/path/path_cache.cs @@ -403,7 +403,7 @@ uiVertexUV _expandStroke(float w, float fringe, StrokeCap lineCap, StrokeJoin li } uiVertexUV _expandFill(float fringe) { - float aa = fringe; + float aa = this.canSkipAAHairline ? 0f : fringe; float woff = aa * 0.5f; var points = this._points; var paths = this._paths; @@ -469,7 +469,7 @@ uiVertexUV _expandFill(float fringe) { uiList _strokeVertices = null; uiList _strokeUV = null; - if (aa > 0.0f && !this.canSkipAAHairline) { + if (aa > 0.0f) { _strokeVertices = ObjectPool>.alloc(); _strokeUV = ObjectPool>.alloc(); cvertices = 0; From 054e5d36117eb496716beaaa9390a377ca2943b3 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Wed, 20 Nov 2019 17:57:56 +0800 Subject: [PATCH 16/28] Fix some issues. --- Runtime/ui/txt/paragraph.cs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index 65c3d0f6..ad044d66 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -318,7 +318,8 @@ public void paint(Canvas canvas, Offset offset) { } public void layout(ParagraphConstraints constraints) { - if (!this._needsLayout && this._width == constraints.width) { + if ((!this._needsLayout && this._width == constraints.width) || + this._text == null || this._text.isEmpty()) { return; } @@ -663,6 +664,13 @@ internal int totalCodeUnitsInLine(int lineNumber) { } internal void setText(string text, StyledRuns runs, bool skipRequestCharacters = false) { + if (text == null || text.isEmpty()) { + this.clear(); + this._text = text; + this._runs = runs; + this._needsLayout = false; + return; + } this._text = text; this._runs = runs; this._needsLayout = true; @@ -672,7 +680,7 @@ internal void setText(string text, StyledRuns runs, bool skipRequestCharacters = var run = runs.getRun(i); var font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; font.RequestCharactersInTextureSafe(text.Substring(run.start, run.end - run.start), - Mathf.CeilToInt(style.fontSize), style.UnityFontStyle); + style.UnityFontSize, style.UnityFontStyle); } } } From ed629faf91af0b89c9d7dfc0a1ff78972de0a29d Mon Sep 17 00:00:00 2001 From: iizzaya Date: Thu, 21 Nov 2019 14:22:37 +0800 Subject: [PATCH 17/28] [Fix] InputDecoration prefix & suffix --- Runtime/material/input_decorator.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Runtime/material/input_decorator.cs b/Runtime/material/input_decorator.cs index 33f6efee..e0c4d748 100644 --- a/Runtime/material/input_decorator.cs +++ b/Runtime/material/input_decorator.cs @@ -2102,8 +2102,12 @@ public InputDecoration( this.hasFloatingPlaceholder = hasFloatingPlaceholder; this.isDense = isDense; this.contentPadding = contentPadding; + this.prefix = prefix; + this.prefixText = prefixText; this.prefixIcon = prefixIcon; this.prefixStyle = prefixStyle; + this.suffix = suffix; + this.suffixText = suffixText; this.suffixIcon = suffixIcon; this.suffixStyle = suffixStyle; this.counter = counter; From 09dfb52a9cc127869b945851f9f7ec9cf2bd48d0 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Thu, 21 Nov 2019 16:00:35 +0800 Subject: [PATCH 18/28] Update bhh when creating picture. --- Runtime/external/RTree.cs | 4 ++ Runtime/ui/painting/picture.cs | 52 ++++++++++++----- .../cmdbufferCanvas/rendering/canvas_impl.cs | 2 +- Runtime/ui/renderer/common/picture.cs | 56 ++++++++++++++----- 4 files changed, 85 insertions(+), 29 deletions(-) diff --git a/Runtime/external/RTree.cs b/Runtime/external/RTree.cs index 58a07b12..9e20da44 100644 --- a/Runtime/external/RTree.cs +++ b/Runtime/external/RTree.cs @@ -136,6 +136,10 @@ public interface BBoxHierarchy where T : ISpatialData { IReadOnlyList Search(in uiRect boundingBox); void BulkLoad(IEnumerable items); + + void Insert(T data); + + void Clear(); } public class RTree : BBoxHierarchy where T : ISpatialData diff --git a/Runtime/ui/painting/picture.cs b/Runtime/ui/painting/picture.cs index b8a130eb..a003d1e4 100644 --- a/Runtime/ui/painting/picture.cs +++ b/Runtime/ui/painting/picture.cs @@ -33,6 +33,10 @@ public class PictureRecorder { readonly List _drawCmds = new List(); readonly List _states = new List(); + + readonly BBoxHierarchy _bbh = new RTree(); + + readonly List _stateUpdateIndices = new List(); bool _isDynamic; @@ -60,6 +64,8 @@ public void reset() { layerOffset = null, paintBounds = Rect.zero, }); + this._bbh.Clear(); + this._stateUpdateIndices.Clear(); } public Picture endRecording() { @@ -67,26 +73,13 @@ public Picture endRecording() { throw new Exception("unmatched save/restore commands"); } - int index = 0; - RTree bbh = new RTree(); - List stateUpdateIndices = new List(); - foreach (var cmd in this._drawCmds) { - if (cmd is StateUpdateDrawCmd) { - stateUpdateIndices.Add(index); - } - else { - bbh.Insert(new IndexedRect(cmd.bounds(5), index)); - } - index++; - } - var state = this._getState(); return new Picture( new List(this._drawCmds), state.paintBounds, this._isDynamic, - bbh, - stateUpdateIndices); + this._bbh, + this._stateUpdateIndices); } public void addDrawCmd(DrawCmd drawCmd) { @@ -95,6 +88,7 @@ public void addDrawCmd(DrawCmd drawCmd) { switch (drawCmd) { case DrawSave _: this._states.Add(this._getState().copy()); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; case DrawSaveLayer cmd: { this._states.Add(new CanvasState { @@ -104,6 +98,7 @@ public void addDrawCmd(DrawCmd drawCmd) { layerOffset = cmd.rect.topLeft, paintBounds = Rect.zero, }); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -120,6 +115,7 @@ public void addDrawCmd(DrawCmd drawCmd) { paintBounds = state.xform.mapRect(paintBounds); this._addPaintBounds(paintBounds); } + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -128,6 +124,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); state.xform = new Matrix3(state.xform); state.xform.preTranslate(cmd.dx, cmd.dy); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -135,6 +132,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); state.xform = new Matrix3(state.xform); state.xform.preScale(cmd.sx, (cmd.sy ?? cmd.sx)); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -149,6 +147,7 @@ public void addDrawCmd(DrawCmd drawCmd) { cmd.offset.dx, cmd.offset.dy); } + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -157,6 +156,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); state.xform = new Matrix3(state.xform); state.xform.preSkew(cmd.sx, cmd.sy); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -164,18 +164,21 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); state.xform = new Matrix3(state.xform); state.xform.preConcat(cmd.matrix); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case DrawResetMatrix _: { var state = this._getState(); state.xform = Matrix3.I(); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case DrawSetMatrix cmd: { var state = this._getState(); state.xform = new Matrix3(cmd.matrix); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -184,6 +187,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var rect = state.xform.mapRect(cmd.rect); state.scissor = state.scissor == null ? rect : state.scissor.intersect(rect); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -192,6 +196,7 @@ public void addDrawCmd(DrawCmd drawCmd) { var rect = state.xform.mapRect(cmd.rrect.outerRect); state.scissor = state.scissor == null ? rect : state.scissor.intersect(rect); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -205,6 +210,7 @@ public void addDrawCmd(DrawCmd drawCmd) { cache.computeFillMesh(0.0f, out _); var rect = cache.fillMesh.transform(state.xform).bounds; state.scissor = state.scissor == null ? rect : state.scissor.intersect(rect); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } @@ -243,9 +249,13 @@ public void addDrawCmd(DrawCmd drawCmd) { float sigma = scale * paint.maskFilter.sigma; float sigma3 = 3 * sigma; this._addPaintBounds(mesh.bounds.inflate(sigma3)); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(mesh.bounds.inflate(sigma3 + 5)).Value, + this._drawCmds.Count - 1)); } else { this._addPaintBounds(mesh.bounds); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(mesh.bounds.inflate(5)).Value, + this._drawCmds.Count - 1)); } break; @@ -257,6 +267,8 @@ public void addDrawCmd(DrawCmd drawCmd) { cmd.image.width, cmd.image.height); rect = state.xform.mapRect(rect); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); if (cmd.image.isDynamic) { this._isDynamic = true; } @@ -268,6 +280,8 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); if (cmd.image.isDynamic) { this._isDynamic = true; } @@ -279,6 +293,8 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); if (cmd.image.isDynamic) { this._isDynamic = true; } @@ -290,6 +306,8 @@ public void addDrawCmd(DrawCmd drawCmd) { var state = this._getState(); var rect = state.xform.mapRect(cmd.picture.paintBounds); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); if (cmd.picture.isDynamic) { this._isDynamic = true; } @@ -308,9 +326,13 @@ public void addDrawCmd(DrawCmd drawCmd) { float sigma = scale * paint.maskFilter.sigma; float sigma3 = 3 * sigma; this._addPaintBounds(rect.inflate(sigma3)); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(sigma3 + 5)).Value, + this._drawCmds.Count - 1)); } else { this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.fromRect(rect.inflate(5)).Value, + this._drawCmds.Count - 1)); } break; diff --git a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs index f0c8b1cc..dc4447c0 100644 --- a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs +++ b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs @@ -932,7 +932,7 @@ void _drawUIPicture(uiPicture picture, bool needsSave = true) { drawCmds.Add(picture.drawCmds[cmdIndices[i]]); } } - + foreach (var drawCmd in drawCmds) { switch (drawCmd) { case uiDrawSave _: diff --git a/Runtime/ui/renderer/common/picture.cs b/Runtime/ui/renderer/common/picture.cs index 0a64729e..15496bdd 100644 --- a/Runtime/ui/renderer/common/picture.cs +++ b/Runtime/ui/renderer/common/picture.cs @@ -36,6 +36,10 @@ public class uiPictureRecorder { readonly List _drawCmds = new List(128); readonly List _states = new List(32); + + readonly BBoxHierarchy _bbh = new RTree(); + + readonly List _stateUpdateIndices = new List(); public uiPictureRecorder() { this.reset(); @@ -68,6 +72,8 @@ public void reset() { layerOffset = null, paintBounds = uiRectHelper.zero }); + this._bbh.Clear(); + this._stateUpdateIndices.Clear(); } public uiPicture endRecording() { @@ -75,21 +81,14 @@ public uiPicture endRecording() { throw new Exception("unmatched save/restore commands"); } - int index = 0; - RTree bbh = new RTree(); uiList stateUpdateIndices = ObjectPool>.alloc(); - foreach (var cmd in this._drawCmds) { - if (cmd is uiStateUpdateDrawCmd) { - stateUpdateIndices.Add(index); - } - else { - bbh.Insert(new IndexedRect(cmd.bounds(5), index)); - } - index++; - } - + stateUpdateIndices.AddRange(this._stateUpdateIndices); var state = this._getState(); - return uiPicture.create(this._drawCmds, state.paintBounds, bbh: bbh, stateUpdateIndices: stateUpdateIndices); + return uiPicture.create( + this._drawCmds, + state.paintBounds, + bbh: this._bbh, + stateUpdateIndices: stateUpdateIndices); } public void addDrawCmd(uiDrawCmd drawCmd) { @@ -98,6 +97,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { switch (drawCmd) { case uiDrawSave _: this._states.Add(this._getState().copy()); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; case uiDrawSaveLayer cmd: { this._states.Add(new uiCanvasState { @@ -107,6 +107,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { layerOffset = cmd.rect.Value.topLeft, paintBounds = uiRectHelper.zero }); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawRestore _: { @@ -124,6 +125,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { } this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawTranslate cmd: { @@ -131,6 +133,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.xform = new uiMatrix3(state.xform); state.xform.preTranslate(cmd.dx, cmd.dy); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawScale cmd: { @@ -138,6 +141,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.xform = new uiMatrix3(state.xform); state.xform.preScale(cmd.sx, (cmd.sy ?? cmd.sx)); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawRotate cmd: { @@ -153,6 +157,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { } this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawSkew cmd: { @@ -160,6 +165,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.xform = new uiMatrix3(state.xform); state.xform.preSkew(cmd.sx, cmd.sy); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawConcat cmd: { @@ -167,18 +173,21 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.xform = new uiMatrix3(state.xform); state.xform.preConcat(cmd.matrix.Value); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawResetMatrix _: { var state = this._getState(); state.xform = uiMatrix3.I(); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawSetMatrix cmd: { var state = this._getState(); state.xform = new uiMatrix3(cmd.matrix.Value); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawClipRect cmd: { @@ -187,6 +196,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { var rect = state.xform.mapRect(cmd.rect.Value); state.scissor = state.scissor == null ? rect : state.scissor.Value.intersect(rect); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawClipRRect cmd: { @@ -195,6 +205,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { var rect = state.xform.mapRect(uiRectHelper.fromRect(cmd.rrect.outerRect).Value); state.scissor = state.scissor == null ? rect : state.scissor.Value.intersect(rect); this._setState(state); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawClipPath cmd: { @@ -210,6 +221,7 @@ public void addDrawCmd(uiDrawCmd drawCmd) { state.scissor = state.scissor == null ? rect : state.scissor.Value.intersect(rect); this._setState(state); ObjectPool.release(transformedMesh); + this._stateUpdateIndices.Add(this._drawCmds.Count - 1); break; } case uiDrawPath cmd: { @@ -250,9 +262,13 @@ public void addDrawCmd(uiDrawCmd drawCmd) { float sigma = scale * paint.maskFilter.Value.sigma; float sigma3 = 3 * sigma; this._addPaintBounds(uiRectHelper.inflate(mesh.bounds, sigma3)); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(mesh.bounds, sigma3 + 5), + this._drawCmds.Count - 1)); } else { this._addPaintBounds(mesh.bounds); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(mesh.bounds, 5), + this._drawCmds.Count - 1)); } ObjectPool.release(mesh); @@ -264,24 +280,32 @@ public void addDrawCmd(uiDrawCmd drawCmd) { cmd.image.width, cmd.image.height); rect = state.xform.mapRect(rect); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); break; } case uiDrawImageRect cmd: { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst.Value); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); break; } case uiDrawImageNine cmd: { var state = this._getState(); var rect = state.xform.mapRect(cmd.dst.Value); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); break; } case uiDrawPicture cmd: { var state = this._getState(); var rect = state.xform.mapRect(uiRectHelper.fromRect(cmd.picture.paintBounds).Value); this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); break; } case uiDrawTextBlob cmd: { @@ -290,15 +314,21 @@ public void addDrawCmd(uiDrawCmd drawCmd) { var rect = uiRectHelper.fromRect( cmd.textBlob.Value.shiftedBoundsInText(cmd.offset.Value.dx, cmd.offset.Value.dy)).Value; rect = state.xform.mapRect(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); var paint = cmd.paint; if (paint.maskFilter != null && paint.maskFilter.Value.sigma != 0) { float sigma = scale * paint.maskFilter.Value.sigma; float sigma3 = 3 * sigma; this._addPaintBounds(uiRectHelper.inflate(rect, sigma3)); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, sigma3 + 5), + this._drawCmds.Count - 1)); } else { this._addPaintBounds(rect); + this._bbh.Insert(new IndexedRect(uiRectHelper.inflate(rect, 5), + this._drawCmds.Count - 1)); } break; From e30188fc68e296bfd029770e35b8617c4e099af9 Mon Sep 17 00:00:00 2001 From: iizzaya Date: Thu, 21 Nov 2019 16:17:52 +0800 Subject: [PATCH 19/28] [Widget] Add Material Search Widget --- Runtime/material/search.cs | 284 ++++++++++++++++++++++++++++++++ Runtime/material/search.cs.meta | 11 ++ Runtime/widgets/pages.cs | 2 + Runtime/widgets/routes.cs | 5 +- 4 files changed, 300 insertions(+), 2 deletions(-) create mode 100644 Runtime/material/search.cs create mode 100644 Runtime/material/search.cs.meta diff --git a/Runtime/material/search.cs b/Runtime/material/search.cs new file mode 100644 index 00000000..2644b761 --- /dev/null +++ b/Runtime/material/search.cs @@ -0,0 +1,284 @@ +using System; +using System.Collections.Generic; +using RSG; +using Unity.UIWidgets.animation; +using Unity.UIWidgets.foundation; +using Unity.UIWidgets.service; +using Unity.UIWidgets.widgets; +using UnityEngine; +using Color = Unity.UIWidgets.ui.Color; + +namespace Unity.UIWidgets.material { + public class SearchUtils { + public static IPromise showSearch( + BuildContext context, + SearchDelegate del, + string query = "" + ) { + D.assert(del != null); + D.assert(context != null); + + del.query = query ?? del.query; + del._currentBody = _SearchBody.suggestions; + return Navigator.of(context).push(new _SearchPageRoute( + del: del + )); + } + } + + public abstract class SearchDelegate { + public abstract Widget buildSuggestions(BuildContext context); + public abstract Widget buildResults(BuildContext context); + public abstract Widget buildLeading(BuildContext context); + public abstract List buildActions(BuildContext context); + + public virtual ThemeData appBarTheme(BuildContext context) { + D.assert(context != null); + ThemeData theme = Theme.of(context); + D.assert(theme != null); + return theme.copyWith( + primaryColor: Colors.white, + primaryIconTheme: theme.primaryIconTheme.copyWith(color: Colors.grey), + primaryColorBrightness: Brightness.light, + primaryTextTheme: theme.textTheme + ); + } + + public virtual string query { + get { return this._queryTextController.text; } + set { + D.assert(this.query != null); + this._queryTextController.text = value; + } + } + + public virtual void showResults(BuildContext context) { + this._focusNode.unfocus(); + this._currentBody = _SearchBody.results; + } + + public virtual void showSuggestions(BuildContext context) { + FocusScope.of(context).requestFocus(this._focusNode); + this._currentBody = _SearchBody.suggestions; + } + + public virtual void close(BuildContext context, object result) { + this._currentBody = null; + this._focusNode.unfocus(); + var state = Navigator.of(context); + state.popUntil((Route route) => route == this._route); + state.pop(result); + } + + + public virtual Animation transitionAnimation { + get { return this._proxyAnimation; } + } + + readonly internal FocusNode _focusNode = new FocusNode(); + + readonly internal TextEditingController _queryTextController = new TextEditingController(); + + readonly internal ProxyAnimation _proxyAnimation = new ProxyAnimation(Animations.kAlwaysDismissedAnimation); + + readonly internal ValueNotifier<_SearchBody?> _currentBodyNotifier = new ValueNotifier<_SearchBody?>(null); + + internal _SearchBody? _currentBody { + get { return this._currentBodyNotifier.value; } + set { this._currentBodyNotifier.value = value; } + } + + internal _SearchPageRoute _route; + } + + enum _SearchBody { + suggestions, + results + } + + class _SearchPageRoute : PageRoute { + public _SearchPageRoute(SearchDelegate del) { + D.assert(del != null); + D.assert(del._route == null, + () => $"The {this.del.GetType()} instance is currently used by another active " + + "search. Please close that search by calling close() on the SearchDelegate " + + "before openening another search with the same delegate instance." + ); + this.del = del; + this.del._route = this; + } + + public readonly SearchDelegate del; + + public override Color barrierColor { + get { return null; } + } + + public override TimeSpan transitionDuration { + get { return new TimeSpan(0, 0, 0, 0, 300); } + } + + public override bool maintainState { + get { return false; } + } + + public override Widget buildTransitions( + BuildContext context, + Animation animation, + Animation secondaryAnimation, + Widget child + ) { + return new FadeTransition( + opacity: animation, + child: child + ); + } + + public override Animation createAnimation() { + Animation animation = base.createAnimation(); + this.del._proxyAnimation.parent = animation; + return animation; + } + + public override Widget buildPage( + BuildContext context, + Animation animation, + Animation secondaryAnimation + ) { + return new _SearchPage( + del: this.del, + animation: animation + ); + } + + protected internal override void didComplete(object result) { + base.didComplete(result); + D.assert(this.del._route == this); + this.del._route = null; + this.del._currentBody = null; + } + } + + class _SearchPage : StatefulWidget { + public _SearchPage( + SearchDelegate del, + Animation animation + ) { + this.del = del; + this.animation = animation; + } + + public readonly SearchDelegate del; + + public readonly Animation animation; + + public override State createState() { + return new _SearchPageState(); + } + } + + class _SearchPageState : State<_SearchPage> { + public override void initState() { + base.initState(); + this.queryTextController.addListener(this._onQueryChanged); + this.widget.animation.addStatusListener(this._onAnimationStatusChanged); + this.widget.del._currentBodyNotifier.addListener(this._onSearchBodyChanged); + this.widget.del._focusNode.addListener(this._onFocusChanged); + } + + public override void dispose() { + base.dispose(); + this.queryTextController.removeListener(this._onQueryChanged); + this.widget.animation.removeStatusListener(this._onAnimationStatusChanged); + this.widget.del._currentBodyNotifier.removeListener(this._onSearchBodyChanged); + this.widget.del._focusNode.removeListener(this._onFocusChanged); + } + + void _onAnimationStatusChanged(AnimationStatus status) { + if (status != AnimationStatus.completed) { + return; + } + + this.widget.animation.removeStatusListener(this._onAnimationStatusChanged); + if (this.widget.del._currentBody == _SearchBody.suggestions) { + FocusScope.of(this.context).requestFocus(this.widget.del._focusNode); + } + } + + void _onFocusChanged() { + if (this.widget.del._focusNode.hasFocus && this.widget.del._currentBody != _SearchBody.suggestions) { + this.widget.del.showSuggestions(this.context); + } + } + + void _onQueryChanged() { + this.setState(() => { }); + } + + void _onSearchBodyChanged() { + this.setState(() => { }); + } + + public override Widget build(BuildContext context) { + MaterialD.debugCheckHasMaterialLocalizations(context); + + ThemeData theme = this.widget.del.appBarTheme(context); + string searchFieldLabel = MaterialLocalizations.of(context).searchFieldLabel; + Widget body = null; + switch (this.widget.del._currentBody) { + case _SearchBody.suggestions: + body = new KeyedSubtree( + key: new ValueKey<_SearchBody>(_SearchBody.suggestions), + child: this.widget.del.buildSuggestions(context) + ); + break; + case _SearchBody.results: + body = new KeyedSubtree( + key: new ValueKey<_SearchBody>(_SearchBody.results), + child: this.widget.del.buildResults(context) + ); + break; + } + + string routeName; + switch (Theme.of(this.context).platform) { + case RuntimePlatform.IPhonePlayer: + routeName = ""; + break; + case RuntimePlatform.Android: + routeName = searchFieldLabel; + break; + } + + return new Scaffold( + appBar: new AppBar( + backgroundColor: theme.primaryColor, + iconTheme: theme.primaryIconTheme, + textTheme: theme.primaryTextTheme, + brightness: theme.primaryColorBrightness, + leading: this.widget.del.buildLeading(context), + title: new TextField( + controller: this.queryTextController, + focusNode: this.widget.del._focusNode, + style: theme.textTheme.title, + textInputAction: TextInputAction.search, + onSubmitted: (string _) => { this.widget.del.showResults(context); }, + decoration: new InputDecoration( + border: InputBorder.none, + hintText: searchFieldLabel + ) + ), + actions: this.widget.del.buildActions(context) + ), + body: new AnimatedSwitcher( + duration: new TimeSpan(0, 0, 0, 0, 300), + child: body + ) + ); + } + + TextEditingController queryTextController { + get { return this.widget.del._queryTextController; } + } + } +} \ No newline at end of file diff --git a/Runtime/material/search.cs.meta b/Runtime/material/search.cs.meta new file mode 100644 index 00000000..d08e6aee --- /dev/null +++ b/Runtime/material/search.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 421b92c9ff7d642af9b2e60e013c297d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/widgets/pages.cs b/Runtime/widgets/pages.cs index faab8093..ee314532 100644 --- a/Runtime/widgets/pages.cs +++ b/Runtime/widgets/pages.cs @@ -7,6 +7,8 @@ namespace Unity.UIWidgets.widgets { public abstract class PageRoute : ModalRoute { public readonly bool fullscreenDialog; + public PageRoute() {} + public PageRoute(RouteSettings settings, bool fullscreenDialog = false) : base(settings) { this.fullscreenDialog = fullscreenDialog; } diff --git a/Runtime/widgets/routes.cs b/Runtime/widgets/routes.cs index 594042d9..035bb042 100644 --- a/Runtime/widgets/routes.cs +++ b/Runtime/widgets/routes.cs @@ -471,8 +471,9 @@ public override Widget build(BuildContext context) { } public abstract class ModalRoute : LocalHistoryRouteTransitionRoute { - protected ModalRoute(RouteSettings settings) : base(settings) { - } + + protected ModalRoute() {} + protected ModalRoute(RouteSettings settings) : base(settings) { } public static Color _kTransparent = new Color(0x00000000); From ca73aa8daa781da58c429181ae828169126c8937 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Thu, 21 Nov 2019 16:18:29 +0800 Subject: [PATCH 20/28] Cleanup. --- Runtime/ui/geometry.cs | 2 +- Runtime/ui/painting/draw_cmd.cs | 4 +--- Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs | 3 ++- 3 files changed, 4 insertions(+), 5 deletions(-) diff --git a/Runtime/ui/geometry.cs b/Runtime/ui/geometry.cs index c009d238..a6230747 100644 --- a/Runtime/ui/geometry.cs +++ b/Runtime/ui/geometry.cs @@ -546,7 +546,7 @@ public float area { } public float margin { - get { return this.width * this.height; } + get { return this.width + this.height; } } public static readonly Rect zero = new Rect(0, 0, 0, 0); diff --git a/Runtime/ui/painting/draw_cmd.cs b/Runtime/ui/painting/draw_cmd.cs index 5fee3176..909fa335 100644 --- a/Runtime/ui/painting/draw_cmd.cs +++ b/Runtime/ui/painting/draw_cmd.cs @@ -1,6 +1,4 @@ -using UnityEngine; - -namespace Unity.UIWidgets.ui { +namespace Unity.UIWidgets.ui { public abstract class DrawCmd { public abstract uiRect bounds(float margin); } diff --git a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs index dc4447c0..1441b1c9 100644 --- a/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs +++ b/Runtime/ui/renderer/cmdbufferCanvas/rendering/canvas_impl.cs @@ -862,7 +862,8 @@ void _drawPicture(Picture picture, bool needsSave = true) { break; } - case DrawImage cmd: {this._drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, + case DrawImage cmd: { + this._drawImage(cmd.image, (uiOffset.fromOffset(cmd.offset)).Value, uiPaint.fromPaint(cmd.paint)); break; } From 0fe7f3aafadaabf6a4528d01f7026da173c6fe87 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Thu, 21 Nov 2019 17:14:51 +0800 Subject: [PATCH 21/28] Fix iOS issue. --- Runtime/Plugins/platform/ios/UIWidgetsTextInputPlugin.mm | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Runtime/Plugins/platform/ios/UIWidgetsTextInputPlugin.mm b/Runtime/Plugins/platform/ios/UIWidgetsTextInputPlugin.mm index f25a25f2..f48bb0fa 100755 --- a/Runtime/Plugins/platform/ios/UIWidgetsTextInputPlugin.mm +++ b/Runtime/Plugins/platform/ios/UIWidgetsTextInputPlugin.mm @@ -425,6 +425,10 @@ - (NSUInteger)incrementOffsetPosition:(NSUInteger)position { - (UITextPosition*)positionFromPosition:(UITextPosition*)position offset:(NSInteger)offset { NSUInteger offsetPosition = ((UIWidgetsTextPosition*)position).index; + NSInteger newLocation = (NSInteger)offsetPosition + offset; + if (newLocation < 0 || newLocation > (NSInteger)self.text.length) { + return nil; + } if (offset >= 0) { for (NSInteger i = 0; i < offset && offsetPosition < self.text.length; ++i) offsetPosition = [self incrementOffsetPosition:offsetPosition]; @@ -552,8 +556,8 @@ - (void)updateEditingState { NSUInteger selectionBase = ((UIWidgetsTextPosition*)_selectedTextRange.start).index; NSUInteger selectionExtent = ((UIWidgetsTextPosition*)_selectedTextRange.end).index; - NSUInteger composingBase = 0; - NSUInteger composingExtent = 0; + NSUInteger composingBase = -1; + NSUInteger composingExtent = -1; if (self.markedTextRange != nil) { composingBase = ((UIWidgetsTextPosition*)self.markedTextRange.start).index; composingExtent = ((UIWidgetsTextPosition*)self.markedTextRange.end).index; From cfd937ff1cf1d06db1eea188829c601491df8d17 Mon Sep 17 00:00:00 2001 From: "xingwei.zhu" Date: Thu, 21 Nov 2019 18:38:15 +0800 Subject: [PATCH 22/28] fix PLUGIN --- Runtime/Plugins/platform/ios/UIWidgetsTextInputPlugin.mm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Runtime/Plugins/platform/ios/UIWidgetsTextInputPlugin.mm b/Runtime/Plugins/platform/ios/UIWidgetsTextInputPlugin.mm index f48bb0fa..62cd35c4 100755 --- a/Runtime/Plugins/platform/ios/UIWidgetsTextInputPlugin.mm +++ b/Runtime/Plugins/platform/ios/UIWidgetsTextInputPlugin.mm @@ -556,8 +556,8 @@ - (void)updateEditingState { NSUInteger selectionBase = ((UIWidgetsTextPosition*)_selectedTextRange.start).index; NSUInteger selectionExtent = ((UIWidgetsTextPosition*)_selectedTextRange.end).index; - NSUInteger composingBase = -1; - NSUInteger composingExtent = -1; + NSUInteger composingBase = 0; + NSUInteger composingExtent = 0; if (self.markedTextRange != nil) { composingBase = ((UIWidgetsTextPosition*)self.markedTextRange.start).index; composingExtent = ((UIWidgetsTextPosition*)self.markedTextRange.end).index; From 0cb0081311d87d648854988b2875b147e15e3788 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Fri, 22 Nov 2019 12:17:49 +0800 Subject: [PATCH 23/28] Fix bug in shifted box. --- Runtime/rendering/shifted_box.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Runtime/rendering/shifted_box.cs b/Runtime/rendering/shifted_box.cs index 5197ead7..359da663 100644 --- a/Runtime/rendering/shifted_box.cs +++ b/Runtime/rendering/shifted_box.cs @@ -601,7 +601,7 @@ public RenderFractionallySizedOverflowBox( public float? widthFactor { get { return this._widthFactor; } set { - if (this._widthFactor != value) { + if (this._widthFactor == value) { return; } @@ -615,7 +615,7 @@ public float? widthFactor { public float? heightFactor { get { return this._heightFactor; } set { - if (this._heightFactor != value) { + if (this._heightFactor == value) { return; } @@ -842,7 +842,7 @@ public RenderBaseline( public float baseline { get { return this._baseline; } set { - if (this._baseline != value) { + if (this._baseline == value) { return; } @@ -857,7 +857,7 @@ public float baseline { public TextBaseline baselineType { get { return this._baselineType; } set { - if (this._baselineType != value) { + if (this._baselineType == value) { return; } From 056ec24d2dd3b15244eaf06339805c94bc190d69 Mon Sep 17 00:00:00 2001 From: Json <785123832@qq.com> Date: Fri, 22 Nov 2019 17:46:03 +0800 Subject: [PATCH 24/28] [Widget] Add Cupertino ActivityIndicator Widget --- Runtime/cupertino/activity_indicator.cs | 130 +++++++++++++++++++ Runtime/cupertino/activity_indicator.cs.meta | 11 ++ Runtime/cupertino/button.cs | 1 - Runtime/cupertino/slider.cs | 2 + 4 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 Runtime/cupertino/activity_indicator.cs create mode 100644 Runtime/cupertino/activity_indicator.cs.meta diff --git a/Runtime/cupertino/activity_indicator.cs b/Runtime/cupertino/activity_indicator.cs new file mode 100644 index 00000000..bea234ab --- /dev/null +++ b/Runtime/cupertino/activity_indicator.cs @@ -0,0 +1,130 @@ +using System; +using Unity.UIWidgets.animation; +using Unity.UIWidgets.foundation; +using Unity.UIWidgets.ui; +using Unity.UIWidgets.widgets; +using UnityEngine; +using Canvas = Unity.UIWidgets.ui.Canvas; +using Color = Unity.UIWidgets.ui.Color; + +namespace Unity.UIWidgets.cupertino { + static class CupertinoActivityIndicatorUtils { + public const float _kDefaultIndicatorRadius = 10.0f; + public const float _kTwoPI = Mathf.PI * 2.0f; + public const int _kTickCount = 12; + public const int _kHalfTickCount = _kTickCount / 2; + public static readonly Color _kTickColor = CupertinoColors.lightBackgroundGray; + public static readonly Color _kActiveTickColor = new Color(0xFF9D9D9D); + } + + public class CupertinoActivityIndicator : StatefulWidget { + public CupertinoActivityIndicator( + Key key = null, + bool animating = true, + float radius = CupertinoActivityIndicatorUtils._kDefaultIndicatorRadius + ) : base(key: key) { + D.assert(radius > 0); + this.animating = animating; + this.radius = radius; + } + + public readonly bool animating; + public readonly float radius; + + public override State createState() { + return new _CupertinoActivityIndicatorState(); + } + } + + class _CupertinoActivityIndicatorState : TickerProviderStateMixin { + AnimationController _controller; + + public override void initState() { + base.initState(); + this._controller = new AnimationController( + duration: TimeSpan.FromSeconds(1), + vsync: this + ); + + if (this.widget.animating) { + this._controller.repeat(); + } + } + + public override void didUpdateWidget(StatefulWidget oldWidget) { + base.didUpdateWidget(oldWidget: oldWidget); + if (oldWidget is CupertinoActivityIndicator _oldWidget) { + if (this.widget.animating != _oldWidget.animating) { + if (this.widget.animating) { + this._controller.repeat(); + } + else { + this._controller.stop(); + } + } + } + } + + public override void dispose() { + this._controller.dispose(); + base.dispose(); + } + + public override Widget build(BuildContext context) { + return new SizedBox( + height: this.widget.radius * 2, + width: this.widget.radius * 2, + child: new CustomPaint( + painter: new _CupertinoActivityIndicatorPainter( + position: this._controller, + radius: this.widget.radius + ) + ) + ); + } + } + + class _CupertinoActivityIndicatorPainter : AbstractCustomPainter { + public _CupertinoActivityIndicatorPainter( + Animation position, + float radius + ) : base(repaint: position) { + this.tickFundamentalRRect = RRect.fromLTRBXY( + left: -radius, + top: 1.0f * radius / CupertinoActivityIndicatorUtils._kDefaultIndicatorRadius, + right: -radius / 2.0f, + bottom: -1.0f * radius / CupertinoActivityIndicatorUtils._kDefaultIndicatorRadius, + radiusX: 1.0f, + radiusY: 1.0f + ); + this.position = position; + } + + readonly Animation position; + readonly RRect tickFundamentalRRect; + + public override void paint(Canvas canvas, Size size) { + Paint paint = new Paint(); + + canvas.save(); + canvas.translate(size.width / 2.0f, size.height / 2.0f); + + int activeTick = (CupertinoActivityIndicatorUtils._kTickCount * this.position.value).floor(); + + for (int i = 0; i < CupertinoActivityIndicatorUtils._kTickCount; ++i) { + float t = (((i + activeTick) % CupertinoActivityIndicatorUtils._kTickCount) / + CupertinoActivityIndicatorUtils._kHalfTickCount).clamp(0, 1); + paint.color = Color.lerp(a: CupertinoActivityIndicatorUtils._kActiveTickColor, + b: CupertinoActivityIndicatorUtils._kTickColor, t: t); + canvas.drawRRect(rect: this.tickFundamentalRRect, paint: paint); + canvas.rotate(-CupertinoActivityIndicatorUtils._kTwoPI / CupertinoActivityIndicatorUtils._kTickCount); + } + + canvas.restore(); + } + + public override bool shouldRepaint(CustomPainter oldPainter) { + return (oldPainter as _CupertinoActivityIndicatorPainter).position != this.position; + } + } +} \ No newline at end of file diff --git a/Runtime/cupertino/activity_indicator.cs.meta b/Runtime/cupertino/activity_indicator.cs.meta new file mode 100644 index 00000000..840bed11 --- /dev/null +++ b/Runtime/cupertino/activity_indicator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a08f265049c0646969c9b5adafa6916f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/cupertino/button.cs b/Runtime/cupertino/button.cs index a308237e..bb75998c 100644 --- a/Runtime/cupertino/button.cs +++ b/Runtime/cupertino/button.cs @@ -1,5 +1,4 @@ using System; -using RSG; using Unity.UIWidgets.animation; using Unity.UIWidgets.foundation; using Unity.UIWidgets.gestures; diff --git a/Runtime/cupertino/slider.cs b/Runtime/cupertino/slider.cs index 09988286..0d58acb7 100644 --- a/Runtime/cupertino/slider.cs +++ b/Runtime/cupertino/slider.cs @@ -174,6 +174,8 @@ public _RenderCupertinoSlider( this._divisions = divisions; this._activeColor = activeColor; this._onChanged = onChanged; + this.onChangeStart = onChangeStart; + this.onChangeEnd = onChangeEnd; this._drag = new HorizontalDragGestureRecognizer(); this._drag.onStart = this._handleDragStart; this._drag.onUpdate = this._handleDragUpdate; From 690b86d6ca0b5450d5a6b20609e47fab1da208f9 Mon Sep 17 00:00:00 2001 From: "xingwei.zhu" Date: Fri, 22 Nov 2019 18:05:59 +0800 Subject: [PATCH 25/28] fix raster cache error on low-end android devices --- Runtime/editor/window_config.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Runtime/editor/window_config.cs b/Runtime/editor/window_config.cs index 609d1d57..f985ddaf 100644 --- a/Runtime/editor/window_config.cs +++ b/Runtime/editor/window_config.cs @@ -4,7 +4,12 @@ namespace Unity.UIWidgets.editor { public class WindowConfig { public readonly bool disableRasterCache; - public static float MaxRasterImageSize = 4096; +#if UNITY_ANDROID + //make API compatible to low-end Android devices + public static float MaxRasterImageSize = 2048; +#else + public static float MaxRasterImageSize = 4096; +#endif static bool? _disableComputeBuffer = null; From 18ebe774b69441ef7067b73553817922e99d1e6c Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Mon, 25 Nov 2019 15:36:26 +0800 Subject: [PATCH 26/28] Fix bug in Paragraph.setText. --- Runtime/ui/painting/draw_cmd.cs | 71 +++++--------------------- Runtime/ui/renderer/common/draw_cmd.cs | 69 +++++-------------------- Runtime/ui/txt/paragraph.cs | 2 +- 3 files changed, 28 insertions(+), 114 deletions(-) diff --git a/Runtime/ui/painting/draw_cmd.cs b/Runtime/ui/painting/draw_cmd.cs index 909fa335..9aa7089e 100644 --- a/Runtime/ui/painting/draw_cmd.cs +++ b/Runtime/ui/painting/draw_cmd.cs @@ -1,91 +1,70 @@ -namespace Unity.UIWidgets.ui { +namespace Unity.UIWidgets.ui { public abstract class DrawCmd { - public abstract uiRect bounds(float margin); } - public abstract class StateUpdateDrawCmd : DrawCmd { - public override uiRect bounds(float margin) { - return uiRectHelper.zero; - } + public class DrawSave : DrawCmd { } - public class DrawSave : StateUpdateDrawCmd { - } - - public class DrawSaveLayer : StateUpdateDrawCmd { + public class DrawSaveLayer : DrawCmd { public Rect rect; public Paint paint; } - public class DrawRestore : StateUpdateDrawCmd { + public class DrawRestore : DrawCmd { } - public class DrawTranslate : StateUpdateDrawCmd { + public class DrawTranslate : DrawCmd { public float dx; public float dy; } - public class DrawScale : StateUpdateDrawCmd { + public class DrawScale : DrawCmd { public float sx; public float? sy; } - public class DrawRotate : StateUpdateDrawCmd { + public class DrawRotate : DrawCmd { public float radians; public Offset offset; } - public class DrawSkew : StateUpdateDrawCmd { + public class DrawSkew : DrawCmd { public float sx; public float sy; } - public class DrawConcat : StateUpdateDrawCmd { + public class DrawConcat : DrawCmd { public Matrix3 matrix; } - public class DrawResetMatrix : StateUpdateDrawCmd { + public class DrawResetMatrix : DrawCmd { } - public class DrawSetMatrix : StateUpdateDrawCmd { + public class DrawSetMatrix : DrawCmd { public Matrix3 matrix; } - public class DrawClipRect : StateUpdateDrawCmd { + public class DrawClipRect : DrawCmd { public Rect rect; } - public class DrawClipRRect : StateUpdateDrawCmd { + public class DrawClipRRect : DrawCmd { public RRect rrect; } - public class DrawClipPath : StateUpdateDrawCmd { + public class DrawClipPath : DrawCmd { public Path path; } public class DrawPath : DrawCmd { public Path path; public Paint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.fromRect(this.path.getBoundsWithMargin(margin)).Value; - } } public class DrawImage : DrawCmd { public Image image; public Offset offset; public Paint paint; - - // TODO: Should divide by device pixel ratio here, which is not available as - // this DrawCmd is created. This bounds should only used as an upper bound, - // assuming that device pixel ratio is always >= 1 - public override uiRect bounds(float margin) { - return uiRectHelper.fromLTWH( - this.offset.dx - margin, this.offset.dy - margin, - this.image.width + 2 * margin, - this.image.height + 2 * margin); - } } public class DrawImageRect : DrawCmd { @@ -93,10 +72,6 @@ public class DrawImageRect : DrawCmd { public Rect src; public Rect dst; public Paint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.fromRect(this.dst.inflate(margin)).Value; - } } public class DrawImageNine : DrawCmd { @@ -105,33 +80,15 @@ public class DrawImageNine : DrawCmd { public Rect center; public Rect dst; public Paint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.fromLTRB( - this.dst.left + ((this.center.left - this.src.left) * this.src.width) - margin, - this.dst.top + ((this.center.top - this.src.top) * this.src.height) - margin, - this.dst.right - ((this.src.right - this.center.right) * this.src.width) + margin, - this.dst.bottom - ((this.src.bottom - this.center.bottom) * this.src.height) + margin - ); - } } public class DrawPicture : DrawCmd { public Picture picture; - public override uiRect bounds(float margin) { - return uiRectHelper.fromRect(this.picture.paintBounds.inflate(margin)).Value; - } } public class DrawTextBlob : DrawCmd { public TextBlob? textBlob; public Offset offset; public Paint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.fromRect(this.textBlob.Value.boundsInText - .translate(this.offset.dx, this.offset.dy) - .inflate(margin)).Value; - } } } \ No newline at end of file diff --git a/Runtime/ui/renderer/common/draw_cmd.cs b/Runtime/ui/renderer/common/draw_cmd.cs index 63723a57..91255b91 100644 --- a/Runtime/ui/renderer/common/draw_cmd.cs +++ b/Runtime/ui/renderer/common/draw_cmd.cs @@ -1,16 +1,9 @@ namespace Unity.UIWidgets.ui { public abstract class uiDrawCmd : PoolObject { public abstract void release(); - public abstract uiRect bounds(float margin); } - public abstract class uiStateUpdateDrawCmd : uiDrawCmd { - public override uiRect bounds(float margin) { - return uiRectHelper.zero; - } - } - - public class uiDrawSave : uiStateUpdateDrawCmd { + public class uiDrawSave : uiDrawCmd { public uiDrawSave() { } @@ -24,7 +17,7 @@ public override void release() { } } - public class uiDrawSaveLayer : uiStateUpdateDrawCmd { + public class uiDrawSaveLayer : uiDrawCmd { public uiDrawSaveLayer() { } @@ -47,7 +40,7 @@ public override void clear() { public uiPaint paint; } - public class uiDrawRestore : uiStateUpdateDrawCmd { + public class uiDrawRestore : uiDrawCmd { public uiDrawRestore() { } @@ -61,7 +54,7 @@ public override void release() { } } - public class uiDrawTranslate : uiStateUpdateDrawCmd { + public class uiDrawTranslate : uiDrawCmd { public uiDrawTranslate() { } @@ -80,7 +73,7 @@ public override void release() { public float dy; } - public class uiDrawScale : uiStateUpdateDrawCmd { + public class uiDrawScale : uiDrawCmd { public uiDrawScale() { } @@ -99,7 +92,7 @@ public override void release() { public float? sy; } - public class uiDrawRotate : uiStateUpdateDrawCmd { + public class uiDrawRotate : uiDrawCmd { public uiDrawRotate() { } @@ -122,7 +115,7 @@ public override void clear() { public uiOffset? offset; } - public class uiDrawSkew : uiStateUpdateDrawCmd { + public class uiDrawSkew : uiDrawCmd { public uiDrawSkew() { } @@ -141,7 +134,7 @@ public override void release() { public float sy; } - public class uiDrawConcat : uiStateUpdateDrawCmd { + public class uiDrawConcat : uiDrawCmd { public uiDrawConcat() { } @@ -162,7 +155,7 @@ public override void clear() { public uiMatrix3? matrix; } - public class uiDrawResetMatrix : uiStateUpdateDrawCmd { + public class uiDrawResetMatrix : uiDrawCmd { public uiDrawResetMatrix() { } @@ -176,7 +169,7 @@ public override void release() { } } - public class uiDrawSetMatrix : uiStateUpdateDrawCmd { + public class uiDrawSetMatrix : uiDrawCmd { public uiDrawSetMatrix() { } @@ -197,7 +190,7 @@ public override void clear() { public uiMatrix3? matrix; } - public class uiDrawClipRect : uiStateUpdateDrawCmd { + public class uiDrawClipRect : uiDrawCmd { public uiDrawClipRect() { } @@ -218,7 +211,7 @@ public override void clear() { public uiRect? rect; } - public class uiDrawClipRRect : uiStateUpdateDrawCmd { + public class uiDrawClipRRect : uiDrawCmd { public uiDrawClipRRect() { } @@ -239,7 +232,7 @@ public override void clear() { public RRect rrect; } - public class uiDrawClipPath : uiStateUpdateDrawCmd { + public class uiDrawClipPath : uiDrawCmd { public uiDrawClipPath() { } @@ -285,10 +278,6 @@ public override void clear() { public uiPath path; public uiPaint paint; - - public override uiRect bounds(float margin) { - return this.path.getBoundsWithMargin(margin); - } } public class uiDrawImage : uiDrawCmd { @@ -315,17 +304,6 @@ public override void clear() { public Image image; public uiOffset? offset; public uiPaint paint; - - - // TODO: Should divide by device pixel ratio here, which is not available as - // this DrawCmd is created. This bounds should only used as an upper bound, - // assuming that device pixel ratio is always >= 1 - public override uiRect bounds(float margin) { - return uiRectHelper.fromLTWH( - this.offset.Value.dx - margin, this.offset.Value.dy - margin, - this.image.width + 2 * margin, - this.image.height + 2 * margin); - } } public class uiDrawImageRect : uiDrawCmd { @@ -355,10 +333,6 @@ public override void clear() { public uiRect? src; public uiRect? dst; public uiPaint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.inflate(this.dst.Value, margin); - } } public class uiDrawImageNine : uiDrawCmd { @@ -391,15 +365,6 @@ public override void clear() { public uiRect? center; public uiRect? dst; public uiPaint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.fromLTRB( - this.dst.Value.left + ((this.center.Value.left - this.src.Value.left) * this.src.Value.width) - margin, - this.dst.Value.top + ((this.center.Value.top - this.src.Value.top) * this.src.Value.height) - margin, - this.dst.Value.right - ((this.src.Value.right - this.center.Value.right) * this.src.Value.width) + margin, - this.dst.Value.bottom - ((this.src.Value.bottom - this.center.Value.bottom) * this.src.Value.height) + margin - ); - } } public class uiDrawPicture : uiDrawCmd { @@ -421,9 +386,6 @@ public override void clear() { } public Picture picture; - public override uiRect bounds(float margin) { - return uiRectHelper.fromRect(this.picture.paintBounds.inflate(margin)).Value; - } } public class uiDrawTextBlob : uiDrawCmd { @@ -450,10 +412,5 @@ public override void clear() { public TextBlob? textBlob; public uiOffset? offset; public uiPaint paint; - - public override uiRect bounds(float margin) { - return uiRectHelper.fromRect(this.textBlob.Value.boundsInText.translate( - this.offset.Value.dx, this.offset.Value.dy).inflate(margin)).Value; - } } } \ No newline at end of file diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index ad044d66..77a62849 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -676,8 +676,8 @@ internal void setText(string text, StyledRuns runs, bool skipRequestCharacters = this._needsLayout = true; if (!skipRequestCharacters) { for (int i = 0; i < runs.size; i++) { - var style = runs.getStyle(i); var run = runs.getRun(i); + var style = run.style; var font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; font.RequestCharactersInTextureSafe(text.Substring(run.start, run.end - run.start), style.UnityFontSize, style.UnityFontStyle); From aff89288aab5daa36b7ab996985e614b3c3872d7 Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Mon, 25 Nov 2019 16:43:09 +0800 Subject: [PATCH 27/28] Fix bug. --- Runtime/painting/text_painter.cs | 6 +++++- Runtime/ui/txt/paragraph.cs | 10 ++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/Runtime/painting/text_painter.cs b/Runtime/painting/text_painter.cs index e93f54d1..88be9c5a 100644 --- a/Runtime/painting/text_painter.cs +++ b/Runtime/painting/text_painter.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using Unity.UIWidgets.foundation; using Unity.UIWidgets.service; using Unity.UIWidgets.ui; @@ -214,6 +215,9 @@ public void layout(float minWidth = 0.0f, float maxWidth = float.PositiveInfinit this._text.build(builder, this.textScaleFactor); this._paragraph = builder.build(); } + else { + Debug.Log($"Use Old Paragraph [{DateTime.Now.Millisecond}]"); + } this._lastMinWidth = minWidth; this._lastMaxWidth = maxWidth; diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index 77a62849..d8068008 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -318,8 +318,7 @@ public void paint(Canvas canvas, Offset offset) { } public void layout(ParagraphConstraints constraints) { - if ((!this._needsLayout && this._width == constraints.width) || - this._text == null || this._text.isEmpty()) { + if (!this._needsLayout && this._width == constraints.width) { return; } @@ -458,12 +457,17 @@ public void layout(ParagraphConstraints constraints) { int textCount = textEnd - textStart; // Keep track of the pointer to _glyphPositions in the start of this run int glyphPositionStyleRunStart = pGlyphPositions; + Font font = FontManager.instance.getOrCreate( + style.fontFamily, + style.fontWeight, + style.fontStyle).font; // Ellipsize the text if ellipsis string is set, and this is the last lineStyleRun of // the current line, and this is the last line or max line is not set if (!string.IsNullOrEmpty(ellipsis) && !hardBreak && !this._width.isInfinite() && lineStyleRunIndex == lineStyleRunCount - 1 && (lineNumber == lineLimit - 1 || this._paragraphStyle.maxLines == null)) { + font.RequestCharactersInTextureSafe(ellipsis, style.UnityFontSize, style.UnityFontStyle); float ellipsisWidth = Layout.measureText(ellipsis, style); // Find the minimum number of characters to truncate, so that the truncated text @@ -533,8 +537,6 @@ public void layout(ParagraphConstraints constraints) { } // Create paint record - var font = FontManager.instance.getOrCreate(style.fontFamily, - style.fontWeight, style.fontStyle).font; var metrics = FontMetrics.fromFont(font, style.UnityFontSize); PaintRecord paintRecord = new PaintRecord(style, runXOffset, 0, builder.make(), metrics, advance); From 9d5d355616cc9331147a611aa846797188d139ee Mon Sep 17 00:00:00 2001 From: Yuncong Zhang Date: Mon, 25 Nov 2019 17:07:16 +0800 Subject: [PATCH 28/28] Revert all optimization on paragraph layout. --- Runtime/painting/text_painter.cs | 6 +----- Runtime/ui/txt/layout.cs | 4 ++-- Runtime/ui/txt/paragraph.cs | 25 +++---------------------- 3 files changed, 6 insertions(+), 29 deletions(-) diff --git a/Runtime/painting/text_painter.cs b/Runtime/painting/text_painter.cs index 88be9c5a..7449ca25 100644 --- a/Runtime/painting/text_painter.cs +++ b/Runtime/painting/text_painter.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using Unity.UIWidgets.foundation; using Unity.UIWidgets.service; using Unity.UIWidgets.ui; @@ -215,9 +214,6 @@ public void layout(float minWidth = 0.0f, float maxWidth = float.PositiveInfinit this._text.build(builder, this.textScaleFactor); this._paragraph = builder.build(); } - else { - Debug.Log($"Use Old Paragraph [{DateTime.Now.Millisecond}]"); - } this._lastMinWidth = minWidth; this._lastMaxWidth = maxWidth; diff --git a/Runtime/ui/txt/layout.cs b/Runtime/ui/txt/layout.cs index 3e65ce38..e13c8bff 100644 --- a/Runtime/ui/txt/layout.cs +++ b/Runtime/ui/txt/layout.cs @@ -18,7 +18,7 @@ public static float measureText(string text, TextStyle style) { } else { Font font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; - // font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); + font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); for (int i = 0; i < text.Length; i++) { char ch = text[i]; if (font.getGlyphInfo(ch, out var glyphInfo, style.UnityFontSize, style.UnityFontStyle)) { @@ -100,7 +100,7 @@ public static float computeCharWidths(float offset, string text, int start, int else { Font font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; // TODO: it is kind of a waste to require the entire string for this style, but SubString causes alloc - // font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); + font.RequestCharactersInTextureSafe(text, style.UnityFontSize, style.UnityFontStyle); for (int i = 0; i < count; i++) { char ch = text[start + i]; if (ch == '\t') { diff --git a/Runtime/ui/txt/paragraph.cs b/Runtime/ui/txt/paragraph.cs index d8068008..8304485f 100644 --- a/Runtime/ui/txt/paragraph.cs +++ b/Runtime/ui/txt/paragraph.cs @@ -457,17 +457,12 @@ public void layout(ParagraphConstraints constraints) { int textCount = textEnd - textStart; // Keep track of the pointer to _glyphPositions in the start of this run int glyphPositionStyleRunStart = pGlyphPositions; - Font font = FontManager.instance.getOrCreate( - style.fontFamily, - style.fontWeight, - style.fontStyle).font; // Ellipsize the text if ellipsis string is set, and this is the last lineStyleRun of // the current line, and this is the last line or max line is not set if (!string.IsNullOrEmpty(ellipsis) && !hardBreak && !this._width.isInfinite() && lineStyleRunIndex == lineStyleRunCount - 1 && (lineNumber == lineLimit - 1 || this._paragraphStyle.maxLines == null)) { - font.RequestCharactersInTextureSafe(ellipsis, style.UnityFontSize, style.UnityFontStyle); float ellipsisWidth = Layout.measureText(ellipsis, style); // Find the minimum number of characters to truncate, so that the truncated text @@ -537,6 +532,8 @@ public void layout(ParagraphConstraints constraints) { } // Create paint record + var font = FontManager.instance.getOrCreate(style.fontFamily, + style.fontWeight, style.fontStyle).font; var metrics = FontMetrics.fromFont(font, style.UnityFontSize); PaintRecord paintRecord = new PaintRecord(style, runXOffset, 0, builder.make(), metrics, advance); @@ -665,26 +662,10 @@ internal int totalCodeUnitsInLine(int lineNumber) { return nextLineStart - lineStart; } - internal void setText(string text, StyledRuns runs, bool skipRequestCharacters = false) { - if (text == null || text.isEmpty()) { - this.clear(); - this._text = text; - this._runs = runs; - this._needsLayout = false; - return; - } + internal void setText(string text, StyledRuns runs) { this._text = text; this._runs = runs; this._needsLayout = true; - if (!skipRequestCharacters) { - for (int i = 0; i < runs.size; i++) { - var run = runs.getRun(i); - var style = run.style; - var font = FontManager.instance.getOrCreate(style.fontFamily, style.fontWeight, style.fontStyle).font; - font.RequestCharactersInTextureSafe(text.Substring(run.start, run.end - run.start), - style.UnityFontSize, style.UnityFontStyle); - } - } } public void setParagraphStyle(ParagraphStyle style) {