|
|
@@ -6,25 +6,90 @@ using Avalonia.Utilities;
|
|
|
|
|
|
namespace Avalonia.Media
|
|
|
{
|
|
|
+ /// <summary>
|
|
|
+ /// Represents a box shadow which can be attached to an element or control.
|
|
|
+ /// </summary>
|
|
|
public struct BoxShadow
|
|
|
{
|
|
|
+ private readonly static char[] s_Separator = new char[] { ' ', '\t' };
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the horizontal offset (distance) of the shadow.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Positive values place the shadow to the right of the element while
|
|
|
+ /// negative values place the shadow to the left.
|
|
|
+ /// </remarks>
|
|
|
public double OffsetX { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the vertical offset (distance) of the shadow.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Positive values place the shadow below the element while
|
|
|
+ /// negative values place the shadow above.
|
|
|
+ /// </remarks>
|
|
|
public double OffsetY { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the blur radius.
|
|
|
+ /// This is used to control the amount of blurring.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// The larger this value, the bigger the blur effect, so the shadow becomes larger and more transparent.
|
|
|
+ /// Negative values are not allowed. If not specified, the default (zero) is used and the shadow edge is sharp.
|
|
|
+ /// </remarks>
|
|
|
public double Blur { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the spread radius.
|
|
|
+ /// This is used to control the overall size of the shadow.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Positive values will cause the shadow to expand and grow larger, negative values will cause the shadow to shrink.
|
|
|
+ /// If not specified, the default (zero) is used and the shadow will be the same size as the element.
|
|
|
+ /// </remarks>
|
|
|
public double Spread { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets the color of the shadow.
|
|
|
+ /// </summary>
|
|
|
public Color Color { get; set; }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// Gets or sets a value indicating whether the shadow is inset and drawn within the element rather than outside of it.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// Inset changes the shadow to inside the element (as if the content was depressed inside the box).
|
|
|
+ /// If false (the default), the shadow is assumed to be a drop shadow (as if the box were raised above the content).
|
|
|
+ /// <br/><br/>
|
|
|
+ /// Inset shadows are drawn inside the element, above the background (even when it's transparent), but below any content.
|
|
|
+ /// </remarks>
|
|
|
public bool IsInset { get; set; }
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Indicates whether the current object is equal to another object of the same type.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="other">An object to compare with this object.</param>
|
|
|
+ /// <returns>
|
|
|
+ /// <c>true</c> if the current object is equal to the other parameter; otherwise, <c>false</c>.
|
|
|
+ /// </returns>
|
|
|
public bool Equals(in BoxShadow other)
|
|
|
{
|
|
|
- return OffsetX.Equals(other.OffsetX) && OffsetY.Equals(other.OffsetY) && Blur.Equals(other.Blur) && Spread.Equals(other.Spread) && Color.Equals(other.Color);
|
|
|
+ return OffsetX.Equals(other.OffsetX)
|
|
|
+ && OffsetY.Equals(other.OffsetY)
|
|
|
+ && Blur.Equals(other.Blur)
|
|
|
+ && Spread.Equals(other.Spread)
|
|
|
+ && Color.Equals(other.Color);
|
|
|
}
|
|
|
|
|
|
+ /// <inheritdoc/>
|
|
|
public override bool Equals(object? obj)
|
|
|
{
|
|
|
return obj is BoxShadow other && Equals(other);
|
|
|
}
|
|
|
|
|
|
+ /// <inheritdoc/>
|
|
|
public override int GetHashCode()
|
|
|
{
|
|
|
unchecked
|
|
|
@@ -38,8 +103,6 @@ namespace Avalonia.Media
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- private readonly static char[] s_Separator = new char[] { ' ', '\t' };
|
|
|
-
|
|
|
struct ArrayReader
|
|
|
{
|
|
|
private int _index;
|
|
|
@@ -55,20 +118,28 @@ namespace Avalonia.Media
|
|
|
{
|
|
|
s = null;
|
|
|
if (_index >= _arr.Length)
|
|
|
+ {
|
|
|
return false;
|
|
|
+ }
|
|
|
+
|
|
|
s = _arr[_index];
|
|
|
_index++;
|
|
|
+
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
public string ReadString()
|
|
|
{
|
|
|
- if(!TryReadString(out var rv))
|
|
|
+ if (!TryReadString(out var rv))
|
|
|
+ {
|
|
|
throw new FormatException();
|
|
|
+ }
|
|
|
+
|
|
|
return rv;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// <inheritdoc/>
|
|
|
public override string ToString()
|
|
|
{
|
|
|
var sb = StringBuilderCache.Acquire();
|
|
|
@@ -106,20 +177,45 @@ namespace Avalonia.Media
|
|
|
Color.ToString(sb);
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Parses a <see cref="BoxShadow"/> string.
|
|
|
+ /// </summary>
|
|
|
+ /// <remarks>
|
|
|
+ /// A box shadow may be specified in multiple formats with separate components:
|
|
|
+ /// <list type="bullet">
|
|
|
+ /// <item>Two, three, or four length values.</item>
|
|
|
+ /// <item>A color value.</item>
|
|
|
+ /// <item>An optional inset keyword.</item>
|
|
|
+ /// </list>
|
|
|
+ /// If only two length values are given they will be interpreted as <see cref="OffsetX"/> and <see cref="OffsetY"/>.
|
|
|
+ /// If a third value is given, it is interpreted as a <see cref="Blur"/>, and if a fourth value is given,
|
|
|
+ /// it is interpreted as <see cref="Spread"/>.
|
|
|
+ /// </remarks>
|
|
|
+ /// <param name="s">The input string to parse.</param>
|
|
|
+ /// <returns>A new <see cref="BoxShadow"/></returns>
|
|
|
public static unsafe BoxShadow Parse(string s)
|
|
|
{
|
|
|
- if(s == null)
|
|
|
+ if (s == null)
|
|
|
+ {
|
|
|
throw new ArgumentNullException();
|
|
|
+ }
|
|
|
+
|
|
|
if (s.Length == 0)
|
|
|
+ {
|
|
|
throw new FormatException();
|
|
|
+ }
|
|
|
|
|
|
var p = s.Split(s_Separator, StringSplitOptions.RemoveEmptyEntries);
|
|
|
if (p.Length == 1 && p[0] == "none")
|
|
|
+ {
|
|
|
return default;
|
|
|
-
|
|
|
+ }
|
|
|
+
|
|
|
if (p.Length < 3 || p.Length > 6)
|
|
|
+ {
|
|
|
throw new FormatException();
|
|
|
-
|
|
|
+ }
|
|
|
+
|
|
|
bool inset = false;
|
|
|
|
|
|
var tokenizer = new ArrayReader(p);
|
|
|
@@ -135,16 +231,20 @@ namespace Avalonia.Media
|
|
|
var offsetY = double.Parse(tokenizer.ReadString(), CultureInfo.InvariantCulture);
|
|
|
double blur = 0;
|
|
|
double spread = 0;
|
|
|
-
|
|
|
|
|
|
tokenizer.TryReadString(out var token3);
|
|
|
tokenizer.TryReadString(out var token4);
|
|
|
tokenizer.TryReadString(out var token5);
|
|
|
|
|
|
- if (token4 != null)
|
|
|
+ if (token4 != null)
|
|
|
+ {
|
|
|
blur = double.Parse(token3!, CultureInfo.InvariantCulture);
|
|
|
+ }
|
|
|
+
|
|
|
if (token5 != null)
|
|
|
+ {
|
|
|
spread = double.Parse(token4!, CultureInfo.InvariantCulture);
|
|
|
+ }
|
|
|
|
|
|
var color = Color.Parse(token5 ?? token4 ?? token3!);
|
|
|
return new BoxShadow
|
|
|
@@ -158,12 +258,36 @@ namespace Avalonia.Media
|
|
|
};
|
|
|
}
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Transforms the specified bounding rectangle to account for the shadow's offset, spread, and blur.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="rect">The original bounding <see cref="Rect"/> to transform.</param>
|
|
|
+ /// <returns>
|
|
|
+ /// A new <see cref="Rect"/> that includes the shadow's offset, spread, and blur if the shadow is not inset;
|
|
|
+ /// otherwise, the original rectangle.
|
|
|
+ /// </returns>
|
|
|
public Rect TransformBounds(in Rect rect)
|
|
|
=> IsInset ? rect : rect.Translate(new Vector(OffsetX, OffsetY)).Inflate(Spread + Blur);
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Determines whether two <see cref="BoxShadow"/> values are equal.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="left">The first <see cref="BoxShadow"/> to compare.</param>
|
|
|
+ /// <param name="right">The second <see cref="BoxShadow"/> to compare.</param>
|
|
|
+ /// <returns>
|
|
|
+ /// <c>true</c> if the two <see cref="BoxShadow"/> values are equal; otherwise, <c>false</c>.
|
|
|
+ /// </returns>
|
|
|
public static bool operator ==(BoxShadow left, BoxShadow right) =>
|
|
|
left.Equals(right);
|
|
|
|
|
|
+ /// <summary>
|
|
|
+ /// Determines whether two <see cref="BoxShadow"/> values are not equal.
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="left">The first <see cref="BoxShadow"/> to compare.</param>
|
|
|
+ /// <param name="right">The second <see cref="BoxShadow"/> to compare.</param>
|
|
|
+ /// <returns>
|
|
|
+ /// <c>true</c> if the two <see cref="BoxShadow"/> values are not equal; otherwise, <c>false</c>.
|
|
|
+ /// </returns>
|
|
|
public static bool operator !=(BoxShadow left, BoxShadow right) =>
|
|
|
!(left == right);
|
|
|
}
|