using System; using System.Collections.Generic; using System.Linq; using Unity.UIWidgets.foundation; using Unity.UIWidgets.ui; namespace Unity.UIWidgets.painting { public enum BorderStyle { none, solid, } public class BorderSide : IEquatable { public BorderSide( Color color = null, double width = 1.0, BorderStyle style = BorderStyle.solid ) { this.color = color ?? Color.black; this.width = width; this.style = style; } public static BorderSide merge(BorderSide a, BorderSide b) { D.assert(a != null); D.assert(b != null); D.assert(canMerge(a, b)); bool aIsNone = a.style == BorderStyle.none && a.width == 0.0; bool bIsNone = b.style == BorderStyle.none && b.width == 0.0; if (aIsNone && bIsNone) { return none; } if (aIsNone) { return b; } if (bIsNone) { return a; } D.assert(a.color == b.color); D.assert(a.style == b.style); return new BorderSide( color: a.color, // == b.color width: a.width + b.width, style: a.style // == b.style ); } public readonly Color color; public readonly double width; public readonly BorderStyle style; public static readonly BorderSide none = new BorderSide(width: 0.0); public BorderSide copyWith( Color color = null, double? width = null, BorderStyle? style = null ) { D.assert(width == null || width >= 0.0); return new BorderSide( color: color ?? this.color, width: width ?? this.width, style: style ?? this.style ); } public BorderSide scale(double t) { return new BorderSide( color: this.color, width: Math.Max(0.0, this.width * t), style: t <= 0.0 ? BorderStyle.none : this.style ); } public Paint toPaint() { switch (this.style) { case BorderStyle.solid: return new Paint { color = this.color, strokeWidth = this.width, style = PaintingStyle.stroke, }; case BorderStyle.none: return new Paint { color = Color.clear, strokeWidth = 0.0, style = PaintingStyle.stroke }; } return null; } public static bool canMerge(BorderSide a, BorderSide b) { D.assert(a != null); D.assert(b != null); if ((a.style == BorderStyle.none && a.width == 0.0) || (b.style == BorderStyle.none && b.width == 0.0)) { return true; } return a.style == b.style && a.color == b.color; } public static BorderSide lerp(BorderSide a, BorderSide b, double t) { D.assert(a != null); D.assert(b != null); if (t == 0.0) { return a; } if (t == 1.0) { return b; } double width = MathUtils.lerpDouble(a.width, b.width, t); if (width < 0.0) { return none; } if (a.style == b.style) { return new BorderSide( color: Color.lerp(a.color, b.color, t), width: width, style: a.style // == b.style ); } Color colorA = null, colorB = null; switch (a.style) { case BorderStyle.solid: colorA = a.color; break; case BorderStyle.none: colorA = a.color.withAlpha(0x00); break; } switch (b.style) { case BorderStyle.solid: colorB = b.color; break; case BorderStyle.none: colorB = b.color.withAlpha(0x00); break; } return new BorderSide( color: Color.lerp(colorA, colorB, t), width: width, style: BorderStyle.solid ); } public bool Equals(BorderSide other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return Equals(this.color, other.color) && this.width.Equals(other.width) && this.style == other.style; } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != this.GetType()) { return false; } return this.Equals((BorderSide) obj); } public override int GetHashCode() { unchecked { var hashCode = (this.color != null ? this.color.GetHashCode() : 0); hashCode = (hashCode * 397) ^ this.width.GetHashCode(); hashCode = (hashCode * 397) ^ (int) this.style; return hashCode; } } public static bool operator ==(BorderSide left, BorderSide right) { return Equals(left, right); } public static bool operator !=(BorderSide left, BorderSide right) { return !Equals(left, right); } public override string ToString() { return $"{this.GetType()}({this.color}, {this.width:F1}, {this.style})"; } } public abstract class ShapeBorder { protected ShapeBorder() { } public abstract EdgeInsets dimensions { get; } public virtual ShapeBorder add(ShapeBorder other, bool reversed = false) { return null; } public static ShapeBorder operator +(ShapeBorder it, ShapeBorder other) { return it.add(other) ?? other.add(it, reversed: true) ?? new _CompoundBorder(new List {other, it}); } public abstract ShapeBorder scale(double t); public virtual ShapeBorder lerpFrom(ShapeBorder a, double t) { if (a == null) { return this.scale(t); } return null; } public virtual ShapeBorder lerpTo(ShapeBorder b, double t) { if (b == null) { return this.scale(1.0 - t); } return null; } public static ShapeBorder lerp(ShapeBorder a, ShapeBorder b, double t) { ShapeBorder result = null; if (b != null) { result = b.lerpFrom(a, t); } if (result == null && a != null) { result = a.lerpTo(b, t); } return result ?? (t < 0.5 ? a : b); } public abstract Path getOuterPath(Rect rect); public abstract Path getInnerPath(Rect rect); public abstract void paint(Canvas canvas, Rect rect); public override string ToString() { return $"{this.GetType()}()"; } } class _CompoundBorder : ShapeBorder, IEquatable<_CompoundBorder> { public _CompoundBorder(List borders) { D.assert(borders != null); D.assert(borders.Count >= 2); D.assert(!borders.Any(border => border is _CompoundBorder)); this.borders = borders; } public readonly List borders; public override EdgeInsets dimensions { get { return this.borders.Aggregate( EdgeInsets.zero, (previousValue, border) => previousValue.add(border.dimensions)); } } public override ShapeBorder add(ShapeBorder other, bool reversed = false) { if (!(other is _CompoundBorder)) { ShapeBorder ours = reversed ? this.borders.Last() : this.borders.First(); ShapeBorder merged = ours.add(other, reversed: reversed) ?? other.add(ours, reversed: !reversed); if (merged != null) { List result = new List(this.borders); result[reversed ? result.Count - 1 : 0] = merged; return new _CompoundBorder(result); } } List mergedBorders = new List(); if (reversed) { mergedBorders.AddRange(this.borders); } if (other is _CompoundBorder border) { mergedBorders.AddRange(border.borders); } else { mergedBorders.Add(other); } if (!reversed) { mergedBorders.AddRange(this.borders); } return new _CompoundBorder(mergedBorders); } public override ShapeBorder scale(double t) { return new _CompoundBorder( this.borders.Select(border => border.scale(t)).ToList() ); } public override ShapeBorder lerpFrom(ShapeBorder a, double t) { return lerp(a, this, t); } public override ShapeBorder lerpTo(ShapeBorder b, double t) { return lerp(this, b, t); } public new static _CompoundBorder lerp(ShapeBorder a, ShapeBorder b, double t) { D.assert(a is _CompoundBorder || b is _CompoundBorder); List aList = a is _CompoundBorder aBorder ? aBorder.borders : new List {a}; List bList = b is _CompoundBorder bBorder ? bBorder.borders : new List {b}; List results = new List(); int length = Math.Max(aList.Count, bList.Count); for (int index = 0; index < length; index += 1) { ShapeBorder localA = index < aList.Count ? aList[index] : null; ShapeBorder localB = index < bList.Count ? bList[index] : null; if (localA != null && localB != null) { ShapeBorder localResult = localA.lerpTo(localB, t) ?? localB.lerpFrom(localA, t); if (localResult != null) { results.Add(localResult); continue; } } if (localB != null) { results.Add(localB.scale(t)); } if (localA != null) { results.Add(localA.scale(1.0 - t)); } } return new _CompoundBorder(results); } public override Path getInnerPath(Rect rect) { for (int index = 0; index < this.borders.Count - 1; index += 1) { rect = this.borders[index].dimensions.deflateRect(rect); } return this.borders.Last().getInnerPath(rect); } public override Path getOuterPath(Rect rect) { return this.borders.First().getOuterPath(rect); } public override void paint(Canvas canvas, Rect rect) { foreach (ShapeBorder border in this.borders) { border.paint(canvas, rect); rect = border.dimensions.deflateRect(rect); } } public bool Equals(_CompoundBorder other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return this.borders.SequenceEqual(other.borders); } public override bool Equals(object obj) { if (ReferenceEquals(null, obj)) { return false; } if (ReferenceEquals(this, obj)) { return true; } if (obj.GetType() != this.GetType()) { return false; } return this.Equals((_CompoundBorder) obj); } public override int GetHashCode() { return (this.borders != null ? this.borders.GetHashCode() : 0); } public static bool operator ==(_CompoundBorder left, _CompoundBorder right) { return Equals(left, right); } public static bool operator !=(_CompoundBorder left, _CompoundBorder right) { return !Equals(left, right); } public override string ToString() { return string.Join(" + ", ((IList) this.borders).Reverse().Select((border) => border.ToString())); } } public static class BorderUtils { public static void paintBorder(Canvas canvas, Rect rect, BorderSide top = null, BorderSide right = null, BorderSide bottom = null, BorderSide left = null ) { D.assert(canvas != null); D.assert(rect != null); top = top ?? BorderSide.none; right = right ?? BorderSide.none; bottom = bottom ?? BorderSide.none; left = left ?? BorderSide.none; switch (top.style) { case BorderStyle.solid: Paint paint = new Paint { strokeWidth = 0.0, color = top.color, }; Path path = new Path(); path.moveTo(rect.left, rect.top); path.lineTo(rect.right, rect.top); if (top.width == 0.0) { paint.style = PaintingStyle.stroke; } else { paint.style = PaintingStyle.fill; path.lineTo(rect.right - right.width, rect.top + top.width); path.lineTo(rect.left + left.width, rect.top + top.width); } canvas.drawPath(path, paint); break; case BorderStyle.none: break; } switch (right.style) { case BorderStyle.solid: Paint paint = new Paint { strokeWidth = 0.0, color = right.color, }; Path path = new Path(); path.moveTo(rect.right, rect.top); path.lineTo(rect.right, rect.bottom); if (right.width == 0.0) { paint.style = PaintingStyle.stroke; } else { paint.style = PaintingStyle.fill; path.lineTo(rect.right - right.width, rect.bottom - bottom.width); path.lineTo(rect.right - right.width, rect.top + top.width); } canvas.drawPath(path, paint); break; case BorderStyle.none: break; } switch (bottom.style) { case BorderStyle.solid: Paint paint = new Paint { strokeWidth = 0.0, color = bottom.color, }; Path path = new Path(); path.moveTo(rect.right, rect.bottom); path.lineTo(rect.left, rect.bottom); if (bottom.width == 0.0) { paint.style = PaintingStyle.stroke; } else { paint.style = PaintingStyle.fill; path.lineTo(rect.left + left.width, rect.bottom - bottom.width); path.lineTo(rect.right - right.width, rect.bottom - bottom.width); } canvas.drawPath(path, paint); break; case BorderStyle.none: break; } switch (left.style) { case BorderStyle.solid: Paint paint = new Paint { strokeWidth = 0.0, color = left.color, }; Path path = new Path(); path.moveTo(rect.left, rect.bottom); path.lineTo(rect.left, rect.top); if (left.width == 0.0) { paint.style = PaintingStyle.stroke; } else { paint.style = PaintingStyle.fill; path.lineTo(rect.left + left.width, rect.top + top.width); path.lineTo(rect.left + left.width, rect.bottom - bottom.width); } canvas.drawPath(path, paint); break; case BorderStyle.none: break; } } } }