using System; using System.Collections.Generic; using System.Linq; using Unity.UIWidgets.foundation; using Unity.UIWidgets.ui; using UnityEngine; using Color = Unity.UIWidgets.ui.Color; using Rect = Unity.UIWidgets.ui.Rect; namespace Unity.UIWidgets.painting { class _ColorsAndStops { public _ColorsAndStops(List colors, List stops) { this.colors = colors; this.stops = stops; } public readonly List colors; public readonly List stops; public static _ColorsAndStops _interpolateColorsAndStops( List aColors, List aStops, List bColors, List bStops, float t) { D.assert(aColors.Count == bColors.Count, "Cannot interpolate between two gradients with a different number of colors."); D.assert((aStops == null && aColors.Count == 2) || (aStops != null && aStops.Count == aColors.Count)); D.assert((bStops == null && bColors.Count == 2) || (bStops != null && bStops.Count == bColors.Count)); List interpolatedColors = new List(); for (int i = 0; i < aColors.Count; i += 1) { interpolatedColors.Add(Color.lerp(aColors[i], bColors[i], t)); } List interpolatedStops = null; if (aStops != null || bStops != null) { aStops = aStops ?? new List {0.0f, 1.0f}; bStops = bStops ?? new List {0.0f, 1.0f}; D.assert(aStops.Count == bStops.Count); interpolatedStops = new List(); for (int i = 0; i < aStops.Count; i += 1) { interpolatedStops.Add(MathUtils.lerpFloat(aStops[i], bStops[i], t).clamp(0.0f, 1.0f)); } } return new _ColorsAndStops(interpolatedColors, interpolatedStops); } } public abstract class Gradient { public Gradient( List colors = null, List stops = null ) { D.assert(colors != null); this.colors = colors; this.stops = stops; } public readonly List colors; public readonly List stops; protected List _impliedStops() { if (this.stops != null) { return this.stops; } if (this.colors.Count == 2) { return null; } D.assert(this.colors.Count >= 2, "colors list must have at least two colors"); float separation = 1.0f / (this.colors.Count - 1); return Enumerable.Range(0, this.colors.Count).Select(i => i * separation).ToList(); } public abstract PaintShader createShader(Rect rect); public abstract Gradient scale(float factor); protected virtual Gradient lerpFrom(Gradient a, float t) { if (a == null) { return this.scale(t); } return null; } protected virtual Gradient lerpTo(Gradient b, float t) { if (b == null) { return this.scale(1.0f - t); } return null; } public static Gradient lerp(Gradient a, Gradient b, float t) { Gradient result = null; if (b != null) { result = b.lerpFrom(a, t); // if a is null, this must return non-null } if (result == null && a != null) { result = a.lerpTo(b, t); // if b is null, this must return non-null } if (result != null) { return result; } if (a == null && b == null) { return null; } D.assert(a != null && b != null); return t < 0.5 ? a.scale(1.0f - (t * 2.0f)) : b.scale((t - 0.5f) * 2.0f); } } public class LinearGradient : Gradient, IEquatable { public LinearGradient( Alignment begin = null, Alignment end = null, List colors = null, List stops = null, TileMode tileMode = TileMode.clamp ) : base(colors: colors, stops: stops) { this.begin = begin ?? Alignment.centerLeft; this.end = end ?? Alignment.centerRight; this.tileMode = tileMode; } public readonly Alignment begin; public readonly Alignment end; public readonly TileMode tileMode; public override PaintShader createShader(Rect rect) { return ui.Gradient.linear( this.begin.withinRect(rect), this.end.withinRect(rect), this.colors, this._impliedStops(), this.tileMode ); } public override Gradient scale(float factor) { return new LinearGradient( begin: this.begin, end: this.end, colors: this.colors.Select(color => Color.lerp(null, color, factor)).ToList(), stops: this.stops, tileMode: this.tileMode ); } protected override Gradient lerpFrom(Gradient a, float t) { if (a == null || (a is LinearGradient && a.colors.Count == this.colors.Count)) { return LinearGradient.lerp((LinearGradient) a, this, t); } return base.lerpFrom(a, t); } protected override Gradient lerpTo(Gradient b, float t) { if (b == null || (b is LinearGradient && b.colors.Count == this.colors.Count)) { return LinearGradient.lerp(this, (LinearGradient) b, t); } return base.lerpTo(b, t); } public static LinearGradient lerp(LinearGradient a, LinearGradient b, float t) { if (a == null && b == null) { return null; } if (a == null) { return (LinearGradient) b.scale(t); } if (b == null) { return (LinearGradient) a.scale(1.0f - t); } _ColorsAndStops interpolated = _ColorsAndStops._interpolateColorsAndStops(a.colors, a.stops, b.colors, b.stops, t); return new LinearGradient( begin: Alignment.lerp(a.begin, b.begin, t), end: Alignment.lerp(a.end, b.end, t), colors: interpolated.colors, stops: interpolated.stops, tileMode: t < 0.5 ? a.tileMode : b.tileMode ); } public bool Equals(LinearGradient other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return this.colors.equalsList(other.colors) && this.stops.equalsList(other.stops) && Equals(this.begin, other.begin) && Equals(this.end, other.end) && this.tileMode == other.tileMode; } 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((LinearGradient) obj); } public override int GetHashCode() { unchecked { var hashCode = this.colors.hashList(); hashCode = (hashCode * 397) ^ this.stops.hashList(); hashCode = (hashCode * 397) ^ (this.begin != null ? this.begin.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (this.end != null ? this.end.GetHashCode() : 0); hashCode = (hashCode * 397) ^ (int) this.tileMode; return hashCode; } } public static bool operator ==(LinearGradient left, LinearGradient right) { return Equals(left, right); } public static bool operator !=(LinearGradient left, LinearGradient right) { return !Equals(left, right); } public override String ToString() { return $"{this.GetType()}({this.begin}, {this.end}," + $"{this.colors.toStringList()}, {this.stops.toStringList()}, {this.tileMode})"; } } public class RadialGradient : Gradient, IEquatable { public RadialGradient( Alignment center = null, float radius = 0.5f, List colors = null, List stops = null, TileMode tileMode = TileMode.clamp ) : base(colors: colors, stops: stops) { this.center = center ?? Alignment.center; this.radius = radius; this.tileMode = tileMode; } public readonly Alignment center; public readonly float radius; public readonly TileMode tileMode; public override PaintShader createShader(Rect rect) { return ui.Gradient.radial( this.center.withinRect(rect), this.radius * rect.shortestSide, this.colors, this._impliedStops(), this.tileMode ); } public override Gradient scale(float factor) { return new RadialGradient( center: this.center, radius: this.radius, colors: this.colors.Select(color => Color.lerp(null, color, factor)).ToList(), stops: this.stops, tileMode: this.tileMode ); } protected override Gradient lerpFrom(Gradient a, float t) { if (a == null || (a is RadialGradient && a.colors.Count == this.colors.Count)) { return RadialGradient.lerp((RadialGradient) a, this, t); } return base.lerpFrom(a, t); } protected override Gradient lerpTo(Gradient b, float t) { if (b == null || (b is RadialGradient && b.colors.Count == this.colors.Count)) { return RadialGradient.lerp(this, (RadialGradient) b, t); } return base.lerpTo(b, t); } public static RadialGradient lerp(RadialGradient a, RadialGradient b, float t) { if (a == null && b == null) { return null; } if (a == null) { return (RadialGradient) b.scale(t); } if (b == null) { return (RadialGradient) a.scale(1.0f - t); } _ColorsAndStops interpolated = _ColorsAndStops._interpolateColorsAndStops(a.colors, a.stops, b.colors, b.stops, t); return new RadialGradient( center: Alignment.lerp(a.center, b.center, t), radius: Mathf.Max(0.0f, MathUtils.lerpFloat(a.radius, b.radius, t)), colors: interpolated.colors, stops: interpolated.stops, tileMode: t < 0.5 ? a.tileMode : b.tileMode ); } public bool Equals(RadialGradient other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return this.colors.equalsList(other.colors) && this.stops.equalsList(other.stops) && Equals(this.center, other.center) && Equals(this.radius, other.radius) && this.tileMode == other.tileMode; } 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((RadialGradient) obj); } public override int GetHashCode() { unchecked { var hashCode = this.colors.hashList(); hashCode = (hashCode * 397) ^ this.stops.hashList(); hashCode = (hashCode * 397) ^ (this.center != null ? this.center.GetHashCode() : 0); hashCode = (hashCode * 397) ^ this.radius.GetHashCode(); hashCode = (hashCode * 397) ^ (int) this.tileMode; return hashCode; } } public static bool operator ==(RadialGradient left, RadialGradient right) { return Equals(left, right); } public static bool operator !=(RadialGradient left, RadialGradient right) { return !Equals(left, right); } public override String ToString() { return $"{this.GetType()}({this.center}, {this.radius}," + $"{this.colors.toStringList()}, {this.stops.toStringList()}, {this.tileMode})"; } } public class SweepGradient : Gradient, IEquatable { public SweepGradient( Alignment center = null, float startAngle = 0.0f, float endAngle = Mathf.PI * 2, List colors = null, List stops = null, TileMode tileMode = TileMode.clamp ) : base(colors: colors, stops: stops) { this.center = center ?? Alignment.center; this.startAngle = startAngle; this.endAngle = endAngle; this.tileMode = tileMode; } public readonly Alignment center; public readonly float startAngle; public readonly float endAngle; public readonly TileMode tileMode; public override PaintShader createShader(Rect rect) { return ui.Gradient.sweep( this.center.withinRect(rect), this.colors, this._impliedStops(), this.tileMode, this.startAngle, this.endAngle ); } public override Gradient scale(float factor) { return new SweepGradient( center: this.center, startAngle: this.startAngle, endAngle: this.endAngle, colors: this.colors.Select(color => Color.lerp(null, color, factor)).ToList(), stops: this.stops, tileMode: this.tileMode ); } protected override Gradient lerpFrom(Gradient a, float t) { if (a == null || (a is SweepGradient && a.colors.Count == this.colors.Count)) { return SweepGradient.lerp((SweepGradient) a, this, t); } return base.lerpFrom(a, t); } protected override Gradient lerpTo(Gradient b, float t) { if (b == null || (b is SweepGradient && b.colors.Count == this.colors.Count)) { return lerp(this, (SweepGradient) b, t); } return base.lerpTo(b, t); } public static SweepGradient lerp(SweepGradient a, SweepGradient b, float t) { if (a == null && b == null) { return null; } if (a == null) { return (SweepGradient) b.scale(t); } if (b == null) { return (SweepGradient) a.scale(1.0f - t); } _ColorsAndStops interpolated = _ColorsAndStops._interpolateColorsAndStops(a.colors, a.stops, b.colors, b.stops, t); return new SweepGradient( center: Alignment.lerp(a.center, b.center, t), startAngle: Mathf.Max(0.0f, MathUtils.lerpFloat(a.startAngle, b.startAngle, t)), endAngle: Mathf.Max(0.0f, MathUtils.lerpFloat(a.endAngle, b.endAngle, t)), colors: interpolated.colors, stops: interpolated.stops, tileMode: t < 0.5 ? a.tileMode : b.tileMode ); } public bool Equals(SweepGradient other) { if (ReferenceEquals(null, other)) { return false; } if (ReferenceEquals(this, other)) { return true; } return this.colors.equalsList(other.colors) && this.stops.equalsList(other.stops) && Equals(this.center, other.center) && Equals(this.startAngle, other.startAngle) && Equals(this.endAngle, other.endAngle) && this.tileMode == other.tileMode; } 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((SweepGradient) obj); } public override int GetHashCode() { unchecked { var hashCode = this.colors.hashList(); hashCode = (hashCode * 397) ^ this.stops.hashList(); hashCode = (hashCode * 397) ^ (this.center != null ? this.center.GetHashCode() : 0); hashCode = (hashCode * 397) ^ this.startAngle.GetHashCode(); hashCode = (hashCode * 397) ^ this.endAngle.GetHashCode(); hashCode = (hashCode * 397) ^ (int) this.tileMode; return hashCode; } } public static bool operator ==(SweepGradient left, SweepGradient right) { return Equals(left, right); } public static bool operator !=(SweepGradient left, SweepGradient right) { return !Equals(left, right); } public override String ToString() { return $"{this.GetType()}({this.center}, {this.startAngle}, {this.endAngle}, " + $"{this.colors.toStringList()}, {this.stops.toStringList()}, {this.tileMode})"; } } }