浏览代码

add Path.contains.

add DecorationImage and ShapeDocorations.

TODO: Gradient & Shadow.
/main
kg 6 年前
当前提交
8ccc89d6
共有 33 个文件被更改,包括 3836 次插入585 次删除
  1. 106
      Runtime/painting/alignment.cs
  2. 47
      Runtime/painting/basic_types.cs
  3. 245
      Runtime/painting/border_radius.cs
  4. 459
      Runtime/painting/borders.cs
  5. 279
      Runtime/painting/box_border.cs
  6. 258
      Runtime/painting/box_decoration.cs
  7. 11
      Runtime/painting/box_fit.cs
  8. 58
      Runtime/painting/box_shadow.cs
  9. 5
      Runtime/painting/clip.cs
  10. 34
      Runtime/painting/decoration.cs
  11. 176
      Runtime/painting/decoration_image.cs
  12. 33
      Runtime/painting/edge_insets.cs
  13. 5
      Runtime/painting/gradient.cs
  14. 4
      Runtime/rendering/proxy_box.cs
  15. 49
      Runtime/rendering/stack.cs
  16. 531
      Runtime/ui/geometry.cs
  17. 30
      Runtime/ui/painting/painting.cs
  18. 617
      Runtime/ui/painting/path.cs
  19. 34
      Runtime/widgets/basic.cs
  20. 2
      Runtime/widgets/scroll_view.cs
  21. 23
      Tests/Editor/CanvasAndLayers.cs
  22. 34
      Tests/Editor/RenderBoxes.cs
  23. 4
      Tests/Editor/Widgets.cs
  24. 146
      Runtime/painting/beveled_rectangle_border.cs
  25. 11
      Runtime/painting/beveled_rectangle_border.cs.meta
  26. 101
      Runtime/painting/circle_border.cs
  27. 11
      Runtime/painting/circle_border.cs.meta
  28. 331
      Runtime/painting/rounded_rectangle_border.cs
  29. 11
      Runtime/painting/rounded_rectangle_border.cs.meta
  30. 277
      Runtime/painting/shape_decoration.cs
  31. 11
      Runtime/painting/shape_decoration.cs.meta
  32. 467
      Runtime/painting/stadium_border.cs
  33. 11
      Runtime/painting/stadium_border.cs.meta

106
Runtime/painting/alignment.cs


public static readonly Alignment bottomCenter = new Alignment(0.0, 1.0);
public static readonly Alignment bottomRight = new Alignment(1.0, 1.0);
public Alignment add(Alignment other) {
return this + other;
}
public static Alignment operator -(Alignment a, Alignment b) {
return new Alignment(a.x - b.x, a.y - b.y);
}

);
}
public static Alignment lerp(Alignment a, Alignment b, double t) {
if (a == null && b == null) {
return null;
}
if (a == null) {
return new Alignment(MathUtils.lerpDouble(0.0, b.x, t), MathUtils.lerpDouble(0.0, b.y, t));
}
if (b == null) {
return new Alignment(MathUtils.lerpDouble(a.x, 0.0, t), MathUtils.lerpDouble(a.y, 0.0, t));
}
return new Alignment(MathUtils.lerpDouble(a.x, b.x, t), MathUtils.lerpDouble(a.y, b.y, t));
}
public bool Equals(Alignment other) {
if (ReferenceEquals(null, other)) {
return false;

public static bool operator !=(Alignment a, Alignment b) {
return !(a == b);
}
}
public class AlignmentDirectional : IEquatable<AlignmentDirectional> {
public AlignmentDirectional(double start, double y) {
this.start = start;
this.y = y;
}
public double start;
public double y;
public static readonly AlignmentDirectional topStart = new AlignmentDirectional(-1.0, -1.0);
public static readonly AlignmentDirectional topCenter = new AlignmentDirectional(0.0, -1.0);
public static readonly AlignmentDirectional topEnd = new AlignmentDirectional(1.0, -1.0);
public static readonly AlignmentDirectional centerStart = new AlignmentDirectional(-1.0, 0.0);
public static readonly AlignmentDirectional center = new AlignmentDirectional(0.0, 0.0);
public static readonly AlignmentDirectional centerEnd = new AlignmentDirectional(1.0, 0.0);
public static readonly AlignmentDirectional bottomStart = new AlignmentDirectional(-1.0, 1.0);
public static readonly AlignmentDirectional bottomCenter = new AlignmentDirectional(0.0, 1.0);
public static readonly AlignmentDirectional bottomEnd = new AlignmentDirectional(1.0, 1.0);
public AlignmentDirectional add(AlignmentDirectional other) {
return this + other;
}
public static AlignmentDirectional operator -(AlignmentDirectional a, AlignmentDirectional b) {
return new AlignmentDirectional(a.start - b.start, a.y - b.y);
}
public static AlignmentDirectional operator +(AlignmentDirectional a, AlignmentDirectional b) {
return new AlignmentDirectional(a.start + b.start, a.y + b.y);
}
public static AlignmentDirectional operator -(AlignmentDirectional a) {
return new AlignmentDirectional(-a.start, -a.y);
public static AlignmentDirectional operator *(AlignmentDirectional a, double b) {
return new AlignmentDirectional(a.start * b, a.y * b);
}
public static AlignmentDirectional operator /(AlignmentDirectional a, double b) {
return new AlignmentDirectional(a.start / b, a.y / b);
}
public static AlignmentDirectional operator %(AlignmentDirectional a, double b) {
return new AlignmentDirectional(a.start % b, a.y % b);
}
public bool Equals(AlignmentDirectional other) {
if (ReferenceEquals(null, other)) {
return false;
public override string ToString() {
if (this.x == -1.0 && this.y == -1.0) {
return "topLeft";
}
if (this.x == 0.0 && this.y == -1.0) {
return "topCenter";
}
if (this.x == 1.0 && this.y == -1.0) {
return "topRight";
}
if (this.x == -1.0 && this.y == 0.0) {
return "centerLeft";
}
if (this.x == 0.0 && this.y == 0.0) {
return "center";
}
if (this.x == 1.0 && this.y == 0.0) {
return "centerRight";
}
if (this.x == -1.0 && this.y == 1.0) {
return "bottomLeft";
if (ReferenceEquals(this, other)) {
return true;
if (this.x == 0.0 && this.y == 1.0) {
return "bottomCenter";
return this.start.Equals(other.start) && this.y.Equals(other.y);
}
public Alignment resolve(TextDirection direction) {
switch (direction) {
case TextDirection.rtl:
return new Alignment(-this.start, this.y);
case TextDirection.ltr:
return new Alignment(this.start, this.y);
if (this.x == 1.0 && this.y == 1.0) {
return "bottomRight";
return null;
return $"Alignment({this.x:F1}, {this.y:F1})";
}
}
}

47
Runtime/painting/basic_types.cs


using System;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.widgets;
public enum AxisDirection {
up,
right,
down,
left,
public enum RenderComparison {
identical,
metadata,
paint,
layout,
}
public enum Axis {

public enum VerticalDirection {
up,
down,
}
public enum AxisDirection {
up,
right,
down,
left,
}
public static class AxisUtils {

throw new Exception("unknown axisDirection");
}
public static AxisDirection getAxisDirectionFromAxisReverseAndDirectionality(
BuildContext context,
Axis axis,
bool reverse
) {
switch (axis) {
case Axis.horizontal:
D.assert(WidgetsD.debugCheckHasDirectionality(context));
TextDirection textDirection = Directionality.of(context);
AxisDirection axisDirection = textDirectionToAxisDirection(textDirection);
return reverse ? flipAxisDirection(axisDirection) : axisDirection;
case Axis.vertical:
return reverse ? AxisDirection.up : AxisDirection.down;
}
throw new Exception("unknown axisDirection");
}
}
/// The values in this enum are ordered such that they are in increasing order
/// of cost. A value with index N implies all the values with index less than N.
/// For example, [layout] (index 3) implies [paint] (2).
public enum RenderComparison {
identical,
metadata,
paint,
layout,
}
}

245
Runtime/painting/border_radius.cs


using System;
using System.Text;
double topLeft,
double topRight,
double bottomRight,
double bottomLeft) {
this.topLeft = topLeft;
this.topRight = topRight;
this.bottomRight = bottomRight;
this.bottomLeft = bottomLeft;
Radius topLeft,
Radius topRight,
Radius bottomRight,
Radius bottomLeft) {
this.topLeft = topLeft ?? Radius.zero;
this.topRight = topRight ?? Radius.zero;
this.bottomRight = bottomRight ?? Radius.zero;
this.bottomLeft = bottomLeft ?? Radius.zero;
public static BorderRadius all(Radius radius) {
return only(radius, radius, radius, radius);
}
public static BorderRadius vertical(double top, double bottom) {
public static BorderRadius circular(double radius) {
return BorderRadius.all(Radius.circular(radius));
}
public static BorderRadius vertical(Radius top = null, Radius bottom = null) {
return only(top, top, bottom, bottom);
}
public static BorderRadius vertical(double? top = null, double? bottom = null) {
public static BorderRadius horizontal(double left, double right) {
public static BorderRadius horizontal(Radius left = null, Radius right = null) {
return only(left, right, right, left);
}
public static BorderRadius horizontal(double? left = null, double? right = null) {
double topLeft = 0.0, double topRight = 0.0,
double bottomRight = 0.0, double bottomLeft = 0.0) {
Radius topLeft = null, Radius topRight = null,
Radius bottomRight = null, Radius bottomLeft = null) {
public static BorderRadius only(
double? topLeft = null, double? topRight = null,
double? bottomRight = null, double? bottomLeft = null) {
var tlRadius = topLeft != null ? Radius.circular(topLeft.Value) : null;
var trRadius = topRight != null ? Radius.circular(topRight.Value) : null;
var brRadius = bottomRight != null ? Radius.circular(bottomRight.Value) : null;
var blRadius = bottomLeft != null ? Radius.circular(bottomLeft.Value) : null;
return new BorderRadius(tlRadius, trRadius, brRadius, blRadius);
}
public static readonly BorderRadius zero = all(0);
public static readonly BorderRadius zero = all(Radius.zero);
public readonly double topLeft;
public readonly double topRight;
public readonly double bottomRight;
public readonly double bottomLeft;
public readonly Radius topLeft;
public readonly Radius topRight;
public readonly Radius bottomRight;
public readonly Radius bottomLeft;
public RRect toRRect(Rect rect) {
return RRect.fromRectAndCorners(

bottomLeft: this.bottomLeft,
bottomRight: this.bottomRight
bottomRight: this.bottomRight,
bottomLeft: this.bottomLeft
);
}
public static BorderRadius operator -(BorderRadius it, BorderRadius other) {
return BorderRadius.only(
topLeft: it.topLeft - other.topLeft,
topRight: it.topRight - other.topRight,
bottomLeft: it.bottomLeft - other.bottomLeft,
bottomRight: it.bottomRight - other.bottomRight
);
}
public static BorderRadius operator +(BorderRadius it, BorderRadius other) {
return BorderRadius.only(
topLeft: it.topLeft + other.topLeft,
topRight: it.topRight + other.topRight,
bottomLeft: it.bottomLeft + other.bottomLeft,
bottomRight: it.bottomRight + other.bottomRight
);
}
public static BorderRadius operator -(BorderRadius it) {
return BorderRadius.only(
topLeft: -it.topLeft,
topRight: -it.topRight,
bottomLeft: -it.bottomLeft,
bottomRight: -it.bottomRight
);
}
public static BorderRadius operator *(BorderRadius it, double other) {
return BorderRadius.only(
topLeft: it.topLeft * other,
topRight: it.topRight * other,
bottomLeft: it.bottomLeft * other,
bottomRight: it.bottomRight * other
);
}
public static BorderRadius operator /(BorderRadius it, double other) {
return BorderRadius.only(
topLeft: it.topLeft / other,
topRight: it.topRight / other,
bottomLeft: it.bottomLeft / other,
bottomRight: it.bottomRight / other
);
}
public static BorderRadius operator %(BorderRadius it, double other) {
return BorderRadius.only(
topLeft: it.topLeft % other,
topRight: it.topRight % other,
bottomLeft: it.bottomLeft % other,
bottomRight: it.bottomRight % other
);
}
public static BorderRadius lerp(BorderRadius a, BorderRadius b, double t) {
if (a == null && b == null) {
return null;
}
if (a == null) {
return b * t;
}
if (b == null) {
return a * (1.0 - t);
}
return BorderRadius.only(
topLeft: Radius.lerp(a.topLeft, b.topLeft, t),
topRight: Radius.lerp(a.topRight, b.topRight, t),
bottomLeft: Radius.lerp(a.bottomLeft, b.bottomLeft, t),
bottomRight: Radius.lerp(a.bottomRight, b.bottomRight, t)
);
}

return hashCode;
}
}
}
public class BorderWidth : IEquatable<BorderWidth> {
BorderWidth(
double top,
double right,
double bottom,
double left) {
this.top = top;
this.right = right;
this.bottom = bottom;
this.left = left;
}
public static BorderWidth only(
double top = 0, double right = 0,
double bottom = 0, double left = 0) {
return new BorderWidth(top, right, bottom, left);
}
public static BorderWidth all(double width) {
return only(width, width, width, width);
}
public static readonly BorderWidth zero = only();
public readonly double top;
public readonly double right;
public readonly double bottom;
public readonly double left;
public bool Equals(BorderWidth other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return this.top.Equals(other.top)
&& this.right.Equals(other.right)
&& this.bottom.Equals(other.bottom)
&& this.left.Equals(other.left);
}
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;
public override string ToString() {
string visual = null;
if (this.topLeft == this.topRight &&
this.topRight == this.bottomLeft &&
this.bottomLeft == this.bottomRight) {
if (this.topLeft != Radius.zero) {
if (this.topLeft.x == this.topLeft.y) {
visual = $"BorderRadius.circular({this.topLeft.x:F1})";
} else {
visual = $"BorderRadius.all({this.topLeft})";
}
}
} else {
var result = new StringBuilder();
result.Append("BorderRadius.only(");
bool comma = false;
if (this.topLeft != Radius.zero) {
result.Append($"topLeft: {this.topLeft}");
comma = true;
}
if (this.topRight != Radius.zero) {
if (comma) {
result.Append(", ");
}
result.Append($"topRight: {this.topRight}");
comma = true;
}
if (this.bottomLeft != Radius.zero) {
if (comma) {
result.Append(", ");
}
result.Append($"bottomLeft: {this.bottomLeft}");
comma = true;
}
if (this.bottomRight != Radius.zero) {
if (comma) {
result.Append(", ");
}
result.Append($"bottomRight: {this.bottomRight}");
}
result.Append(")");
visual = result.ToString();
return this.Equals((BorderWidth) obj);
}
public override int GetHashCode() {
unchecked {
var hashCode = this.top.GetHashCode();
hashCode = (hashCode * 397) ^ this.right.GetHashCode();
hashCode = (hashCode * 397) ^ this.bottom.GetHashCode();
hashCode = (hashCode * 397) ^ this.left.GetHashCode();
return hashCode;
if (visual != null) {
return visual;
return "BorderRadius.zero";
}
}
}

459
Runtime/painting/borders.cs


using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Unity.UIWidgets.foundation;
public enum BorderStyle {
none,
solid,
}
double width = 1.0
double width = 1.0,
BorderStyle style = BorderStyle.solid
this.color = color ?? new Color(0xFF000000);
this.color = color ?? Color.black;
this.style = style;
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 BorderSide.none;
}
if (aIsNone) {
return b;
}
if (bIsNone) {
return a;
}
D.assert(a.color == b.color);
D.assert(a.style == b.style);
color: a.color,
width: a.width + b.width
color: a.color, // == b.color
width: a.width + b.width,
style: a.style // == b.style
public readonly BorderStyle style;
double? width = null
double? width = null,
BorderStyle? style = null
D.assert(width == null || width >= 0.0);
width: width ?? this.width
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;
}
return a.color == b.color;
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 BorderSide.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(this, other)) {
return true;
}
return Equals(this.color, other.color) && this.width.Equals(other.width);
return Equals(this.color, other.color) && this.width.Equals(other.width) && this.style == other.style;
}
public override bool Equals(object obj) {

public override int GetHashCode() {
unchecked {
return ((this.color != null ? this.color.GetHashCode() : 0) * 397) ^ this.width.GetHashCode();
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<ShapeBorder> {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<ShapeBorder> borders) {
D.assert(borders != null);
D.assert(borders.Count >= 2);
D.assert(!borders.Any(border => border is _CompoundBorder));
this.borders = borders;
}
public readonly List<ShapeBorder> 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<ShapeBorder> result = new List<ShapeBorder>(this.borders);
result[reversed ? result.Count - 1 : 0] = merged;
return new _CompoundBorder(result);
}
}
List<ShapeBorder> mergedBorders = new List<ShapeBorder>();
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 _CompoundBorder.lerp(a, this, t);
}
public override ShapeBorder lerpTo(ShapeBorder b, double t) {
return _CompoundBorder.lerp(this, b, t);
}
public new static _CompoundBorder lerp(ShapeBorder a, ShapeBorder b, double t) {
D.assert(a is _CompoundBorder || b is _CompoundBorder);
List<ShapeBorder> aList = a is _CompoundBorder aBorder ? aBorder.borders : new List<ShapeBorder> {a};
List<ShapeBorder> bList = b is _CompoundBorder bBorder ? bBorder.borders : new List<ShapeBorder> {b};
List<ShapeBorder> results = new List<ShapeBorder>();
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 static bool operator ==(BorderSide lhs, BorderSide rhs) {
return Equals(lhs, rhs);
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<ShapeBorder>) this.borders).Reverse().Select((border) => border.ToString()));
}
public static bool operator !=(BorderSide lhs, BorderSide rhs) {
return !(lhs == rhs);
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;
}
}
}
}

279
Runtime/painting/box_border.cs


using System;
using System.Collections.Generic;
public class Border : IEquatable<Border> {
public enum BoxShape {
rectangle,
circle,
}
public class Border : ShapeBorder, IEquatable<Border> {
public Border(
BorderSide top = null,
BorderSide right = null,

this.left = left ?? BorderSide.none;
}
public readonly BorderSide top;
public readonly BorderSide right;
public readonly BorderSide bottom;
public readonly BorderSide left;
double width = 1.0
double width = 1.0,
BorderStyle style = BorderStyle.solid
BorderSide side = new BorderSide(color: color, width: width);
BorderSide side = new BorderSide(color: color, width: width, style: style);
D.assert(a != null);
D.assert(b != null);
D.assert(BorderSide.canMerge(a.top, b.top));
D.assert(BorderSide.canMerge(a.right, b.right));
D.assert(BorderSide.canMerge(a.bottom, b.bottom));
D.assert(BorderSide.canMerge(a.left, b.left));
return new Border(
top: BorderSide.merge(a.top, b.top),
right: BorderSide.merge(a.right, b.right),

}
public EdgeInsets dimensions {
public readonly BorderSide top;
public readonly BorderSide right;
public readonly BorderSide bottom;
public readonly BorderSide left;
public override EdgeInsets dimensions {
get {
return EdgeInsets.fromLTRB(
this.left.width,

}
}
public bool isUniform {
get { return this.isSameColor && this.isSameWidth && this.isSameStyle; }
}
public bool isSameColor {
get {
Color topColor = this.top.color;

}
}
public Border add(Border other) {
if (BorderSide.canMerge(this.top, other.top) &&
BorderSide.canMerge(this.right, other.right) &&
BorderSide.canMerge(this.bottom, other.bottom) &&
BorderSide.canMerge(this.left, other.left)) {
return merge(this, other);
public bool isSameStyle {
get {
var topStyle = this.top.style;
return this.right.style == topStyle
&& this.bottom.style == topStyle
&& this.left.style == topStyle;
}
}
public override ShapeBorder add(ShapeBorder other, bool reversed = false) {
if (!(other is Border border)) {
return null;
}
if (BorderSide.canMerge(this.top, border.top) &&
BorderSide.canMerge(this.right, border.right) &&
BorderSide.canMerge(this.bottom, border.bottom) &&
BorderSide.canMerge(this.left, border.left)) {
return merge(this, border);
public void paint(Canvas canvas, Rect rect, BorderRadius borderRadius = null) {
if (this.isSameColor && this.isSameWidth) {
if (borderRadius == null) {
var width = this.top.width;
var paint = new Paint {
color = this.top.color,
strokeWidth = width,
style = PaintingStyle.stroke
};
public override ShapeBorder scale(double t) {
return new Border(
top: this.top.scale(t),
right: this.right.scale(t),
bottom: this.bottom.scale(t),
left: this.left.scale(t)
);
}
canvas.drawRect(rect.deflate(width / 2), paint);
return;
}
public override ShapeBorder lerpFrom(ShapeBorder a, double t) {
if (a is Border border) {
return Border.lerp(border, this, t);
}
var outer = borderRadius.toRRect(rect);
if (this.top.width == 0) {
var paint = new Paint {
color = this.top.color,
style = PaintingStyle.stroke
};
return base.lerpFrom(a, t);
}
canvas.drawRRect(outer, paint);
return;
}
public override ShapeBorder lerpTo(ShapeBorder b, double t) {
if (b is Border border) {
return Border.lerp(this, border, t);
}
return base.lerpTo(b, t);
}
{
var inner = outer.deflate(this.top.width);
var paint = new Paint {
color = this.top.color,
};
public static Border lerp(Border a, Border b, double t) {
if (a == null && b == null) {
return null;
}
if (a == null) {
return (Border) b.scale(t);
}
if (b == null) {
return (Border) a.scale(1.0 - t);
}
return new Border(
top: BorderSide.lerp(a.top, b.top, t),
right: BorderSide.lerp(a.right, b.right, t),
bottom: BorderSide.lerp(a.bottom, b.bottom, t),
left: BorderSide.lerp(a.left, b.left, t)
);
}
public override void paint(Canvas canvas, Rect rect) {
this.paint(canvas, rect, BoxShape.rectangle, null);
}
canvas.drawDRRect(outer, inner, paint);
public void paint(Canvas canvas, Rect rect,
BoxShape shape = BoxShape.rectangle,
BorderRadius borderRadius = null) {
if (this.isUniform) {
switch (this.top.style) {
case BorderStyle.none:
return;
case BorderStyle.solid:
switch (shape) {
case BoxShape.circle:
D.assert(borderRadius == null,
"A borderRadius can only be given for rectangular boxes.");
_paintUniformBorderWithCircle(canvas, rect, this.top);
break;
case BoxShape.rectangle:
if (borderRadius != null) {
_paintUniformBorderWithRadius(canvas, rect, this.top, borderRadius);
} else {
_paintUniformBorderWithRectangle(canvas, rect, this.top);
}
break;
}
return;
return;
D.assert(shape == BoxShape.rectangle, "A border can only be drawn as a circle if it is uniform.");
BorderUtils.paintBorder(canvas, rect,
top: this.top, right: this.right, bottom: this.bottom, left: this.left);
}
{
var paint = new Paint {
color = this.top.color,
};
var path = new Path();
path.moveTo(rect.left, rect.top);
path.lineTo(rect.right, rect.top);
if (this.top.width == 0) {
paint.style = PaintingStyle.stroke;
} else {
path.lineTo(rect.right - this.right.width, rect.top + this.top.width);
path.lineTo(rect.left + this.right.width, rect.top + this.top.width);
}
public override Path getInnerPath(Rect rect) {
var path = new Path();
path.addRect(this.dimensions.deflateRect(rect));
return path;
}
canvas.drawPath(path, paint);
}
public override Path getOuterPath(Rect rect) {
var path = new Path();
path.addRect(rect);
return path;
}
{
var paint = new Paint {
color = this.right.color,
};
var path = new Path();
path.moveTo(rect.right, rect.top);
path.lineTo(rect.right, rect.bottom);
if (this.right.width == 0) {
paint.style = PaintingStyle.stroke;
} else {
path.lineTo(rect.right - this.right.width, rect.bottom - this.bottom.width);
path.lineTo(rect.right - this.right.width, rect.top + this.top.width);
}
static void _paintUniformBorderWithRadius(Canvas canvas, Rect rect, BorderSide side,
BorderRadius borderRadius) {
D.assert(side.style != BorderStyle.none);
Paint paint = new Paint {
color = side.color,
};
canvas.drawPath(path, paint);
RRect outer = borderRadius.toRRect(rect);
double width = side.width;
if (width == 0.0) {
paint.style = PaintingStyle.stroke;
paint.strokeWidth = 0.0;
canvas.drawRRect(outer, paint);
} else {
RRect inner = outer.deflate(width);
canvas.drawDRRect(outer, inner, paint);
}
{
var paint = new Paint {
color = this.bottom.color,
};
var path = new Path();
path.moveTo(rect.right, rect.bottom);
path.lineTo(rect.left, rect.bottom);
if (this.bottom.width == 0) {
paint.style = PaintingStyle.stroke;
} else {
path.lineTo(rect.left + this.left.width, rect.bottom - this.bottom.width);
path.lineTo(rect.right - this.right.width, rect.bottom - this.bottom.width);
}
static void _paintUniformBorderWithCircle(Canvas canvas, Rect rect, BorderSide side) {
D.assert(side.style != BorderStyle.none);
double width = side.width;
Paint paint = side.toPaint();
double radius = (rect.shortestSide - width) / 2.0;
canvas.drawCircle(rect.center, radius, paint);
}
canvas.drawPath(path, paint);
}
{
var paint = new Paint {
color = this.left.color,
};
var path = new Path();
path.moveTo(rect.left, rect.bottom);
path.lineTo(rect.left, rect.top);
if (this.left.width == 0) {
paint.style = PaintingStyle.stroke;
} else {
path.lineTo(rect.left + this.left.width, rect.top + this.top.width);
path.lineTo(rect.left + this.left.width, rect.bottom - this.bottom.width);
}
canvas.drawPath(path, paint);
}
static void _paintUniformBorderWithRectangle(Canvas canvas, Rect rect, BorderSide side) {
D.assert(side.style != BorderStyle.none);
double width = side.width;
Paint paint = side.toPaint();
canvas.drawRect(rect.deflate(width / 2.0), paint);
}
public bool Equals(Border other) {

hashCode = (hashCode * 397) ^ (this.left != null ? this.left.GetHashCode() : 0);
return hashCode;
}
}
public override string ToString() {
if (this.isUniform) {
return $"{this.GetType()}.all({this.top})";
}
List<string> arguments = new List<string>();
if (this.top != BorderSide.none) {
arguments.Add($"top: {this.top}");
}
if (this.right != BorderSide.none) {
arguments.Add($"right: {this.right}");
}
if (this.bottom != BorderSide.none) {
arguments.Add($"bottom: {this.bottom}");
}
if (this.left != BorderSide.none) {
arguments.Add($"left: {this.left}");
}
return $"{this.GetType()}({string.Join(", ", arguments)})";
}
}
}

258
Runtime/painting/box_decoration.cs


Border border = null,
BorderRadius borderRadius = null,
List<BoxShadow> boxShadow = null,
Gradient gradient = null
Gradient gradient = null,
BlendMode? backgroundBlendMode = null,
BoxShape shape = BoxShape.rectangle
D.assert(
backgroundBlendMode == null || color != null || gradient != null,
"backgroundBlendMode applies to BoxDecoration\'s background color or " +
"gradient, but no color or gradient was provided."
);
this.color = color;
this.image = image;
this.border = border;

this.backgroundBlendMode = backgroundBlendMode;
this.shape = shape;
}
public override bool debugAssertIsValid() {
D.assert(this.shape != BoxShape.circle || this.borderRadius == null);
return base.debugAssertIsValid();
}
public readonly Color color;

public readonly List<BoxShadow> boxShadow;
public readonly Gradient gradient;
public readonly BlendMode? backgroundBlendMode;
public readonly BoxShape shape;
public override EdgeInsets padding {
get { return this.border?.dimensions; }
}
public override EdgeInsets padding {
get {
if (this.border != null) {
return this.border.dimensions;
}
public BoxDecoration scale(double factor) {
return new BoxDecoration(
color: Color.lerp(null, this.color, factor),
image: this.image,
border: Border.lerp(null, this.border, factor),
borderRadius: BorderRadius.lerp(null, this.borderRadius, factor),
boxShadow: BoxShadow.lerpList(null, this.boxShadow, factor),
gradient: this.gradient?.scale(factor),
backgroundBlendMode: this.backgroundBlendMode,
shape: this.shape
);
}
return base.padding;
public override bool isComplex {
get { return this.boxShadow != null; }
}
public override Decoration lerpFrom(Decoration a, double t) {
if (a == null) {
return this.scale(t);
if (a is BoxDecoration boxDecoration) {
return BoxDecoration.lerp(boxDecoration, this, t);
}
return base.lerpFrom(a, t);
public override BoxPainter createBoxPainter(VoidCallback onChanged = null) {
return new _BoxDecorationPainter(this, onChanged);
public override Decoration lerpTo(Decoration b, double t) {
if (b == null) {
return this.scale(1.0 - t);
}
if (b is BoxDecoration boxDecoration) {
return BoxDecoration.lerp(this, boxDecoration, t);
}
return base.lerpTo(b, t);
}
public static BoxDecoration lerp(BoxDecoration a, BoxDecoration b, double t) {
if (a == null && b == null) {
return null;
}
if (a == null) {
return b.scale(t);
}
if (b == null) {
return a.scale(1.0 - t);
}
if (t == 0.0) {
return a;
}
if (t == 1.0) {
return b;
}
return new BoxDecoration(
color: Color.lerp(a.color, b.color, t),
image: t < 0.5 ? a.image : b.image,
border: Border.lerp(a.border, b.border, t),
borderRadius: BorderRadius.lerp(a.borderRadius, b.borderRadius, t),
boxShadow: BoxShadow.lerpList(a.boxShadow, b.boxShadow, t),
gradient: Gradient.lerp(a.gradient, b.gradient, t),
backgroundBlendMode: t < 0.5 ? a.backgroundBlendMode : b.backgroundBlendMode,
shape: t < 0.5 ? a.shape : b.shape
);
}
public bool Equals(BoxDecoration other) {

if (ReferenceEquals(this, other)) {
return true;
}
return Equals(this.color, other.color)
&& Equals(this.image, other.image)
&& Equals(this.border, other.border)
&& Equals(this.borderRadius, other.borderRadius)
&& Equals(this.boxShadow, other.boxShadow)
&& Equals(this.gradient, other.gradient);
return Equals(this.color, other.color) && Equals(this.image, other.image) &&
Equals(this.border, other.border) && Equals(this.borderRadius, other.borderRadius) &&
Equals(this.boxShadow, other.boxShadow) && Equals(this.gradient, other.gradient) &&
this.backgroundBlendMode == other.backgroundBlendMode && this.shape == other.shape;
}
public override bool Equals(object obj) {

hashCode = (hashCode * 397) ^ (this.borderRadius != null ? this.borderRadius.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.boxShadow != null ? this.boxShadow.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.gradient != null ? this.gradient.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ this.backgroundBlendMode.GetHashCode();
hashCode = (hashCode * 397) ^ (int) this.shape;
public static bool operator ==(BoxDecoration a, BoxDecoration b) {
return Equals(a, b);
public static bool operator ==(BoxDecoration left, BoxDecoration right) {
return Equals(left, right);
public static bool operator !=(BoxDecoration a, BoxDecoration b) {
return !(a == b);
public static bool operator !=(BoxDecoration left, BoxDecoration right) {
return !Equals(left, right);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.whitespace;
properties.add(new DiagnosticsProperty<Color>("color", this.color, defaultValue: null));
properties.add(new DiagnosticsProperty<DecorationImage>("image", this.image, defaultValue: null));
properties.add(new DiagnosticsProperty<Border>("border", this.border, defaultValue: null));
properties.add(
new DiagnosticsProperty<BorderRadius>("borderRadius", this.borderRadius, defaultValue: null));
//properties.add(new IterableProperty<BoxShadow>("boxShadow", boxShadow, defaultValue: null, style: DiagnosticsTreeStyle.whitespace));
properties.add(new DiagnosticsProperty<Gradient>("gradient", this.gradient, defaultValue: null));
properties.add(new DiagnosticsProperty<Color>("color", this.color,
defaultValue: Diagnostics.kNullDefaultValue));
properties.add(new DiagnosticsProperty<DecorationImage>("image", this.image,
defaultValue: Diagnostics.kNullDefaultValue));
properties.add(new DiagnosticsProperty<Border>("border", this.border,
defaultValue: Diagnostics.kNullDefaultValue));
properties.add(new DiagnosticsProperty<BorderRadius>("borderRadius", this.borderRadius,
defaultValue: Diagnostics.kNullDefaultValue));
properties.add(new EnumerableProperty<BoxShadow>("boxShadow", this.boxShadow,
defaultValue: Diagnostics.kNullDefaultValue, style: DiagnosticsTreeStyle.whitespace));
properties.add(new DiagnosticsProperty<Gradient>("gradient", this.gradient,
defaultValue: Diagnostics.kNullDefaultValue));
properties.add(new DiagnosticsProperty<BlendMode?>("backgroundBlendMode", this.backgroundBlendMode,
defaultValue: Diagnostics.kNullDefaultValue));
properties.add(new DiagnosticsProperty<BoxShape>("shape", this.shape, defaultValue: BoxShape.rectangle));
}
public override bool hitTest(Size size, Offset position) {
D.assert((Offset.zero & size).contains(position));
switch (this.shape) {
case BoxShape.rectangle:
if (this.borderRadius != null) {
RRect bounds = this.borderRadius.toRRect(Offset.zero & size);
return bounds.contains(position);
}
return true;
case BoxShape.circle:
Offset center = size.center(Offset.zero);
double distance = (position - center).distance;
return distance <= Math.Min(size.width, size.height) / 2.0;
}
return false;
}
public override BoxPainter createBoxPainter(VoidCallback onChanged = null) {
D.assert(onChanged != null || this.image == null);
return new _BoxDecorationPainter(this, onChanged);
}
}
public class _BoxDecorationPainter : BoxPainter {
class _BoxDecorationPainter : BoxPainter {
D.assert(decoration != null);
public readonly BoxDecoration _decoration;
readonly BoxDecoration _decoration;
public Paint _cachedBackgroundPaint;
Paint _cachedBackgroundPaint;
public Rect _rectForCachedBackgroundPaint;
Rect _rectForCachedBackgroundPaint;
public Paint _getBackgroundPaint(Rect rect) {
if (this._cachedBackgroundPaint == null) {
var paint = new Paint();
Paint _getBackgroundPaint(Rect rect) {
D.assert(rect != null);
D.assert(this._decoration.gradient != null || this._rectForCachedBackgroundPaint == null);
if (this._cachedBackgroundPaint == null ||
(this._decoration.gradient != null && this._rectForCachedBackgroundPaint != rect)) {
var paint = new Paint();
if (this._decoration.backgroundBlendMode != null) {
paint.blendMode = this._decoration.backgroundBlendMode.Value;
}
if (this._decoration.gradient != null) {
//paint.shader = this._decoration.gradient.createShader(rect);
this._rectForCachedBackgroundPaint = rect;
}
this._cachedBackgroundPaint = paint;
}

public void _paintBox(Canvas canvas, Rect rect, Paint paint) {
if (this._decoration.borderRadius == null) {
canvas.drawRect(rect, paint);
} else {
canvas.drawRRect(this._decoration.borderRadius.toRRect(rect), paint);
void _paintBox(Canvas canvas, Rect rect, Paint paint) {
switch (this._decoration.shape) {
case BoxShape.circle:
D.assert(this._decoration.borderRadius == null);
Offset center = rect.center;
double radius = rect.shortestSide / 2.0;
canvas.drawCircle(center, radius, paint);
break;
case BoxShape.rectangle:
if (this._decoration.borderRadius == null) {
canvas.drawRect(rect, paint);
} else {
canvas.drawRRect(this._decoration.borderRadius.toRRect(rect), paint);
}
break;
public void _paintShadows(Canvas canvas, Rect rect) {
void _paintShadows(Canvas canvas, Rect rect) {
if (this._decoration.boxShadow == null) {
return;
}

Rect bounds = rect.shift(boxShadow.offset).inflate(boxShadow.spreadRadius);
// canvas.drawRectShadow(bounds, paint);
this._paintBox(canvas, bounds, paint);
public void _paintBackgroundColor(Canvas canvas, Rect rect) {
void _paintBackgroundColor(Canvas canvas, Rect rect) {
var paint = this._getBackgroundPaint(rect);
this._paintBox(canvas, rect, paint);
this._paintBox(canvas, rect, this._getBackgroundPaint(rect));
public void _paintBackgroundImage(Canvas canvas, Rect rect, ImageConfiguration configuration) {
DecorationImagePainter _imagePainter;
void _paintBackgroundImage(Canvas canvas, Rect rect, ImageConfiguration configuration) {
this._imagePainter = this._imagePainter ?? this._decoration.image.createPainter(this.onChanged);
Path clipPath = null;
switch (this._decoration.shape) {
case BoxShape.circle:
clipPath = new Path();
clipPath.addOval(rect);
break;
case BoxShape.rectangle:
if (this._decoration.borderRadius != null) {
clipPath = new Path();
clipPath.addRRect(this._decoration.borderRadius.toRRect(rect));
}
break;
}
this._imagePainter.paint(canvas, rect, clipPath, configuration);
public override void dispose() {
base.dispose();
public override void Dispose() {
this._imagePainter?.Dispose();
base.Dispose();
D.assert(configuration != null);
D.assert(configuration.size != null);
if (this._decoration.border != null) {
this._decoration.border.paint(
canvas,
rect,
borderRadius: this._decoration.borderRadius
);
}
this._decoration.border?.paint(
canvas,
rect,
shape: this._decoration.shape,
borderRadius: this._decoration.borderRadius
);
}
public override string ToString() {
return $"BoxPainter for {this._decoration}";
}
}
}

11
Runtime/painting/box_fit.cs


this.destination = destination;
}
public Size source;
public Size destination;
public readonly Size source;
public readonly Size destination;
if (inputSize.height <= 0.0 || inputSize.width <= 0.0 || outputSize.height <= 0.0 ||
outputSize.width <= 0.0) {
if (inputSize.height <= 0.0
|| inputSize.width <= 0.0
|| outputSize.height <= 0.0
|| outputSize.width <= 0.0) {
Size sourceSize = null;
Size destinationSize = null;
switch (fit) {

58
Runtime/painting/box_shadow.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.ui;
namespace Unity.UIWidgets.painting {

double blurRadius = 0.0,
double spreadRadius = 0.0
) {
this.color = color ?? new Color(0xFF000000);
this.color = color ?? Color.black;
this.offset = offset ?? Offset.zero;
this.blurRadius = blurRadius;
this.spreadRadius = spreadRadius;

public Paint toPaint() {
return new Paint {
color = this.color,
//blurSigma = this.blurSigma
//blurSigma = this.blurSigma, TODO
public BoxShadow scale(double factor) {
return new BoxShadow(
color: this.color,
offset: this.offset * factor,
blurRadius: this.blurRadius * factor,
spreadRadius: this.spreadRadius * factor
);
}
public static BoxShadow lerp(BoxShadow a, BoxShadow b, double t) {
if (a == null && b == null) {
return null;
}
if (a == null) {
return b.scale(t);
}
if (b == null) {
return a.scale(1.0 - t);
}
return new BoxShadow(
color: Color.lerp(a.color, b.color, t),
offset: Offset.lerp(a.offset, b.offset, t),
blurRadius: MathUtils.lerpDouble(a.blurRadius, b.blurRadius, t),
spreadRadius: MathUtils.lerpDouble(a.spreadRadius, b.spreadRadius, t)
);
}
public static List<BoxShadow> lerpList(List<BoxShadow> a, List<BoxShadow> b, double t) {
if (a == null && b == null) {
return null;
}
a = a ?? new List<BoxShadow>();
b = b ?? new List<BoxShadow>();
List<BoxShadow> result = new List<BoxShadow>();
int commonLength = Math.Min(a.Count, b.Count);
for (int i = 0; i < commonLength; i += 1) {
result.Add(BoxShadow.lerp(a[i], b[i], t));
}
for (int i = commonLength; i < a.Count; i += 1) {
result.Add(a[i].scale(1.0 - t));
}
for (int i = commonLength; i < b.Count; i += 1) {
result.Add(b[i].scale(t));
}
return result;
}
public bool Equals(BoxShadow other) {
if (ReferenceEquals(null, other)) {
return false;

public static bool operator !=(BoxShadow a, BoxShadow b) {
return !(a == b);
}
public override string ToString() {
return $"BoxShadow({this.color}, {this.offset}, {this.blurRadius}, {this.spreadRadius})";
}
}
}

5
Runtime/painting/clip.cs


this.canvas.restore();
}
public void clipPathAndPaint(Path path, Clip clipBehavior, Rect bounds, Action painter) {
this._clipAndPaint((bool doAntiAias) => this.canvas.clipPath(path),
clipBehavior, bounds, painter);
}
public void clipRRectAndPaint(RRect rrect, Clip clipBehavior, Rect bounds, Action painter) {
this._clipAndPaint(doAntiAias => this.canvas.clipRRect(rrect),
clipBehavior, bounds, painter);

34
Runtime/painting/decoration.cs


using System;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.ui;

get { return false; }
}
public virtual Decoration lerpFrom(Decoration a, double t) {
return null;
}
public virtual Decoration lerpTo(Decoration b, double t) {
return null;
}
public static Decoration lerp(Decoration a, Decoration b, double t) {
if (a == null && b == null) {
return null;
}
if (a == null) {
return b.lerpFrom(null, t) ?? b;
}
if (b == null) {
return a.lerpTo(null, t) ?? a;
}
if (t == 0.0) {
return a;
}
if (t == 1.0) {
return b;
}
return b.lerpFrom(a, t)
?? a.lerpTo(b, t)
?? (t < 0.5 ? (a.lerpTo(null, t * 2.0) ?? a) : (b.lerpFrom(null, (t - 0.5) * 2.0) ?? b));
}
public virtual bool hitTest(Size size, Offset position) {
return true;
}

public abstract class BoxPainter {
public abstract class BoxPainter : IDisposable {
protected BoxPainter(VoidCallback onChanged = null) {
this.onChanged = onChanged;
}

public abstract void paint(Canvas canvas, Offset offset, ImageConfiguration configuration);
public virtual void dispose() {
public virtual void Dispose() {
}
}
}

176
Runtime/painting/decoration_image.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.ui;

noRepeat,
}
public class DecorationImage {
public DecorationImage() {
public class DecorationImage : IEquatable<DecorationImage> {
public DecorationImage(
ImageProvider image = null,
ColorFilter colorFilter = null,
BoxFit? fit = null,
Alignment alignment = null,
Rect centerSlice = null,
ImageRepeat repeat = ImageRepeat.noRepeat
) {
D.assert(image != null);
this.image = image;
this.colorFilter = colorFilter;
this.fit = fit;
this.alignment = alignment ?? Alignment.center;
this.centerSlice = centerSlice;
this.repeat = repeat;
}
public readonly ImageProvider image;
public readonly ColorFilter colorFilter;
public readonly BoxFit? fit;
public readonly Alignment alignment;
public readonly Rect centerSlice;
public readonly ImageRepeat repeat;
public DecorationImagePainter createPainter(VoidCallback onChanged) {
D.assert(onChanged != null);
return new DecorationImagePainter(this, onChanged);
}
public bool Equals(DecorationImage other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return Equals(this.image, other.image) && Equals(this.colorFilter, other.colorFilter) &&
this.fit == other.fit && Equals(this.alignment, other.alignment) &&
Equals(this.centerSlice, other.centerSlice) && this.repeat == other.repeat;
}
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((DecorationImage) obj);
}
public override int GetHashCode() {
unchecked {
var hashCode = (this.image != null ? this.image.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.colorFilter != null ? this.colorFilter.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ this.fit.GetHashCode();
hashCode = (hashCode * 397) ^ (this.alignment != null ? this.alignment.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.centerSlice != null ? this.centerSlice.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (int) this.repeat;
return hashCode;
}
}
public static bool operator ==(DecorationImage left, DecorationImage right) {
return Equals(left, right);
}
public static bool operator !=(DecorationImage left, DecorationImage right) {
return !Equals(left, right);
}
public override string ToString() {
var properties = new List<string>();
properties.Add($"{this.image}");
if (this.colorFilter != null) {
properties.Add($"{this.colorFilter}");
}
if (this.fit != null &&
!(this.fit == BoxFit.fill && this.centerSlice != null) &&
!(this.fit == BoxFit.scaleDown && this.centerSlice == null)) {
properties.Add($"{this.fit}");
}
properties.Add($"{this.alignment}");
if (this.centerSlice != null) {
properties.Add($"centerSlice: {this.centerSlice}");
}
if (this.repeat != ImageRepeat.noRepeat) {
properties.Add($"{this.repeat}");
}
return $"{this.GetType()}({string.Join(", ", properties)})";
}
}
public class DecorationImagePainter : IDisposable {
internal DecorationImagePainter(DecorationImage details, VoidCallback onChanged) {
D.assert(details != null);
this._details = details;
this._onChanged = onChanged;
}
readonly DecorationImage _details;
readonly VoidCallback _onChanged;
ImageStream _imageStream;
ImageInfo _image;
public void paint(Canvas canvas, Rect rect, Path clipPath, ImageConfiguration configuration) {
D.assert(canvas != null);
D.assert(rect != null);
D.assert(configuration != null);
ImageStream newImageStream = this._details.image.resolve(configuration);
if (newImageStream.key != this._imageStream?.key) {
this._imageStream?.removeListener(this._imageListener);
this._imageStream = newImageStream;
this._imageStream.addListener(this._imageListener);
}
if (this._image == null) {
return;
}
if (clipPath != null) {
canvas.save();
canvas.clipPath(clipPath);
}
ImageUtils.paintImage(
canvas: canvas,
rect: rect,
image: this._image.image,
scale: this._image.scale,
colorFilter: this._details.colorFilter,
fit: this._details.fit,
alignment: this._details.alignment,
centerSlice: this._details.centerSlice,
repeat: this._details.repeat
);
if (clipPath != null) {
canvas.restore();
}
}
void _imageListener(ImageInfo value, bool synchronousCall) {
if (this._image == value) {
return;
}
this._image = value;
D.assert(this._onChanged != null);
if (!synchronousCall) {
this._onChanged();
}
}
public void Dispose() {
this._imageStream?.removeListener(this._imageListener);
}
public override string ToString() {
return $"{this.GetType()}(stream: {this._imageStream}, image: {this._image}) for {this._details}";
}
}

33
Runtime/painting/edge_insets.cs


return this.vertical;
}
throw new InvalidOperationException();
throw new Exception("unknown axis");
}
public Size collapsedSize {

);
}
public static EdgeInsets lerp(EdgeInsets a, EdgeInsets b, double t) {
if (a == null && b == null) {
return null;
}
if (a == null) {
return b * t;
}
if (b == null) {
return a * (1.0 - t);
}
return EdgeInsets.fromLTRB(
MathUtils.lerpDouble(a.left, b.left, t),
MathUtils.lerpDouble(a.top, b.top, t),
MathUtils.lerpDouble(a.right, b.right, t),
MathUtils.lerpDouble(a.bottom, b.bottom, t)
);
}
public EdgeInsets copyWith(
double? left = null,
double? top = null,

public static bool operator !=(EdgeInsets a, EdgeInsets b) {
return !(a == b);
}
public override string ToString() {
if (this.left == 0.0 && this.right == 0.0 && this.top == 0.0 && this.bottom == 0.0) {
return "EdgeInsets.zero";
}
if (this.left == this.right && this.right == this.top && this.top == this.bottom) {
return $"EdgeInsets.all({this.left:F1})";
}
return $"EdgeInsets({this.left:F1}, " +
$"{this.top:F1}, " +
$"{this.right:F1}, " +
$"{this.bottom:F1})";
}
}
}

5
Runtime/painting/gradient.cs


namespace Unity.UIWidgets.painting {
public abstract class Gradient {
public abstract Gradient scale(double factor);
public static Gradient lerp(Gradient a, Gradient b, double t) {
return null;
}
}
}

4
Runtime/rendering/proxy_box.cs


}
if (this._painter != null) {
this._painter.dispose();
this._painter.Dispose();
this._painter = null;
}

public override void detach() {
if (this._painter != null) {
this._painter.dispose();
this._painter.Dispose();
this._painter = null;
}

49
Runtime/rendering/stack.cs


public class RenderStack : RenderBoxContainerDefaultsMixinContainerRenderObjectMixinRenderBox<RenderBox,
StackParentData> {
public RenderStack(
TextDirection? textDirection,
AlignmentDirectional alignment = null) {
this._alignment = alignment ?? AlignmentDirectional.topStart;
this._textDirection = textDirection ?? TextDirection.ltr;
Alignment alignment = null) {
this._alignment = alignment ?? Alignment.topLeft;
this._fit = fit ?? StackFit.loose;
this._overflow = overflow ?? Overflow.clip;
this.addAll(children);

}
}
Alignment _resolvedAlignment;
Alignment _alignment;
void _resolve() {
if (this._resolvedAlignment != null) {
return;
}
this._resolvedAlignment = this.alignment.resolve(this.textDirection);
}
void _markNeedResolution() {
this._resolvedAlignment = null;
this.markNeedsLayout();
}
AlignmentDirectional _alignment;
public AlignmentDirectional alignment {
public Alignment alignment {
get { return this._alignment; }
set {
if (this._alignment == value) {

this._markNeedResolution();
}
}
TextDirection _textDirection;
public TextDirection textDirection {
get { return this._textDirection; }
set {
if (this._textDirection == value) {
return;
}
this._textDirection = value;
this._markNeedResolution();
this.markNeedsLayout();
}
}

}
protected override void performLayout() {
this._resolve();
D.assert(this._resolvedAlignment != null);
this._hasVisualOverflow = false;
bool hasNonPositionedChildren = false;
if (this.childCount == 0) {

StackParentData childParentData = (StackParentData) child.parentData;
if (!childParentData.isPositioned) {
childParentData.offset = this._resolvedAlignment.alongOffset(this.size - child.size);
childParentData.offset = this._alignment.alongOffset(this.size - child.size);
} else {
BoxConstraints childConstraints = new BoxConstraints();

} else if (childParentData.right != null) {
x = this.size.width - childParentData.right.Value - child.size.width;
} else {
x = this._resolvedAlignment.alongOffset(this.size - child.size).dx;
x = this._alignment.alongOffset(this.size - child.size).dx;
}
if (x < 0.0 || x + child.size.width > this.size.width) {

} else if (childParentData.bottom != null) {
y = this.size.height - childParentData.bottom.Value - child.size.height;
} else {
y = this._resolvedAlignment.alongOffset(this.size - child.size).dy;
y = this._alignment.alongOffset(this.size - child.size).dy;
}
if (y < 0.0 || y + child.size.height > this.size.height) {

public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
base.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<AlignmentDirectional>("alignment", this.alignment));
properties.add(new DiagnosticsProperty<Alignment>("alignment", this.alignment));
properties.add(new EnumProperty<StackFit>("fit", this.fit));
properties.add(new EnumProperty<Overflow>("overflow", this.overflow));
}

531
Runtime/ui/geometry.cs


return Math.Abs(value);
}
public static float abs(this float value) {
return Mathf.Abs(value);
}
}
public static int sign(this float value) {
return (int) Mathf.Sign(value);
}
public static bool isInfinite(this double it) {

}
public static double lerpDouble(double a, double b, double t) {
return a + (b - a) * t;
}
public static float lerpFloat(float a, float b, float t) {
return a + (b - a) * t;
}

return a._dx >= b._dx && a._dy >= b._dy;
}
public static bool operator ==(OffsetBase a, OffsetBase b) {
return Equals(a, b);
}
public static bool operator !=(OffsetBase a, OffsetBase b) {
return !(a == b);
}
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return this._dx.Equals(other._dx) && this._dy.Equals(other._dy);
}

}
return obj is OffsetBase && this.Equals((OffsetBase) obj);
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != this.GetType()) {
return false;
}
return this.Equals((OffsetBase) obj);
}
public override int GetHashCode() {

}
public static bool operator ==(OffsetBase left, OffsetBase right) {
return Equals(left, right);
}
public static bool operator !=(OffsetBase left, OffsetBase right) {
return !Equals(left, right);
}
return this.GetType() + "(" + this._dx.ToString("0.0") + ", " + this._dy.ToString("0.0") + ")";
return $"{this.GetType()}({this._dx:F1}, {this._dy:F1})";
}
}

return Rect.fromLTWH(a.dx, a.dy, other.width, other.height);
}
public static Offset lerp(Offset a, Offset b, double t) {
if (a == null && b == null) {
return null;
}
if (a == null) {
return b * t;
}
if (b == null) {
return a * (1.0 - t);
}
return new Offset(MathUtils.lerpDouble(a.dx, b.dx, t), MathUtils.lerpDouble(a.dy, b.dy, t));
}
return this._dx.Equals(other._dx) && this._dy.Equals(other._dy);
return base.Equals(other);
}
public override bool Equals(object obj) {

return obj is Offset && this.Equals((Offset) obj);
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != this.GetType()) {
return false;
}
return this.Equals((Offset) obj);
}
public override int GetHashCode() {

public static bool operator ==(Offset left, Offset right) {
return Equals(left, right);
}
public static bool operator !=(Offset left, Offset right) {
return !Equals(left, right);
}
return "Offset(" + this._dx.ToString("0.0") + ", " + this._dy.ToString("0.0") + ")";
return $"Offset({this._dx:F1}, {this._dy:F1})";
}
}

}
public double shortestSide {
get { return Math.Min(Math.Abs(this.width), Math.Abs(this.height)); }
get { return Math.Min(this.width.abs(), this.height.abs()); }
get { return Math.Max(Math.Abs(this.width), Math.Abs(this.height)); }
get { return Math.Max(this.width.abs(), this.height.abs()); }
}
public Offset topLeft(Offset origin) {
return origin;
}
public Offset topCenter(Offset origin) {
return new Offset(origin.dx + this.width / 2.0, origin.dy);
}
public Offset topRight(Offset origin) {
return new Offset(origin.dx + this.width, origin.dy);
}
public Offset centerLeft(Offset origin) {
return new Offset(origin.dx, origin.dy + this.height / 2.0);
}
public Offset center(Offset origin) {
return new Offset(origin.dx + this.width / 2.0, origin.dy + this.height / 2.0);
}
public Offset centerRight(Offset origin) {
return new Offset(origin.dx + this.width, origin.dy + this.height / 2.0);
}
public Offset bottomLeft(Offset origin) {
return new Offset(origin.dx, origin.dy + this.height);
}
public Offset bottomCenter(Offset origin) {
return new Offset(origin.dx + this.width / 2.0, origin.dy + this.height);
}
public Offset bottomRight(Offset origin) {
return new Offset(origin.dx + this.width, origin.dy + this.height);
}
public Size flipped {
get { return new Size(this.height, this.width); }
}
public static Size lerp(Size a, Size b, double t) {

}
public bool Equals(Size other) {
return this._dx.Equals(other._dx) && this._dy.Equals(other._dy);
return base.Equals(other);
}
public override bool Equals(object obj) {

return obj is Size && this.Equals((Size) obj);
if (ReferenceEquals(this, obj)) {
return true;
}
if (obj.GetType() != this.GetType()) {
return false;
}
return this.Equals((Size) obj);
}
public override int GetHashCode() {

public static bool operator ==(Size left, Size right) {
return Equals(left, right);
}
public static bool operator !=(Size left, Size right) {
return !Equals(left, right);
}
return "Size(" + this._dx.ToString("0.0") + ", " + this._dy.ToString("0.0") + ")";
return $"Size({this._dx:F1}, {this._dy:F1})";
}
}

return new Rect(left, top, left + width, top + height);
}
public static Rect fromCircle(Offset offset, double radius) {
return new Rect(offset.dx - radius, offset.dy - radius, offset.dx + radius, offset.dy + radius);
public static Rect fromCircle(Offset center, double radius) {
return new Rect(center.dx - radius, center.dy - radius, center.dx + radius, center.dy + radius);
}
public static Rect fromPoints(Offset a, Offset b) {

return offset.dx >= this.left && offset.dx < this.right && offset.dy >= this.top && offset.dy < this.bottom;
}
public bool containsInclusive(Offset offset) {
return offset.dx >= this.left && offset.dx <= this.right && offset.dy >= this.top && offset.dy <= this.bottom;
}
public bool contains(Rect rect) {
return this.contains(rect.topLeft) && this.contains(rect.bottomRight);
}

}
}
public override string ToString() {
return "Rect.fromLTRB(" + this.left.ToString("0.0") + ", " + this.top.ToString("0.0") + ", " +
this.right.ToString("0.0") + ", " + this.bottom.ToString("0.0") + ")";
}
public static bool operator ==(Rect left, Rect right) {
return Equals(left, right);
}

}
public override string ToString() {
return "Rect.fromLTRB(" + this.left.ToString("0.0") + ", " + this.top.ToString("0.0") + ", " +
this.right.ToString("0.0") + ", " + this.bottom.ToString("0.0") + ")";
}
}

return elliptical(a.x - b.x, a.y - b.y);
}
public static Radius operator -(Radius a, double b) {
return elliptical(a.x - b, a.y - b);
}
public static Radius operator +(Radius a, double b) {
return elliptical(a.x + b, a.y + b);
}
public static Radius operator *(Radius a, double b) {
return elliptical(a.x * b, a.y * b);
}
public static Radius operator /(Radius a, double b) {
return elliptical(a.x / b, a.y / b);
}
}
public static Radius operator %(Radius a, double b) {
return elliptical(a.x % b, a.y % b);
}
public static Radius lerp(Radius a, Radius b, double t) {

public class RRect : IEquatable<RRect> {
RRect(double left, double top, double right, double bottom,
double tlRadius, double trRadius, double brRadius, double blRadius) {
Radius tlRadius = null, Radius trRadius = null, Radius brRadius = null, Radius blRadius = null) {
this.tlRadius = tlRadius;
this.trRadius = trRadius;
this.brRadius = brRadius;
this.blRadius = blRadius;
this.tlRadius = tlRadius ?? Radius.zero;
this.trRadius = trRadius ?? Radius.zero;
this.brRadius = brRadius ?? Radius.zero;
this.blRadius = blRadius ?? Radius.zero;
}
RRect(double left, double top, double right, double bottom,
double? tlRadius = null, double? trRadius = null, double? brRadius = null, double? blRadius = null) {
this.left = left;
this.top = top;
this.right = right;
this.bottom = bottom;
this.tlRadius = tlRadius != null ? Radius.circular(tlRadius.Value) : Radius.zero;
this.trRadius = trRadius != null ? Radius.circular(trRadius.Value) : Radius.zero;
this.brRadius = brRadius != null ? Radius.circular(brRadius.Value) : Radius.zero;
this.blRadius = blRadius != null ? Radius.circular(blRadius.Value) : Radius.zero;
public static RRect fromLTRBAndRadius(
public static RRect fromLTRBXY(
double radius = 0) {
double radiusX, double radiusY) {
var radius = Radius.elliptical(radiusX, radiusY);
public static RRect fromRect(Rect rect) {
return new RRect(rect.left, rect.top, rect.right, rect.bottom,
0, 0, 0, 0);
public static RRect fromLTRBR(
double left, double top, double right, double bottom, Radius radius) {
return new RRect(left, top, right, bottom,
radius, radius, radius, radius);
}
public static RRect fromLTRBR(
double left, double top, double right, double bottom, double radius) {
var r = Radius.circular(radius);
return new RRect(left, top, right, bottom,
r, r, r, r);
public static RRect fromRectAndRadius(Rect rect, double radius) {
public static RRect fromRectXY(Rect rect, double radiusX, double radiusY) {
var radius = Radius.elliptical(radiusX, radiusY);
public static RRect fromRect(Rect rect) {
return new RRect(rect.left, rect.top, rect.right, rect.bottom, (Radius) null);
}
public static RRect fromRectAndRadius(Rect rect, Radius radius) {
return new RRect(rect.left, rect.top, rect.right, rect.bottom,
radius, radius, radius, radius);
}
public static RRect fromRectAndRadius(Rect rect, double radius) {
var r = Radius.circular(radius);
return new RRect(rect.left, rect.top, rect.right, rect.bottom,
r, r, r, r);
}
double topLeft = 0.0, double topRight = 0.0, double bottomRight = 0.0, double bottomLeft = 0.0) {
Radius topLeft = null, Radius topRight = null, Radius bottomRight = null, Radius bottomLeft = null) {
return new RRect(left, top, right, bottom,
topLeft, topRight, bottomRight, bottomLeft);
}
public static RRect fromLTRBAndCorners(
double left, double top, double right, double bottom,
double? topLeft = null, double? topRight = null, double? bottomRight = null, double? bottomLeft = null) {
return new RRect(left, top, right, bottom,
topLeft, topRight, bottomRight, bottomLeft);
}

double topLeft = 0.0, double topRight = 0.0, double bottomRight = 0.0, double bottomLeft = 0.0) {
Radius topLeft = null, Radius topRight = null, Radius bottomRight = null, Radius bottomLeft = null) {
public static readonly RRect zero = new RRect(0, 0, 0, 0, 0, 0, 0, 0);
public static RRect fromRectAndCorners(
Rect rect,
double? topLeft = null, double? topRight = null, double? bottomRight = null, double? bottomLeft = null) {
return new RRect(rect.left, rect.top, rect.right, rect.bottom,
topLeft, topRight, bottomRight, bottomLeft);
}
public readonly double left;
public readonly double top;

public readonly double tlRadius;
public readonly double trRadius;
public readonly double brRadius;
public readonly double blRadius;
public readonly Radius tlRadius;
public readonly Radius trRadius;
public readonly Radius brRadius;
public readonly Radius blRadius;
public double tlRadiusX {
get { return this.tlRadius.x; }
}
public double tlRadiusY {
get { return this.tlRadius.y; }
}
public double trRadiusX {
get { return this.trRadius.x; }
}
public double trRadiusY {
get { return this.trRadius.y; }
}
public double blRadiusX {
get { return this.blRadius.x; }
}
public double blRadiusY {
get { return this.blRadius.y; }
}
public double brRadiusX {
get { return this.brRadius.x; }
}
public double brRadiusY {
get { return this.brRadius.y; }
}
public static readonly RRect zero = new RRect(0, 0, 0, 0, (Radius) null);
public RRect shift(Offset offset) {
return fromLTRBAndCorners(
this.left + offset.dx,

get {
const double kInsetFactor = 0.29289321881; // 1-cos(pi/4)
double leftRadius = Math.Max(this.blRadius, this.tlRadius);
double topRadius = Math.Max(this.tlRadius, this.trRadius);
double rightRadius = Math.Max(this.trRadius, this.brRadius);
double bottomRadius = Math.Max(this.brRadius, this.blRadius);
double leftRadius = Math.Max(this.blRadiusX, this.tlRadiusX);
double topRadius = Math.Max(this.tlRadiusY, this.trRadiusY);
double rightRadius = Math.Max(this.trRadiusX, this.brRadiusX);
double bottomRadius = Math.Max(this.brRadiusY, this.blRadiusY);
return Rect.fromLTRB(
this.left + leftRadius * kInsetFactor,

public Rect middleRect {
get {
double leftRadius = Math.Max(this.blRadius, this.tlRadius);
double topRadius = Math.Max(this.tlRadius, this.trRadius);
double rightRadius = Math.Max(this.trRadius, this.brRadius);
double bottomRadius = Math.Max(this.brRadius, this.blRadius);
double leftRadius = Math.Max(this.blRadiusX, this.tlRadiusX);
double topRadius = Math.Max(this.tlRadiusY, this.trRadiusY);
double rightRadius = Math.Max(this.trRadiusX, this.brRadiusX);
double bottomRadius = Math.Max(this.brRadiusY, this.blRadiusY);
return Rect.fromLTRB(
this.left + leftRadius,

public Rect wideMiddleRect {
get {
double topRadius = Math.Max(this.tlRadius, this.trRadius);
double bottomRadius = Math.Max(this.brRadius, this.blRadius);
double topRadius = Math.Max(this.tlRadiusY, this.trRadiusY);
double bottomRadius = Math.Max(this.brRadiusY, this.blRadiusY);
return Rect.fromLTRB(
this.left,

public Rect tallMiddleRect {
get {
double leftRadius = Math.Max(this.blRadius, this.tlRadius);
double rightRadius = Math.Max(this.trRadius, this.brRadius);
double leftRadius = Math.Max(this.blRadiusX, this.tlRadiusX);
double rightRadius = Math.Max(this.trRadiusX, this.brRadiusX);
return Rect.fromLTRB(
this.left + leftRadius,

public bool isFinite {
get {
return !double.IsInfinity(this.left)
&& !double.IsInfinity(this.top)
&& !double.IsInfinity(this.right)
&& !double.IsInfinity(this.bottom);
return this.left.isFinite()
&& this.top.isFinite()
&& this.right.isFinite()
&& this.bottom.isFinite();
}
}

public bool isRect {
get {
return this.tlRadius == 0.0 &&
this.trRadius == 0.0 &&
this.blRadius == 0.0 &&
this.brRadius == 0.0;
return this.tlRadius == Radius.zero &&
this.trRadius == Radius.zero &&
this.blRadius == Radius.zero &&
this.brRadius == Radius.zero;
}
}

&& this.trRadius == this.brRadius
&& this.brRadius == this.blRadius
&& (this.width <= 2.0 * this.tlRadius || this.height <= 2.0 * this.tlRadius);
&& (this.width <= 2.0 * this.tlRadiusX || this.height <= 2.0 * this.tlRadiusY);
}
}

&& this.trRadius == this.brRadius
&& this.brRadius == this.blRadius
&& (this.width <= 2.0 * this.tlRadius && this.height <= 2.0 * this.tlRadius);
&& (this.width <= 2.0 * this.tlRadiusX && this.height <= 2.0 * this.tlRadiusY);
return this.width == this.height
&& this.isEllipse;
return this.width == this.height && this.isEllipse;
get { return Math.Min(Math.Abs(this.width), Math.Abs(this.height)); }
get { return Math.Min(this.width.abs(), this.height.abs()); }
get { return Math.Max(Math.Abs(this.width), Math.Abs(this.height)); }
get { return Math.Max(this.width.abs(), this.height.abs()); }
}
public Offset center {

public bool contains(Rect rect) {
if (!this.outerRect.contains(rect)) {
double _getMin(double min, double radius1, double radius2, double limit) {
double sum = radius1 + radius2;
if (sum > limit && sum != 0.0) {
return Math.Min(min, limit / sum);
}
return min;
}
RRect _scaled;
void _scaleRadii() {
if (this._scaled == null) {
double scale = 1.0;
scale = this._getMin(scale, this.blRadiusY, this.tlRadiusY, this.height);
scale = this._getMin(scale, this.tlRadiusX, this.trRadiusX, this.width);
scale = this._getMin(scale, this.trRadiusY, this.brRadiusY, this.height);
scale = this._getMin(scale, this.brRadiusX, this.blRadiusX, this.width);
if (scale < 1.0) {
this._scaled = fromLTRBAndCorners(
left: this.left, top: this.top, right: this.right, bottom: this.bottom,
topLeft: this.tlRadius * scale, topRight: this.trRadius * scale,
bottomRight: this.brRadius * scale, bottomLeft: this.blRadius * scale);
} else {
this._scaled = this;
}
}
}
public bool contains(Offset point) {
if (point.dx < this.left || point.dx >= this.right || point.dy < this.top || point.dy >= this.bottom) {
if (this.isRect) {
this._scaleRadii();
double x;
double y;
double radiusX;
double radiusY;
if (point.dx < this.left + this._scaled.tlRadiusX &&
point.dy < this.top + this._scaled.tlRadiusY) {
x = point.dx - this.left - this._scaled.tlRadiusX;
y = point.dy - this.top - this._scaled.tlRadiusY;
radiusX = this._scaled.tlRadiusX;
radiusY = this._scaled.tlRadiusY;
} else if (point.dx > this.right - this._scaled.trRadiusX &&
point.dy < this.top + this._scaled.trRadiusY) {
x = point.dx - this.right + this._scaled.trRadiusX;
y = point.dy - this.top - this._scaled.trRadiusY;
radiusX = this._scaled.trRadiusX;
radiusY = this._scaled.trRadiusY;
} else if (point.dx > this.right - this._scaled.brRadiusX &&
point.dy > this.bottom - this._scaled.brRadiusY) {
x = point.dx - this.right + this._scaled.brRadiusX;
y = point.dy - this.bottom + this._scaled.brRadiusY;
radiusX = this._scaled.brRadiusX;
radiusY = this._scaled.brRadiusY;
} else if (point.dx < this.left + this._scaled.blRadiusX &&
point.dy > this.bottom - this._scaled.blRadiusY) {
x = point.dx - this.left - this._scaled.blRadiusX;
y = point.dy - this.bottom + this._scaled.blRadiusY;
radiusX = this._scaled.blRadiusX;
radiusY = this._scaled.blRadiusY;
} else {
return this.checkCornerContainment(rect.left, rect.top) &&
this.checkCornerContainment(rect.right, rect.top) &&
this.checkCornerContainment(rect.right, rect.bottom) &&
this.checkCornerContainment(rect.left, rect.bottom);
x = x / radiusX;
y = y / radiusY;
if (x * x + y * y > 1.0) {
return false;
}
return true;
bool checkCornerContainment(double x, double y) {
double radius;
public static RRect lerp(RRect a, RRect b, double t) {
if (a == null && b == null) {
return null;
}
if (a == null) {
return RRect.fromLTRBAndCorners(
b.left * t,
b.top * t,
b.right * t,
b.bottom * t,
b.tlRadius * t,
b.trRadius * t,
b.brRadius * t,
b.blRadius * t
);
}
if (b == null) {
double k = 1.0 - t;
return RRect.fromLTRBAndCorners(
a.left * k,
a.top * k,
a.right * k,
a.bottom * k,
a.tlRadius * k,
a.trRadius * k,
a.brRadius * k,
a.blRadius * k);
}
if (x < this.left + this.tlRadius &&
y < this.top + this.tlRadius) {
x -= this.left + this.tlRadius;
y -= this.top + this.tlRadius;
radius = this.tlRadius;
} else if (x < this.left + this.blRadius &&
y > this.bottom - this.blRadius) {
x -= this.left + this.blRadius;
y -= this.bottom - this.blRadius;
radius = this.blRadius;
} else if (x > this.right - this.trRadius &&
y < this.top + this.trRadius) {
x -= this.right - this.trRadius;
y -= this.bottom - this.blRadius;
radius = this.trRadius;
} else if (x > this.right - this.brRadius &&
y > this.bottom - this.brRadius) {
x -= this.right - this.brRadius;
y -= this.bottom - this.brRadius;
radius = this.brRadius;
} else {
return RRect.fromLTRBAndCorners(
MathUtils.lerpDouble(a.left, b.left, t),
MathUtils.lerpDouble(a.top, b.top, t),
MathUtils.lerpDouble(a.right, b.right, t),
MathUtils.lerpDouble(a.bottom, b.bottom, t),
Radius.lerp(a.tlRadius, b.tlRadius, t),
Radius.lerp(a.trRadius, b.trRadius, t),
Radius.lerp(a.brRadius, b.brRadius, t),
Radius.lerp(a.blRadius, b.blRadius, t));
}
public bool contains(Rect rect) {
if (!this.outerRect.contains(rect)) {
return false;
}
if (this.isRect) {
// A point is in an ellipse (in standard position) if:
// x^2 y^2
// ----- + ----- <= 1
// a^2 b^2
// or :
// b^2*x^2 + a^2*y^2 <= (ab)^2
return x * x + y * y <= radius * radius;
return this.contains(rect.topLeft) &&
this.contains(rect.topRight) &&
this.contains(rect.bottomRight) &&
this.contains(rect.bottomLeft);
public bool Equals(RRect other) {
if (ReferenceEquals(null, other)) {
return false;

public static bool operator !=(RRect a, RRect b) {
return !(a == b);
}
public override string ToString() {
string rect = $"{this.left:F1)}, " +
$"{this.top:F1}, " +
$"{this.right:F1}, " +
$"{this.bottom:F1}";
if (this.tlRadius == this.trRadius &&
this.trRadius == this.brRadius &&
this.brRadius == this.blRadius) {
if (this.tlRadius.x == this.tlRadius.y) {
return $"RRect.fromLTRBR({rect}, {this.tlRadius.x:F1})";
}
return $"RRect.fromLTRBXY($rect, {this.tlRadius.x:F1}, {this.tlRadius.y:F1})";
}
return "RRect.fromLTRBAndCorners(" +
$"{rect}, " +
$"topLeft: {this.tlRadius}, " +
$"topRight: {this.trRadius}, " +
$"bottomRight: {this.brRadius}, " +
$"bottomLeft: {this.blRadius}" +
")";
}
}
}

30
Runtime/ui/painting/painting.cs


public Color(long value) {
this.value = value & 0xFFFFFFFF;
}
public static readonly Color clear = new Color(0x00000000);
public static readonly Color black = new Color(0xFF000000);
public static readonly Color white = new Color(0xFFFFFFFF);
public static Color fromARGB(int a, int r, int g, int b) {
return new Color(

public static UnityEngine.Rect toRect(this Rect rect) {
return new UnityEngine.Rect((float) rect.left, (float) rect.top, (float) rect.width, (float) rect.height);
}
public static Vector4 toVector(this BorderWidth borderWidth) {
return new Vector4((float) borderWidth.left, (float) borderWidth.top, (float) borderWidth.right,
(float) borderWidth.bottom);
}
public static float[] toFloatArray(this BorderWidth borderWidth) {
return new[] {
(float) borderWidth.left, (float) borderWidth.top,
(float) borderWidth.right, (float) borderWidth.bottom
};
}
public static Vector4 toVector(this BorderRadius borderRadius) {
return new Vector4((float) borderRadius.topLeft, (float) borderRadius.topRight,
(float) borderRadius.bottomRight, (float) borderRadius.bottomLeft);
}
public static float[] toFloatArray(this BorderRadius borderRadius) {
return new[] {
(float) borderRadius.topLeft, (float) borderRadius.topRight,
(float) borderRadius.bottomRight, (float) borderRadius.bottomLeft
};
}
public static float alignToPixel(this float v, float devicePixelRatio) {

617
Runtime/ui/painting/path.cs


readonly List<float> _commands = new List<float>();
float _commandx;
float _commandy;
float _minX, _minY;
float _maxX, _maxY;
PathCache _cache;

return this._cache;
}
public void reset() {
public Path() {
this._reset();
}
void _reset() {
this._minX = float.MaxValue;
this._minY = float.MaxValue;
this._maxX = float.MinValue;
this._maxY = float.MinValue;
void _appendCommands(float[] vals) {
if (vals.Length == 1 && (PathCommand) vals[vals.Length - 1] == PathCommand.close) {
// last command is close
} else if (vals.Length == 2 && (PathCommand) vals[vals.Length - 2] == PathCommand.winding) {
// last command is winding
} else {
D.assert(vals.Length >= 2);
this._commandx = vals[vals.Length - 2];
this._commandy = vals[vals.Length - 1];
void _expandBounds(float x, float y) {
this._minX = Mathf.Min(this._minX, x);
this._minY = Mathf.Min(this._minY, y);
this._maxX = Mathf.Max(this._maxX, x);
this._maxY = Mathf.Max(this._maxY, y);
}
public Rect getBounds() {
if (this._minX >= this._maxX || this._minY >= this._maxY) {
return null;
this._commands.AddRange(vals);
return Rect.fromLTRB(this._minX, this._minY, this._maxX, this._maxY);
}
void _appendCommands(float[] commands) {
var i = 0;
while (i < commands.Length) {
var cmd = (PathCommand) commands[i];
switch (cmd) {
case PathCommand.moveTo:
case PathCommand.lineTo:
this._expandBounds(commands[i + 1], commands[i + 2]);
this._commandx = commands[i + 1];
this._commandy = commands[i + 2];
i += 3;
break;
case PathCommand.bezierTo:
this._expandBounds(commands[i + 1], commands[i + 2]);
this._expandBounds(commands[i + 3], commands[i + 4]);
this._expandBounds(commands[i + 5], commands[i + 6]);
this._commandx = commands[i + 5];
this._commandy = commands[i + 6];
i += 7;
break;
case PathCommand.close:
i++;
break;
case PathCommand.winding:
i += 2;
break;
default:
D.assert(false, "unknown cmd: " + cmd);
break;
}
}
this._commands.AddRange(commands);
this._cache = null;
}

float h = (float) rrect.height;
float halfw = Mathf.Abs(w) * 0.5f;
float halfh = Mathf.Abs(h) * 0.5f;
float rxBL = Mathf.Min((float) rrect.blRadius, halfw) * Mathf.Sign(w);
float ryBL = Mathf.Min((float) rrect.blRadius, halfh) * Mathf.Sign(h);
float rxBR = Mathf.Min((float) rrect.brRadius, halfw) * Mathf.Sign(w);
float ryBR = Mathf.Min((float) rrect.brRadius, halfh) * Mathf.Sign(h);
float rxTR = Mathf.Min((float) rrect.trRadius, halfw) * Mathf.Sign(w);
float ryTR = Mathf.Min((float) rrect.trRadius, halfh) * Mathf.Sign(h);
float rxTL = Mathf.Min((float) rrect.tlRadius, halfw) * Mathf.Sign(w);
float ryTL = Mathf.Min((float) rrect.tlRadius, halfh) * Mathf.Sign(h);
float signW = Mathf.Sign(w);
float signH = Mathf.Sign(h);
float rxBL = Mathf.Min((float) rrect.blRadiusX, halfw) * signW;
float ryBL = Mathf.Min((float) rrect.blRadiusY, halfh) * signH;
float rxBR = Mathf.Min((float) rrect.brRadiusX, halfw) * signW;
float ryBR = Mathf.Min((float) rrect.brRadiusY, halfh) * signH;
float rxTR = Mathf.Min((float) rrect.trRadiusX, halfw) * signW;
float ryTR = Mathf.Min((float) rrect.trRadiusY, halfh) * signH;
float rxTL = Mathf.Min((float) rrect.tlRadiusX, halfw) * signW;
float ryTL = Mathf.Min((float) rrect.tlRadiusY, halfh) * signH;
float x = (float) rrect.left;
float y = (float) rrect.top;

public void addCircle(double cx, double cy, double r) {
this.addEllipse(cx, cy, r, r);
}
public void addOval(Rect oval) {
D.assert(oval != null);
var center = oval.center;
this.addEllipse(center.dx, center.dy, oval.width / 2, oval.height / 2);
}
public void addPolygon(IList<Offset> points, bool close) {
D.assert(points != null);
if (points.Count == 0) {
return;
}
var commands = new List<float>();
commands.Add((float) PathCommand.moveTo);
commands.Add((float) points[0].dx);
commands.Add((float) points[0].dy);
for (int i = 1; i < points.Count; i++) {
var point = points[i];
commands.Add((float) PathCommand.lineTo);
commands.Add((float) point.dx);
commands.Add((float) point.dy);
}
if (close) {
commands.Add((float) PathCommand.close);
}
this._appendCommands(commands.ToArray());
}
public void addPath(Path path, Offset offset) {
D.assert(path != null);
var commands = new List<float>();
var i = 0;
while (i < path._commands.Count) {
var cmd = (PathCommand) path._commands[i];
switch (cmd) {
case PathCommand.moveTo:
case PathCommand.lineTo:
commands.Add(path._commands[i]);
commands.Add(path._commands[i + 1] + (float) offset.dx);
commands.Add(path._commands[i + 2] + (float) offset.dy);
i += 3;
break;
case PathCommand.bezierTo:
commands.Add(path._commands[i]);
commands.Add(path._commands[i + 1] + (float) offset.dx);
commands.Add(path._commands[i + 2] + (float) offset.dy);
commands.Add(path._commands[i + 3] + (float) offset.dx);
commands.Add(path._commands[i + 4] + (float) offset.dy);
commands.Add(path._commands[i + 5] + (float) offset.dx);
commands.Add(path._commands[i + 6] + (float) offset.dy);
i += 7;
break;
case PathCommand.close:
commands.Add(path._commands[i]);
i++;
break;
case PathCommand.winding:
commands.Add(path._commands[i]);
commands.Add(path._commands[i + 1]);
i += 2;
break;
default:
D.assert(false, "unknown cmd: " + cmd);
break;
}
}
this._appendCommands(commands.ToArray());
}
public bool contains(Offset point) {
var bounds = this.getBounds();
if (bounds == null) {
return false;
}
if (!bounds.containsInclusive(point)) {
return false;
}
float x = (float) point.dx;
float y = (float) point.dy;
float lastMoveToX = 0;
float lastMoveToY = 0;
float commandx = 0;
float commandy = 0;
PathWinding winding = PathWinding.counterClockwise;
var totalW = 0;
var w = 0;
var i = 0;
while (i < this._commands.Count) {
var cmd = (PathCommand) this._commands[i];
switch (cmd) {
case PathCommand.moveTo:
if (lastMoveToX != commandx || lastMoveToY != commandy) {
w += windingLine(
commandx, commandy,
lastMoveToX, lastMoveToY,
x, y);
}
if (w != 0) {
totalW += winding == PathWinding.counterClockwise ? w : -w;
w = 0;
}
lastMoveToX = commandx = this._commands[i + 1];
lastMoveToY = commandy = this._commands[i + 2];
winding = PathWinding.counterClockwise;
i += 3;
break;
case PathCommand.lineTo:
w += windingLine(
commandx, commandy,
this._commands[i + 1], this._commands[i + 2],
x, y);
commandx = this._commands[i + 1];
commandy = this._commands[i + 2];
i += 3;
break;
case PathCommand.bezierTo:
w += windingCubic(
commandx, commandy,
this._commands[i + 1], this._commands[i + 2],
this._commands[i + 3], this._commands[i + 4],
this._commands[i + 5], this._commands[i + 6],
x, y);
commandx = this._commands[i + 5];
commandy = this._commands[i + 6];
i += 7;
break;
case PathCommand.close:
i++;
break;
case PathCommand.winding:
winding = (PathWinding) this._commands[i + 1];
i += 2;
break;
default:
D.assert(false, "unknown cmd: " + cmd);
break;
}
}
if (lastMoveToX != commandx || lastMoveToY != commandy) {
w += windingLine(
commandx, commandy,
lastMoveToX, lastMoveToY,
x, y);
}
if (w != 0) {
totalW += winding == PathWinding.counterClockwise ? w : -w;
w = 0;
}
return totalW != 0;
}
static int windingLine(float x0, float y0, float x1, float y1, float x, float y) {
if (y0 == y1) {
return 0;
}
int dir = 1; // down. y0 < y1
float minY = y0;
float maxY = y1;
if (y0 > y1) {
dir = -1;
minY = y1;
maxY = y0;
}
if (y < minY || y >= maxY) {
return 0;
}
float cross = (x1 - x0) * (y - y0) - (x - x0) * (y1 - y0);
if (cross == 0) {
return 0;
}
if (cross.sign() == dir) {
return 0;
}
return dir;
}
static int windingCubic(float x1, float y1, float x2, float y2, float x3, float y3, float x4, float y4,
float x, float y) {
Offset[] src = {
new Offset(x1, y1),
new Offset(x2, y2),
new Offset(x3, y3),
new Offset(x4, y4),
};
Offset[] dst = new Offset[10];
int n = _chopCubicAtYExtrema(src, dst);
int w = 0;
for (int i = 0; i <= n; ++i) {
w += _winding_mono_cubic(dst, i * 3, x, y);
}
return w;
}
static int _winding_mono_cubic(Offset[] pts, int ptsBase, float x, float y) {
float y0 = (float) pts[ptsBase + 0].dy;
float y3 = (float) pts[ptsBase + 3].dy;
if (y0 == y3) {
return 0;
}
int dir = 1; // down. y0 < y3
float minY = y0;
float maxY = y3;
if (y0 > y3) {
dir = -1;
minY = y3;
maxY = y0;
}
if (y < minY || y >= maxY) {
return 0;
}
// quickreject or quickaccept
float minX = float.MaxValue, maxX = float.MinValue;
for (int i = 0; i < 4; i++) {
var dx = (float) pts[ptsBase + i].dx;
if (dx < minX) {
minX = dx;
}
if (dx > maxX) {
maxX = dx;
}
}
if (x < minX) {
return 0;
}
if (x > maxX) {
return dir;
}
// compute the actual x(t) value
float t;
if (!_chopMonoAtY(pts, ptsBase, y, out t)) {
return 0;
}
float xt = _eval_cubic_pts(
(float) pts[ptsBase + 0].dx,
(float) pts[ptsBase + 1].dx,
(float) pts[ptsBase + 2].dx,
(float) pts[ptsBase + 3].dx, t);
return xt < x ? dir : 0;
}
static float _eval_cubic_pts(float c0, float c1, float c2, float c3,
float t) {
float A = c3 + 3*(c1 - c2) - c0;
float B = 3*(c2 - c1 - c1 + c0);
float C = 3*(c1 - c0);
float D = c0;
return _poly_eval(A, B, C, D, t);
}
static float _poly_eval(float A, float B, float C, float D, float t) {
return ((A * t + B) * t + C) * t + D;
}
static bool _chopMonoAtY(Offset[] pts, int ptsBase, float y, out float t) {
float[] ycrv = {
(float) pts[ptsBase + 0].dy - y,
(float) pts[ptsBase + 1].dy - y,
(float) pts[ptsBase + 2].dy - y,
(float) pts[ptsBase + 3].dy - y
};
// NEWTON_RAPHSON Quadratic convergence, typically <= 3 iterations.
// Initial guess.
// is not only monotonic but degenerate.
float t1 = ycrv[0] / (ycrv[0] - ycrv[3]);
// Newton's iterations.
const float tol = 1f / 16384; // This leaves 2 fixed noise bits.
float t0;
const int maxiters = 5;
int iters = 0;
bool converged;
do {
t0 = t1;
float y01 = MathUtils.lerpFloat(ycrv[0], ycrv[1], t0);
float y12 = MathUtils.lerpFloat(ycrv[1], ycrv[2], t0);
float y23 = MathUtils.lerpFloat(ycrv[2], ycrv[3], t0);
float y012 = MathUtils.lerpFloat(y01, y12, t0);
float y123 = MathUtils.lerpFloat(y12, y23, t0);
float y0123 = MathUtils.lerpFloat(y012, y123, t0);
float yder = (y123 - y012) * 3;
t1 -= y0123 / yder;
converged = (t1 - t0).abs() <= tol; // NaN-safe
++iters;
} while (!converged && (iters < maxiters));
t = t1;
// The result might be valid, even if outside of the range [0, 1], but
// we never evaluate a Bezier outside this interval, so we return false.
if (t1 < 0 || t1 > 1) {
return false;
}
return converged;
}
static void _flatten_double_cubic_extrema(Offset[] dst, int dstBase) {
var dy = dst[dstBase + 3].dy;
dst[dstBase + 2] = new Offset(dst[dstBase + 2].dx, dy);
dst[dstBase + 4] = new Offset(dst[dstBase + 4].dx, dy);
}
static int _chopCubicAtYExtrema(Offset[] src, Offset[] dst) {
D.assert(src != null && src.Length == 4);
D.assert(dst != null && dst.Length == 10);
float[] tValues = new float[2];
int roots = _findCubicExtrema(
(float) src[0].dy, (float) src[1].dy, (float) src[2].dy, (float) src[3].dy,
tValues);
_chopCubicAt(src, dst, tValues, roots);
if (dst != null && roots > 0) {
// we do some cleanup to ensure our Y extrema are flat
_flatten_double_cubic_extrema(dst, 0);
if (roots == 2) {
_flatten_double_cubic_extrema(dst, 3);
}
}
return roots;
}
static void _chopCubicAt(Offset[] src, int srcBase, Offset[] dst, int dstBase, float t) {
D.assert(src != null && (src.Length == 4 || src.Length == 10));
D.assert(dst != null && dst.Length == 10);
D.assert(t > 0 && t < 1);
var p0 = src[srcBase + 0];
var p1 = src[srcBase + 1];
var p2 = src[srcBase + 2];
var p3 = src[srcBase + 3];
var ab = Offset.lerp(p0, p1, t);
var bc = Offset.lerp(p1, p2, t);
var cd = Offset.lerp(p2, p3, t);
var abc = Offset.lerp(ab, bc, t);
var bcd = Offset.lerp(bc, cd, t);
var abcd = Offset.lerp(abc, bcd, t);
dst[dstBase + 0] = p0;
dst[dstBase + 1] = ab;
dst[dstBase + 2] = abc;
dst[dstBase + 3] = abcd;
dst[dstBase + 4] = bcd;
dst[dstBase + 5] = cd;
dst[dstBase + 6] = p3;
}
static void _chopCubicAt(Offset[] src, Offset[] dst, float[] tValues, int roots) {
D.assert(src != null && src.Length == 4);
D.assert(dst != null && dst.Length == 10);
D.assert(() => {
for (int i = 0; i < roots - 1; i++) {
D.assert(0 < tValues[i] && tValues[i] < 1);
D.assert(0 < tValues[i + 1] && tValues[i + 1] < 1);
D.assert(tValues[i] < tValues[i + 1]);
}
return true;
});
if (dst != null) {
if (roots == 0) {
dst[0] = src[0];
dst[1] = src[1];
dst[2] = src[2];
dst[3] = src[3];
} else {
float t = tValues[0];
int srcBase = 0;
int dstBase = 0;
for (int i = 0; i < roots; i++) {
_chopCubicAt(src, srcBase, dst, dstBase, t);
if (i == roots - 1) {
break;
}
dstBase += 3;
src = dst;
srcBase = dstBase;
// watch out in case the renormalized t isn't in range
if (_valid_unit_divide(tValues[i + 1] - tValues[i], 1 - tValues[i], out t) == 0) {
// if we can't, just create a degenerate cubic
dst[dstBase + 4] = dst[dstBase + 5] = dst[dstBase + 6] = src[srcBase + 3];
break;
}
}
}
}
}
/** Cubic'(t) = At^2 + Bt + C, where
A = 3(-a + 3(b - c) + d)
B = 6(a - 2b + c)
C = 3(b - a)
Solve for t, keeping only those that fit between 0 < t < 1
*/
static int _findCubicExtrema(float a, float b, float c, float d, float[] tValues) {
// we divide A,B,C by 3 to simplify
float A = d - a + 3 * (b - c);
float B = 2 * (a - b - b + c);
float C = b - a;
return _findUnitQuadRoots(A, B, C, tValues);
}
static int _valid_unit_divide(float numer, float denom, out float ratio) {
ratio = 0;
if (numer < 0) {
numer = -numer;
denom = -denom;
}
if (denom == 0 || numer == 0 || numer >= denom) {
return 0;
}
float r = numer / denom;
if (float.IsNaN(r)) {
return 0;
}
D.assert(r >= 0 && r < 1, $"numer {numer}, denom {denom}, r {r}");
if (r == 0) {
// catch underflow if numer <<<< denom
return 0;
}
ratio = r;
return 1;
}
// Just returns its argument, but makes it easy to set a break-point to know when
// _findUnitQuadRoots is going to return 0 (an error).
static int _return_check_zero(int value) {
if (value == 0) {
return 0;
}
return value;
}
static int _findUnitQuadRoots(float A, float B, float C, float[] roots) {
if (A == 0) {
return _return_check_zero(_valid_unit_divide(-C, B, out roots[0]));
}
int r = 0;
// use doubles so we don't overflow temporarily trying to compute R
double dr = (double) B * B - 4 * (double) A * C;
if (dr < 0) {
return _return_check_zero(0);
}
dr = Math.Sqrt(dr);
float R = (float) dr;
if (float.IsInfinity(R)) {
return _return_check_zero(0);
}
float Q = (B < 0) ? -(B - R) / 2 : -(B + R) / 2;
r += _valid_unit_divide(Q, A, out roots[r]);
r += _valid_unit_divide(C, Q, out roots[r]);
if (r == 2) {
if (roots[0] > roots[1]) {
float tmp = roots[0];
roots[0] = roots[1];
roots[1] = tmp;
} else if (roots[0] == roots[1]) {
// nearly-equal?
r -= 1; // skip the double root
}
}
return _return_check_zero(r);
}
counterClockwise = 1,
clockwise = 2,
counterClockwise = 1, // which just means the order as the input is.
clockwise = 2, // which just means the reversed order.
}
[Flags]

public void addPoint(float x, float y, PointFlags flags) {
PathUtils.transformPoint(out x, out y, this._xform, x, y);
this._addPoint(new PathPoint{x = x, y = y, flags = flags});
this._addPoint(new PathPoint {x = x, y = y, flags = flags});
}
void _addPoint(PathPoint point) {

}
if (path.count > 2) {
var area = PathUtils.polyArea(this._points, path.first, path.count);
if (path.winding == PathWinding.counterClockwise && area < 0.0f ||
path.winding == PathWinding.clockwise && area > 0.0f) {
if (path.winding == PathWinding.clockwise) {
PathUtils.polyReverse(this._points, path.first, path.count);
}
}

}
D.assert(indices.Count == cindices);
this._fillConvex = false;
for (var i = 0; i < this._paths.Count; i++) {
var path = this._paths[i];

this.vertices = vertices;
this.triangles = triangles;
this.uv = uv;
double minX = vertices[0].x;
double maxX = vertices[0].x;
double minY = vertices[0].y;

PathUtils.transformPoint(out x, out y, xform, vertex.x, vertex.y);
transVertices.Add(new Vector3(x, y));
}
return new MeshMesh(transVertices, this.triangles, this.uv);
}
}

34
Runtime/widgets/basic.cs


using System;
using System.Collections.Generic;
using System.Linq;
using Unity.UIWidgets.foundation;

public class Stack : MultiChildRenderObjectWidget {
public Stack(
Key key = null,
AlignmentDirectional alignment = null,
TextDirection? textDirection = null,
Alignment alignment = null,
this.alignment = alignment ?? AlignmentDirectional.bottomStart;
this.textDirection = textDirection;
this.alignment = alignment ?? Alignment.bottomLeft;
public readonly AlignmentDirectional alignment;
public readonly TextDirection? textDirection;
public readonly Alignment alignment;
public readonly StackFit fit;
public readonly Overflow overflow;

textDirection: this.textDirection ?? Directionality.of(context),
alignment: this.alignment,
fit: this.fit,
overflow: this.overflow

public override void updateRenderObject(BuildContext context, RenderObject renderObjectRaw) {
var renderObject = (RenderStack) renderObjectRaw;
renderObject.alignment = this.alignment;
renderObject.textDirection = this.textDirection ?? TextDirection.ltr;
renderObject.fit = this.fit;
renderObject.overflow = this.overflow;
}

properties.add(new DiagnosticsProperty<AlignmentDirectional>("alignment", this.alignment));
properties.add(new DiagnosticsProperty<Alignment>("alignment", this.alignment));
properties.add(new EnumProperty<StackFit>("fit", this.fit));
properties.add(new EnumProperty<Overflow>("overflow", this.overflow));
}

widthFactor: widthFactor,
heightFactor: heightFactor,
child: child) {
}
}
public static class LayoutUtils {
public static AxisDirection getAxisDirectionFromAxisReverseAndDirectionality(
BuildContext context,
Axis axis,
bool reverse
) {
switch (axis) {
case Axis.horizontal:
D.assert(WidgetsD.debugCheckHasDirectionality(context));
TextDirection textDirection = Directionality.of(context);
AxisDirection axisDirection = AxisUtils.textDirectionToAxisDirection(textDirection);
return reverse ? AxisUtils.flipAxisDirection(axisDirection) : axisDirection;
case Axis.vertical:
return reverse ? AxisDirection.up : AxisDirection.down;
}
throw new Exception("unknown axisDirection");
}
}

2
Runtime/widgets/scroll_view.cs


public readonly double? cacheExtent;
protected AxisDirection getDirection(BuildContext context) {
return AxisUtils.getAxisDirectionFromAxisReverseAndDirectionality(
return LayoutUtils.getAxisDirectionFromAxisReverseAndDirectionality(
context, this.scrollDirection, this.reverse);
}

23
Tests/Editor/CanvasAndLayers.cs


this._windowAdapter.OnGUI();
if (Event.current.type == EventType.Repaint) {
if (Event.current.type == EventType.Repaint || Event.current.type == EventType.MouseDown) {
this.createRenderTexture();
Window.instance = this._windowAdapter;

color = new Color(0xFFFF0000),
};
canvas.drawRect(
Unity.UIWidgets.ui.Rect.fromLTRB(10, 10, 110, 110),
paint);
// canvas.drawRect(
// Unity.UIWidgets.ui.Rect.fromLTRB(10, 10, 110, 110),
// paint);
var path = new Path();
path.moveTo(10, 150);

path.winding(PathWinding.clockwise);
path.addRect(Unity.UIWidgets.ui.Rect.fromLTWH(0, 100, 100, 100));
path.addRect(Unity.UIWidgets.ui.Rect.fromLTWH(200, 0, 100, 100));
path.addRRect(RRect.fromRectAndRadius(Unity.UIWidgets.ui.Rect.fromLTWH(150, 100, 30, 30), 10));
path.addOval(Unity.UIWidgets.ui.Rect.fromLTWH(150, 50, 100, 100));
path.winding(PathWinding.clockwise);
if (Event.current.type == EventType.MouseDown) {
var pos = new Offset(
Event.current.mousePosition.x,
Event.current.mousePosition.y
);
Debug.Log(pos + ": " + path.contains(pos));
}
canvas.drawPath(path, paint);
canvas.flush();

34
Tests/Editor/RenderBoxes.cs


RenderBoxes() {
this._options = new Func<RenderBox>[] {
this.none,
this.decoratedShape,
this.flex,
};
this._optionStrings = this._options.Select(x => x.Method.Name).ToArray();

child: new RenderDecoratedBox(
decoration: new BoxDecoration(
color: new Color(0xFFFF00FF),
borderRadius: BorderRadius.all(3),
borderRadius: BorderRadius.all(15),
boxShadow: new List<BoxShadow> {
new BoxShadow(
color: new Color(0xFFFF00FF),

)
}
},
image: new DecorationImage(
image: new NetworkImage(
url: "https://sg.fiverrcdn.com/photos/4665137/original/39322-140411095619534.jpg?1424268945"
),
fit: BoxFit.cover)
RenderBox decoratedShape() {
return new RenderConstrainedOverflowBox(
minWidth: 100,
maxWidth: 100,
minHeight: 100,
maxHeight: 100,
child: new RenderDecoratedBox(
decoration: new ShapeDecoration(
color: new Color(0xFFFF00FF),
shape: new BeveledRectangleBorder(
new BorderSide(width: 5, color: Color.white),
BorderRadius.circular(5)),
image: new DecorationImage(
image: new NetworkImage(
url: "https://sg.fiverrcdn.com/photos/4665137/original/39322-140411095619534.jpg?1424268945"
),
fit: BoxFit.cover)
)
)
);
}
RenderBox flex() {
var flexbox = new RenderFlex(
direction: Axis.horizontal,

4
Tests/Editor/Widgets.cs


rowImages.Add(text);
return new Stack(
children: rowImages,
alignment: AlignmentDirectional.center
alignment: Alignment.center
);
}

child: new Container(
color: CLColors.background3,
child: new Transform(
transform: Matrix3.makeRotate(0, (float) px, (float) py),
transform: Matrix3.makeRotate(45, (float) px, (float) py),
child:
new Column(
children: new List<Widget> {

146
Runtime/painting/beveled_rectangle_border.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.ui;
namespace Unity.UIWidgets.painting {
public class BeveledRectangleBorder : ShapeBorder, IEquatable<BeveledRectangleBorder> {
public BeveledRectangleBorder(
BorderSide side = null,
BorderRadius borderRadius = null
) {
this.side = side ?? BorderSide.none;
this.borderRadius = borderRadius ?? BorderRadius.zero;
}
public readonly BorderSide side;
public readonly BorderRadius borderRadius;
public override EdgeInsets dimensions {
get { return EdgeInsets.all(this.side.width); }
}
public override ShapeBorder scale(double t) {
return new BeveledRectangleBorder(
side: this.side.scale(t),
borderRadius: this.borderRadius * t
);
}
public override ShapeBorder lerpFrom(ShapeBorder a, double t) {
if (a is BeveledRectangleBorder border) {
return new BeveledRectangleBorder(
side: BorderSide.lerp(border.side, this.side, t),
borderRadius: BorderRadius.lerp(border.borderRadius, this.borderRadius, t)
);
}
return base.lerpFrom(a, t);
}
public override ShapeBorder lerpTo(ShapeBorder b, double t) {
if (b is BeveledRectangleBorder border) {
return new BeveledRectangleBorder(
side: BorderSide.lerp(this.side, border.side, t),
borderRadius: BorderRadius.lerp(this.borderRadius, border.borderRadius, t)
);
}
return base.lerpTo(b, t);
}
Path _getPath(RRect rrect) {
Offset centerLeft = new Offset(rrect.left, rrect.center.dy);
Offset centerRight = new Offset(rrect.right, rrect.center.dy);
Offset centerTop = new Offset(rrect.center.dx, rrect.top);
Offset centerBottom = new Offset(rrect.center.dx, rrect.bottom);
double tlRadiusX = Math.Max(0.0, rrect.tlRadiusX);
double tlRadiusY = Math.Max(0.0, rrect.tlRadiusY);
double trRadiusX = Math.Max(0.0, rrect.trRadiusX);
double trRadiusY = Math.Max(0.0, rrect.trRadiusY);
double blRadiusX = Math.Max(0.0, rrect.blRadiusX);
double blRadiusY = Math.Max(0.0, rrect.blRadiusY);
double brRadiusX = Math.Max(0.0, rrect.brRadiusX);
double brRadiusY = Math.Max(0.0, rrect.brRadiusY);
List<Offset> vertices = new List<Offset> {
new Offset(rrect.left, Math.Min(centerLeft.dy, rrect.top + tlRadiusY)),
new Offset(Math.Min(centerTop.dx, rrect.left + tlRadiusX), rrect.top),
new Offset(Math.Max(centerTop.dx, rrect.right - trRadiusX), rrect.top),
new Offset(rrect.right, Math.Min(centerRight.dy, rrect.top + trRadiusY)),
new Offset(rrect.right, Math.Max(centerRight.dy, rrect.bottom - brRadiusY)),
new Offset(Math.Max(centerBottom.dx, rrect.right - brRadiusX), rrect.bottom),
new Offset(Math.Min(centerBottom.dx, rrect.left + blRadiusX), rrect.bottom),
new Offset(rrect.left, Math.Max(centerLeft.dy, rrect.bottom - blRadiusY)),
};
var path = new Path();
path.addPolygon(vertices, true);
return path;
}
public override Path getInnerPath(Rect rect) {
return this._getPath(this.borderRadius.toRRect(rect).deflate(this.side.width));
}
public override Path getOuterPath(Rect rect) {
return this._getPath(this.borderRadius.toRRect(rect));
}
public override void paint(Canvas canvas, Rect rect) {
if (rect.isEmpty) {
return;
}
switch (this.side.style) {
case BorderStyle.none:
break;
case BorderStyle.solid:
Path path = this.getOuterPath(rect);
path.addPath(this.getInnerPath(rect), Offset.zero);
canvas.drawPath(path, this.side.toPaint());
break;
}
}
public bool Equals(BeveledRectangleBorder other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return Equals(this.side, other.side) && Equals(this.borderRadius, other.borderRadius);
}
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((BeveledRectangleBorder) obj);
}
public override int GetHashCode() {
unchecked {
return ((this.side != null ? this.side.GetHashCode() : 0) * 397) ^
(this.borderRadius != null ? this.borderRadius.GetHashCode() : 0);
}
}
public static bool operator ==(BeveledRectangleBorder left, BeveledRectangleBorder right) {
return Equals(left, right);
}
public static bool operator !=(BeveledRectangleBorder left, BeveledRectangleBorder right) {
return !Equals(left, right);
}
public override string ToString() {
return $"{this.GetType()}({this.side}, {this.borderRadius})";
}
}
}

11
Runtime/painting/beveled_rectangle_border.cs.meta


fileFormatVersion: 2
guid: 2efce6cbad99f4e5bbfd9e4b46eaae48
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

101
Runtime/painting/circle_border.cs


using System;
using Unity.UIWidgets.ui;
namespace Unity.UIWidgets.painting {
public class CircleBorder : ShapeBorder, IEquatable<CircleBorder> {
public CircleBorder(BorderSide side = null) {
this.side = side ?? BorderSide.none;
}
public readonly BorderSide side;
public override EdgeInsets dimensions {
get { return EdgeInsets.all(this.side.width); }
}
public override ShapeBorder scale(double t) {
return new CircleBorder(side: this.side.scale(t));
}
public override ShapeBorder lerpFrom(ShapeBorder a, double t) {
if (a is CircleBorder border) {
return new CircleBorder(side: BorderSide.lerp(border.side, this.side, t));
}
return base.lerpFrom(a, t);
}
public override ShapeBorder lerpTo(ShapeBorder b, double t) {
if (b is CircleBorder border) {
return new CircleBorder(side: BorderSide.lerp(this.side, border.side, t));
}
return base.lerpTo(b, t);
}
public override Path getInnerPath(Rect rect) {
var path = new Path();
path.addOval(Rect.fromCircle(
center: rect.center,
radius: Math.Max(0.0, rect.shortestSide / 2.0 - this.side.width)
));
return path;
}
public override Path getOuterPath(Rect rect) {
var path = new Path();
path.addOval(Rect.fromCircle(
center: rect.center,
radius: rect.shortestSide / 2.0
));
return path;
}
public override void paint(Canvas canvas, Rect rect) {
switch (this.side.style) {
case BorderStyle.none:
break;
case BorderStyle.solid:
canvas.drawCircle(rect.center, (rect.shortestSide - this.side.width) / 2.0, this.side.toPaint());
break;
}
}
public bool Equals(CircleBorder other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return Equals(this.side, other.side);
}
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((CircleBorder) obj);
}
public override int GetHashCode() {
return (this.side != null ? this.side.GetHashCode() : 0);
}
public static bool operator ==(CircleBorder left, CircleBorder right) {
return Equals(left, right);
}
public static bool operator !=(CircleBorder left, CircleBorder right) {
return !Equals(left, right);
}
public override string ToString() {
return $"{this.GetType()}({this.side})";
}
}
}

11
Runtime/painting/circle_border.cs.meta


fileFormatVersion: 2
guid: a4d8740d6f8e64bd5bbe86f67d3019b7
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

331
Runtime/painting/rounded_rectangle_border.cs


using System;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.ui;
namespace Unity.UIWidgets.painting {
public class RoundedRectangleBorder : ShapeBorder, IEquatable<RoundedRectangleBorder> {
public RoundedRectangleBorder(
BorderSide side = null,
BorderRadius borderRadius = null
) {
this.side = side ?? BorderSide.none;
this.borderRadius = borderRadius ?? BorderRadius.zero;
}
public readonly BorderSide side;
public readonly BorderRadius borderRadius;
public override EdgeInsets dimensions {
get { return EdgeInsets.all(this.side.width); }
}
public override ShapeBorder scale(double t) {
return new RoundedRectangleBorder(
side: this.side.scale(t),
borderRadius: this.borderRadius * t
);
}
public override ShapeBorder lerpFrom(ShapeBorder a, double t) {
if (a is RoundedRectangleBorder border) {
return new RoundedRectangleBorder(
side: BorderSide.lerp(border.side, this.side, t),
borderRadius: BorderRadius.lerp(border.borderRadius, this.borderRadius, t)
);
}
if (a is CircleBorder circleBorder) {
return new _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(circleBorder.side, this.side, t),
borderRadius: this.borderRadius,
circleness: 1.0 - t
);
}
return base.lerpFrom(a, t);
}
public override ShapeBorder lerpTo(ShapeBorder b, double t) {
if (b is RoundedRectangleBorder border) {
return new RoundedRectangleBorder(
side: BorderSide.lerp(this.side, border.side, t),
borderRadius: BorderRadius.lerp(this.borderRadius, border.borderRadius, t)
);
}
if (b is CircleBorder circleBorder) {
return new _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(this.side, circleBorder.side, t),
borderRadius: this.borderRadius,
circleness: t
);
}
return base.lerpTo(b, t);
}
public override Path getInnerPath(Rect rect) {
var path = new Path();
path.addRRect(this.borderRadius.toRRect(rect).deflate(this.side.width));
return path;
}
public override Path getOuterPath(Rect rect) {
var path = new Path();
path.addRRect(this.borderRadius.toRRect(rect));
return path;
}
public override void paint(Canvas canvas, Rect rect) {
switch (this.side.style) {
case BorderStyle.none:
break;
case BorderStyle.solid:
double width = this.side.width;
if (width == 0.0) {
canvas.drawRRect(this.borderRadius.toRRect(rect), this.side.toPaint());
} else {
RRect outer = this.borderRadius.toRRect(rect);
RRect inner = outer.deflate(width);
Paint paint = new Paint {
color = this.side.color,
};
canvas.drawDRRect(outer, inner, paint);
}
break;
}
}
public bool Equals(RoundedRectangleBorder other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return Equals(this.side, other.side) && Equals(this.borderRadius, other.borderRadius);
}
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((RoundedRectangleBorder) obj);
}
public override int GetHashCode() {
unchecked {
return ((this.side != null ? this.side.GetHashCode() : 0) * 397) ^
(this.borderRadius != null ? this.borderRadius.GetHashCode() : 0);
}
}
public static bool operator ==(RoundedRectangleBorder left, RoundedRectangleBorder right) {
return Equals(left, right);
}
public static bool operator !=(RoundedRectangleBorder left, RoundedRectangleBorder right) {
return !Equals(left, right);
}
public override string ToString() {
return $"{this.GetType()}({this.side}, {this.borderRadius})";
}
}
class _RoundedRectangleToCircleBorder : ShapeBorder, IEquatable<_RoundedRectangleToCircleBorder> {
public _RoundedRectangleToCircleBorder(
BorderSide side = null,
BorderRadius borderRadius = null,
double circleness = 0.0
) {
this.side = side ?? BorderSide.none;
this.borderRadius = borderRadius ?? BorderRadius.zero;
this.circleness = circleness;
}
public readonly BorderSide side;
public readonly BorderRadius borderRadius;
public readonly double circleness;
public override EdgeInsets dimensions {
get { return EdgeInsets.all(this.side.width); }
}
public override ShapeBorder scale(double t) {
return new _RoundedRectangleToCircleBorder(
side: this.side.scale(t),
borderRadius: this.borderRadius * t,
circleness: t
);
}
public override ShapeBorder lerpFrom(ShapeBorder a, double t) {
if (a is RoundedRectangleBorder rectBorder) {
return new _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(rectBorder.side, this.side, t),
borderRadius: BorderRadius.lerp(rectBorder.borderRadius, this.borderRadius, t),
circleness: this.circleness * t
);
}
if (a is CircleBorder circleBorder) {
return new _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(circleBorder.side, this.side, t),
borderRadius: this.borderRadius,
circleness: this.circleness + (1.0 - this.circleness) * (1.0 - t)
);
}
if (a is _RoundedRectangleToCircleBorder border) {
return new _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(border.side, this.side, t),
borderRadius: BorderRadius.lerp(border.borderRadius, this.borderRadius, t),
circleness: MathUtils.lerpDouble(border.circleness, this.circleness, t)
);
}
return base.lerpFrom(a, t);
}
public override ShapeBorder lerpTo(ShapeBorder b, double t) {
if (b is RoundedRectangleBorder rectBorder) {
return new _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(this.side, rectBorder.side, t),
borderRadius: BorderRadius.lerp(this.borderRadius, rectBorder.borderRadius, t),
circleness: this.circleness * (1.0 - t)
);
}
if (b is CircleBorder circleBorder) {
return new _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(this.side, circleBorder.side, t),
borderRadius: this.borderRadius,
circleness: this.circleness + (1.0 - this.circleness) * t
);
}
if (b is _RoundedRectangleToCircleBorder border) {
return new _RoundedRectangleToCircleBorder(
side: BorderSide.lerp(this.side, border.side, t),
borderRadius: BorderRadius.lerp(this.borderRadius, border.borderRadius, t),
circleness: MathUtils.lerpDouble(this.circleness, border.circleness, t)
);
}
return base.lerpTo(b, t);
}
Rect _adjustRect(Rect rect) {
if (this.circleness == 0.0 || rect.width == rect.height) {
return rect;
}
if (rect.width < rect.height) {
double delta = this.circleness * (rect.height - rect.width) / 2.0;
return Rect.fromLTRB(
rect.left,
rect.top + delta,
rect.right,
rect.bottom - delta
);
} else {
double delta = this.circleness * (rect.width - rect.height) / 2.0;
return Rect.fromLTRB(
rect.left + delta,
rect.top,
rect.right - delta,
rect.bottom
);
}
}
BorderRadius _adjustBorderRadius(Rect rect) {
BorderRadius resolvedRadius = this.borderRadius;
if (this.circleness == 0.0) {
return resolvedRadius;
}
return BorderRadius.lerp(resolvedRadius, BorderRadius.circular(rect.shortestSide / 2.0), this.circleness);
}
public override Path getInnerPath(Rect rect) {
var path = new Path();
path.addRRect(this._adjustBorderRadius(rect).toRRect(this._adjustRect(rect)).deflate(this.side.width));
return path;
}
public override Path getOuterPath(Rect rect) {
var path = new Path();
path.addRRect(this._adjustBorderRadius(rect).toRRect(this._adjustRect(rect)));
return path;
}
public override void paint(Canvas canvas, Rect rect) {
switch (this.side.style) {
case BorderStyle.none:
break;
case BorderStyle.solid:
double width = this.side.width;
if (width == 0.0) {
canvas.drawRRect(this._adjustBorderRadius(rect).toRRect(this._adjustRect(rect)),
this.side.toPaint());
} else {
RRect outer = this._adjustBorderRadius(rect).toRRect(this._adjustRect(rect));
RRect inner = outer.deflate(width);
Paint paint = new Paint {
color = this.side.color,
};
canvas.drawDRRect(outer, inner, paint);
}
break;
}
}
public bool Equals(_RoundedRectangleToCircleBorder other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return Equals(this.side, other.side) && Equals(this.borderRadius, other.borderRadius) &&
this.circleness.Equals(other.circleness);
}
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((_RoundedRectangleToCircleBorder) obj);
}
public override int GetHashCode() {
unchecked {
var hashCode = (this.side != null ? this.side.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.borderRadius != null ? this.borderRadius.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ this.circleness.GetHashCode();
return hashCode;
}
}
public static bool operator ==(_RoundedRectangleToCircleBorder left, _RoundedRectangleToCircleBorder right) {
return Equals(left, right);
}
public static bool operator !=(_RoundedRectangleToCircleBorder left, _RoundedRectangleToCircleBorder right) {
return !Equals(left, right);
}
public override string ToString() {
return $"RoundedRectangleBorder({this.side}, {this.borderRadius}, " +
$"{this.circleness * 100:F1}% of the way to being a CircleBorder)";
}
}
}

11
Runtime/painting/rounded_rectangle_border.cs.meta


fileFormatVersion: 2
guid: 72dec4218f16947789f8e842bc7aef5a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

277
Runtime/painting/shape_decoration.cs


using System;
using System.Collections.Generic;
using Unity.UIWidgets.foundation;
using Unity.UIWidgets.ui;
namespace Unity.UIWidgets.painting {
public class ShapeDecoration : Decoration, IEquatable<ShapeDecoration> {
public ShapeDecoration(
Color color = null,
DecorationImage image = null,
Gradient gradient = null,
List<BoxShadow> shadows = null,
ShapeBorder shape = null
) {
D.assert(!(color != null && gradient != null));
D.assert(shape != null);
this.color = color;
this.image = image;
this.gradient = gradient;
this.shadows = shadows;
this.shape = shape;
}
public readonly Color color;
public readonly DecorationImage image;
public readonly Gradient gradient;
public readonly List<BoxShadow> shadows;
public readonly ShapeBorder shape;
public static ShapeDecoration fromBoxDecoration(BoxDecoration source) {
ShapeBorder shape = null;
switch (source.shape) {
case BoxShape.circle:
if (source.border != null) {
D.assert(source.border.isUniform);
shape = new CircleBorder(side: source.border.top);
} else {
shape = new CircleBorder();
}
break;
case BoxShape.rectangle:
if (source.borderRadius != null) {
D.assert(source.border == null || source.border.isUniform);
shape = new RoundedRectangleBorder(
side: source.border?.top ?? BorderSide.none,
borderRadius: source.borderRadius
);
} else {
shape = source.border ?? new Border();
}
break;
}
return new ShapeDecoration(
color: source.color,
image: source.image,
gradient: source.gradient,
shadows: source.boxShadow,
shape: shape
);
}
public override EdgeInsets padding {
get { return this.shape.dimensions; }
}
public override bool isComplex {
get { return this.shadows != null; }
}
public override Decoration lerpFrom(Decoration a, double t) {
if (a is BoxDecoration decoration) {
return ShapeDecoration.lerp(ShapeDecoration.fromBoxDecoration(decoration), this, t);
} else if (a == null || a is ShapeDecoration) {
return ShapeDecoration.lerp(a, this, t);
}
return base.lerpFrom(a, t);
}
public override Decoration lerpTo(Decoration b, double t) {
if (b is BoxDecoration decoration) {
return ShapeDecoration.lerp(this, ShapeDecoration.fromBoxDecoration(decoration), t);
} else if (b == null || b is ShapeDecoration) {
return ShapeDecoration.lerp(this, b, t);
}
return base.lerpTo(b, t);
}
public static ShapeDecoration lerp(ShapeDecoration a, ShapeDecoration b, double t) {
if (a == null && b == null) {
return null;
}
if (a != null && b != null) {
if (t == 0.0) {
return a;
}
if (t == 1.0) {
return b;
}
}
return new ShapeDecoration(
color: Color.lerp(a?.color, b?.color, t),
gradient: Gradient.lerp(a?.gradient, b?.gradient, t),
image: t < 0.5 ? a.image : b.image,
shadows: BoxShadow.lerpList(a?.shadows, b?.shadows, t),
shape: ShapeBorder.lerp(a?.shape, b?.shape, t)
);
}
public bool Equals(ShapeDecoration other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return Equals(this.color, other.color) && Equals(this.image, other.image) &&
Equals(this.gradient, other.gradient) && Equals(this.shadows, other.shadows) &&
Equals(this.shape, other.shape);
}
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((ShapeDecoration) obj);
}
public override int GetHashCode() {
unchecked {
var hashCode = (this.color != null ? this.color.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.image != null ? this.image.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.gradient != null ? this.gradient.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.shadows != null ? this.shadows.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.shape != null ? this.shape.GetHashCode() : 0);
return hashCode;
}
}
public static bool operator ==(ShapeDecoration left, ShapeDecoration right) {
return Equals(left, right);
}
public static bool operator !=(ShapeDecoration left, ShapeDecoration right) {
return !Equals(left, right);
}
public override void debugFillProperties(DiagnosticPropertiesBuilder properties) {
base.debugFillProperties(properties);
properties.defaultDiagnosticsTreeStyle = DiagnosticsTreeStyle.whitespace;
properties.add(new DiagnosticsProperty<Color>("color", this.color,
defaultValue: Diagnostics.kNullDefaultValue));
properties.add(new DiagnosticsProperty<Gradient>("gradient", this.gradient,
defaultValue: Diagnostics.kNullDefaultValue));
properties.add(new DiagnosticsProperty<DecorationImage>("image", this.image,
defaultValue: Diagnostics.kNullDefaultValue));
properties.add(new EnumerableProperty<BoxShadow>("shadows", this.shadows,
defaultValue: Diagnostics.kNullDefaultValue, style: DiagnosticsTreeStyle.whitespace));
properties.add(new DiagnosticsProperty<ShapeBorder>("shape", this.shape));
}
public override bool hitTest(Size size, Offset position) {
return this.shape.getOuterPath(Offset.zero & size).contains(position);
}
public override BoxPainter createBoxPainter(VoidCallback onChanged = null) {
D.assert(onChanged != null || this.image == null);
return new _ShapeDecorationPainter(this, onChanged);
}
}
class _ShapeDecorationPainter : BoxPainter {
public _ShapeDecorationPainter(ShapeDecoration decoration, VoidCallback onChanged)
: base(onChanged) {
D.assert(decoration != null);
this._decoration = decoration;
}
readonly ShapeDecoration _decoration;
Rect _lastRect;
Path _outerPath;
Path _innerPath;
Paint _interiorPaint;
int? _shadowCount;
Path[] _shadowPaths;
Paint[] _shadowPaints;
void _precache(Rect rect) {
D.assert(rect != null);
if (rect == this._lastRect) {
return;
}
if (this._interiorPaint == null && (this._decoration.color != null || this._decoration.gradient != null)) {
this._interiorPaint = new Paint();
if (this._decoration.color != null) {
this._interiorPaint.color = this._decoration.color;
}
}
if (this._decoration.gradient != null) {
// this._interiorPaint.shader = this._decoration.gradient.createShader(rect);
}
if (this._decoration.shadows != null) {
if (this._shadowCount == null) {
this._shadowCount = this._decoration.shadows.Count;
this._shadowPaths = new Path[this._shadowCount.Value];
this._shadowPaints = new Paint[this._shadowCount.Value];
for (int index = 0; index < this._shadowCount.Value; index += 1) {
this._shadowPaints[index] = this._decoration.shadows[index].toPaint();
}
}
for (int index = 0; index < this._shadowCount; index += 1) {
BoxShadow shadow = this._decoration.shadows[index];
this._shadowPaths[index] = this._decoration.shape.getOuterPath(
rect.shift(shadow.offset).inflate(shadow.spreadRadius));
}
}
if (this._interiorPaint != null || this._shadowCount != null) {
this._outerPath = this._decoration.shape.getOuterPath(rect);
}
if (this._decoration.image != null) {
this._innerPath = this._decoration.shape.getInnerPath(rect);
}
this._lastRect = rect;
}
void _paintShadows(Canvas canvas) {
if (this._shadowCount != null) {
for (int index = 0; index < this._shadowCount.Value; index += 1) {
canvas.drawPath(this._shadowPaths[index], this._shadowPaints[index]);
}
}
}
void _paintInterior(Canvas canvas) {
if (this._interiorPaint != null) {
canvas.drawPath(this._outerPath, this._interiorPaint);
}
}
DecorationImagePainter _imagePainter;
void _paintImage(Canvas canvas, ImageConfiguration configuration) {
if (this._decoration.image == null) {
return;
}
this._imagePainter = this._imagePainter ?? this._decoration.image.createPainter(this.onChanged);
this._imagePainter.paint(canvas, this._lastRect, this._innerPath, configuration);
}
public override void Dispose() {
this._imagePainter?.Dispose();
base.Dispose();
}
public override void paint(Canvas canvas, Offset offset, ImageConfiguration configuration) {
D.assert(configuration != null);
D.assert(configuration.size != null);
Rect rect = offset & configuration.size;
this._precache(rect);
this._paintShadows(canvas);
this._paintInterior(canvas);
this._paintImage(canvas, configuration);
this._decoration.shape.paint(canvas, rect);
}
}
}

11
Runtime/painting/shape_decoration.cs.meta


fileFormatVersion: 2
guid: 42721d35952224e5aac8229d0f85940a
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:

467
Runtime/painting/stadium_border.cs


using System;
using Unity.UIWidgets.ui;
namespace Unity.UIWidgets.painting {
public class StadiumBorder : ShapeBorder, IEquatable<StadiumBorder> {
public StadiumBorder(BorderSide side = null) {
this.side = side ?? BorderSide.none;
}
public readonly BorderSide side;
public override EdgeInsets dimensions {
get { return EdgeInsets.all(this.side.width); }
}
public override ShapeBorder scale(double t) {
return new StadiumBorder(side: this.side.scale(t));
}
public override ShapeBorder lerpFrom(ShapeBorder a, double t) {
if (a is StadiumBorder stadiumBorder) {
return new StadiumBorder(side: BorderSide.lerp(stadiumBorder.side, this.side, t));
}
if (a is CircleBorder circleBorder) {
return new _StadiumToCircleBorder(
side: BorderSide.lerp(circleBorder.side, this.side, t),
circleness: 1.0 - t
);
}
if (a is RoundedRectangleBorder rectBorder) {
return new _StadiumToRoundedRectangleBorder(
side: BorderSide.lerp(rectBorder.side, this.side, t),
borderRadius: rectBorder.borderRadius,
rectness: 1.0 - t
);
}
return base.lerpFrom(a, t);
}
public override ShapeBorder lerpTo(ShapeBorder b, double t) {
if (b is StadiumBorder stadiumBorder) {
return new StadiumBorder(side: BorderSide.lerp(this.side, stadiumBorder.side, t));
}
if (b is CircleBorder circleBorder) {
return new _StadiumToCircleBorder(
side: BorderSide.lerp(this.side, circleBorder.side, t),
circleness: t
);
}
if (b is RoundedRectangleBorder rectBorder) {
return new _StadiumToRoundedRectangleBorder(
side: BorderSide.lerp(this.side, rectBorder.side, t),
borderRadius: rectBorder.borderRadius,
rectness: t
);
}
return base.lerpTo(b, t);
}
public override Path getInnerPath(Rect rect) {
Radius radius = Radius.circular(rect.shortestSide / 2.0);
var path = new Path();
path.addRRect(RRect.fromRectAndRadius(rect, radius).deflate(this.side.width));
return path;
}
public override Path getOuterPath(Rect rect) {
Radius radius = Radius.circular(rect.shortestSide / 2.0);
var path = new Path();
path.addRRect(RRect.fromRectAndRadius(rect, radius));
return path;
}
public override void paint(Canvas canvas, Rect rect) {
switch (this.side.style) {
case BorderStyle.none:
break;
case BorderStyle.solid:
Radius radius = Radius.circular(rect.shortestSide / 2.0);
canvas.drawRRect(
RRect.fromRectAndRadius(rect, radius).deflate(this.side.width / 2.0),
this.side.toPaint()
);
break;
}
}
public bool Equals(StadiumBorder other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return Equals(this.side, other.side);
}
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((StadiumBorder) obj);
}
public override int GetHashCode() {
return (this.side != null ? this.side.GetHashCode() : 0);
}
public static bool operator ==(StadiumBorder left, StadiumBorder right) {
return Equals(left, right);
}
public static bool operator !=(StadiumBorder left, StadiumBorder right) {
return !Equals(left, right);
}
public override string ToString() {
return $"{this.GetType()}({this.side})";
}
}
class _StadiumToCircleBorder : ShapeBorder, IEquatable<_StadiumToCircleBorder> {
public _StadiumToCircleBorder(
BorderSide side = null,
double circleness = 0.0
) {
this.side = BorderSide.none;
this.circleness = circleness;
}
public readonly BorderSide side;
public readonly double circleness;
public override EdgeInsets dimensions {
get { return EdgeInsets.all(this.side.width); }
}
public override ShapeBorder scale(double t) {
return new _StadiumToCircleBorder(
side: this.side.scale(t),
circleness: t
);
}
public override ShapeBorder lerpFrom(ShapeBorder a, double t) {
if (a is StadiumBorder stadiumBorder) {
return new _StadiumToCircleBorder(
side: BorderSide.lerp(stadiumBorder.side, this.side, t),
circleness: this.circleness * t
);
}
if (a is CircleBorder circleBorder) {
return new _StadiumToCircleBorder(
side: BorderSide.lerp(circleBorder.side, this.side, t),
circleness: this.circleness + (1.0 - this.circleness) * (1.0 - t)
);
}
if (a is _StadiumToCircleBorder border) {
return new _StadiumToCircleBorder(
side: BorderSide.lerp(border.side, this.side, t),
circleness: MathUtils.lerpDouble(border.circleness, this.circleness, t)
);
}
return base.lerpFrom(a, t);
}
public override ShapeBorder lerpTo(ShapeBorder b, double t) {
if (b is StadiumBorder stadiumBorder) {
return new _StadiumToCircleBorder(
side: BorderSide.lerp(this.side, stadiumBorder.side, t),
circleness: this.circleness * (1.0 - t)
);
}
if (b is CircleBorder circleBorder) {
return new _StadiumToCircleBorder(
side: BorderSide.lerp(this.side, circleBorder.side, t),
circleness: this.circleness + (1.0 - this.circleness) * t
);
}
if (b is _StadiumToCircleBorder border) {
return new _StadiumToCircleBorder(
side: BorderSide.lerp(this.side, border.side, t),
circleness: MathUtils.lerpDouble(this.circleness, border.circleness, t)
);
}
return base.lerpTo(b, t);
}
Rect _adjustRect(Rect rect) {
if (this.circleness == 0.0 || rect.width == rect.height) {
return rect;
}
if (rect.width < rect.height) {
double delta = this.circleness * (rect.height - rect.width) / 2.0;
return Rect.fromLTRB(
rect.left,
rect.top + delta,
rect.right,
rect.bottom - delta
);
} else {
double delta = this.circleness * (rect.width - rect.height) / 2.0;
return Rect.fromLTRB(
rect.left + delta,
rect.top,
rect.right - delta,
rect.bottom
);
}
}
BorderRadius _adjustBorderRadius(Rect rect) {
return BorderRadius.circular(rect.shortestSide / 2.0);
}
public override Path getInnerPath(Rect rect) {
var path = new Path();
path.addRRect(this._adjustBorderRadius(rect).toRRect(this._adjustRect(rect)).deflate(this.side.width));
return path;
}
public override Path getOuterPath(Rect rect) {
var path = new Path();
path.addRRect(this._adjustBorderRadius(rect).toRRect(this._adjustRect(rect)));
return path;
}
public override void paint(Canvas canvas, Rect rect) {
switch (this.side.style) {
case BorderStyle.none:
break;
case BorderStyle.solid:
double width = this.side.width;
if (width == 0.0) {
canvas.drawRRect(this._adjustBorderRadius(rect).toRRect(this._adjustRect(rect)),
this.side.toPaint());
} else {
RRect outer = this._adjustBorderRadius(rect).toRRect(this._adjustRect(rect));
RRect inner = outer.deflate(width);
Paint paint = new Paint {
color = this.side.color,
};
canvas.drawDRRect(outer, inner, paint);
}
break;
}
}
public bool Equals(_StadiumToCircleBorder other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return Equals(this.side, other.side) && this.circleness.Equals(other.circleness);
}
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((_StadiumToCircleBorder) obj);
}
public override int GetHashCode() {
unchecked {
return ((this.side != null ? this.side.GetHashCode() : 0) * 397) ^ this.circleness.GetHashCode();
}
}
public static bool operator ==(_StadiumToCircleBorder left, _StadiumToCircleBorder right) {
return Equals(left, right);
}
public static bool operator !=(_StadiumToCircleBorder left, _StadiumToCircleBorder right) {
return !Equals(left, right);
}
public override string ToString() {
return $"StadiumBorder($side, {this.circleness * 100:F1}% " +
"of the way to being a CircleBorder)";
}
}
class _StadiumToRoundedRectangleBorder : ShapeBorder, IEquatable<_StadiumToRoundedRectangleBorder> {
public _StadiumToRoundedRectangleBorder(
BorderSide side = null,
BorderRadius borderRadius = null,
double rectness = 0.0
) {
this.side = side ?? BorderSide.none;
this.borderRadius = borderRadius ?? BorderRadius.zero;
this.rectness = rectness;
}
public readonly BorderSide side;
public readonly BorderRadius borderRadius;
public readonly double rectness;
public override EdgeInsets dimensions {
get { return EdgeInsets.all(this.side.width); }
}
public override ShapeBorder scale(double t) {
return new _StadiumToRoundedRectangleBorder(
side: this.side.scale(t),
borderRadius: this.borderRadius * t,
rectness: t
);
}
public override ShapeBorder lerpFrom(ShapeBorder a, double t) {
if (a is StadiumBorder stadiumBorder) {
return new _StadiumToRoundedRectangleBorder(
side: BorderSide.lerp(stadiumBorder.side, this.side, t),
borderRadius: this.borderRadius,
rectness: this.rectness * t
);
}
if (a is RoundedRectangleBorder rectBorder) {
return new _StadiumToRoundedRectangleBorder(
side: BorderSide.lerp(rectBorder.side, this.side, t),
borderRadius: this.borderRadius,
rectness: this.rectness + (1.0 - this.rectness) * (1.0 - t)
);
}
if (a is _StadiumToRoundedRectangleBorder border) {
return new _StadiumToRoundedRectangleBorder(
side: BorderSide.lerp(border.side, this.side, t),
borderRadius: BorderRadius.lerp(border.borderRadius, this.borderRadius, t),
rectness: MathUtils.lerpDouble(border.rectness, this.rectness, t)
);
}
return base.lerpFrom(a, t);
}
public override ShapeBorder lerpTo(ShapeBorder b, double t) {
if (b is StadiumBorder stadiumBorder) {
return new _StadiumToRoundedRectangleBorder(
side: BorderSide.lerp(this.side, stadiumBorder.side, t),
borderRadius: this.borderRadius,
rectness: this.rectness * (1.0 - t)
);
}
if (b is RoundedRectangleBorder rectBorder) {
return new _StadiumToRoundedRectangleBorder(
side: BorderSide.lerp(this.side, rectBorder.side, t),
borderRadius: this.borderRadius,
rectness: this.rectness + (1.0 - this.rectness) * t
);
}
if (b is _StadiumToRoundedRectangleBorder border) {
return new _StadiumToRoundedRectangleBorder(
side: BorderSide.lerp(this.side, border.side, t),
borderRadius: BorderRadius.lerp(this.borderRadius, border.borderRadius, t),
rectness: MathUtils.lerpDouble(this.rectness, border.rectness, t)
);
}
return base.lerpTo(b, t);
}
BorderRadius _adjustBorderRadius(Rect rect) {
return BorderRadius.lerp(
this.borderRadius,
BorderRadius.all(Radius.circular(rect.shortestSide / 2.0)),
1.0 - this.rectness
);
}
public override Path getInnerPath(Rect rect) {
var path = new Path();
path.addRRect(this._adjustBorderRadius(rect).toRRect(rect).deflate(this.side.width));
return path;
}
public override Path getOuterPath(Rect rect) {
var path = new Path();
path.addRRect(this._adjustBorderRadius(rect).toRRect(rect));
return path;
}
public override void paint(Canvas canvas, Rect rect) {
switch (this.side.style) {
case BorderStyle.none:
break;
case BorderStyle.solid:
double width = this.side.width;
if (width == 0.0) {
canvas.drawRRect(this._adjustBorderRadius(rect).toRRect(rect), this.side.toPaint());
} else {
RRect outer = this._adjustBorderRadius(rect).toRRect(rect);
RRect inner = outer.deflate(width);
Paint paint = new Paint {
color = this.side.color,
};
canvas.drawDRRect(outer, inner, paint);
}
break;
}
}
public bool Equals(_StadiumToRoundedRectangleBorder other) {
if (ReferenceEquals(null, other)) {
return false;
}
if (ReferenceEquals(this, other)) {
return true;
}
return Equals(this.side, other.side) && Equals(this.borderRadius, other.borderRadius) &&
this.rectness.Equals(other.rectness);
}
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((_StadiumToRoundedRectangleBorder) obj);
}
public override int GetHashCode() {
unchecked {
var hashCode = (this.side != null ? this.side.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ (this.borderRadius != null ? this.borderRadius.GetHashCode() : 0);
hashCode = (hashCode * 397) ^ this.rectness.GetHashCode();
return hashCode;
}
}
public static bool operator ==(_StadiumToRoundedRectangleBorder left, _StadiumToRoundedRectangleBorder right) {
return Equals(left, right);
}
public static bool operator !=(_StadiumToRoundedRectangleBorder left, _StadiumToRoundedRectangleBorder right) {
return !Equals(left, right);
}
public override string ToString() {
return $"StadiumBorder({this.side}, {this.borderRadius}, " +
$"{this.rectness * 100:F1}% of the way to being a " +
"RoundedRectangleBorder)";
}
}
}

11
Runtime/painting/stadium_border.cs.meta


fileFormatVersion: 2
guid: cf6d45cec381142ada29150ceb442783
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant:
正在加载...
取消
保存