diff --git a/packages/Avalonia/Avalonia.csproj b/packages/Avalonia/Avalonia.csproj index 412251bc9c..8ac2fb28ed 100644 --- a/packages/Avalonia/Avalonia.csproj +++ b/packages/Avalonia/Avalonia.csproj @@ -5,7 +5,7 @@ - + all diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml b/samples/ControlCatalog/Pages/ColorPickerPage.xaml index b759720cf2..25fffabfd2 100644 --- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml +++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml @@ -103,7 +103,8 @@ HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" />--> + HsvColor="{Binding HsvColor, ElementName=ColorSpectrum1}" + Margin="0,2,0,0" /> diff --git a/src/Avalonia.Base/Diagnostics/AppliedStyle.cs b/src/Avalonia.Base/Diagnostics/AppliedStyle.cs new file mode 100644 index 0000000000..1718f6b043 --- /dev/null +++ b/src/Avalonia.Base/Diagnostics/AppliedStyle.cs @@ -0,0 +1,18 @@ +using Avalonia.Styling; + +namespace Avalonia.Diagnostics +{ + public class AppliedStyle + { + private readonly IStyleInstance _instance; + + internal AppliedStyle(IStyleInstance instance) + { + _instance = instance; + } + + public bool HasActivator => _instance.HasActivator; + public bool IsActive => _instance.IsActive; + public StyleBase Style => (StyleBase)_instance.Source; + } +} diff --git a/src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs b/src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs index 984b145e68..90326891c6 100644 --- a/src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs +++ b/src/Avalonia.Base/Diagnostics/StyleDiagnostics.cs @@ -11,9 +11,9 @@ namespace Avalonia.Diagnostics /// /// Currently applied styles. /// - public IReadOnlyList AppliedStyles { get; } + public IReadOnlyList AppliedStyles { get; } - public StyleDiagnostics(IReadOnlyList appliedStyles) + public StyleDiagnostics(IReadOnlyList appliedStyles) { AppliedStyles = appliedStyles; } diff --git a/src/Avalonia.Base/Media/Color.cs b/src/Avalonia.Base/Media/Color.cs index 50c2faacc0..3ee151389a 100644 --- a/src/Avalonia.Base/Media/Color.cs +++ b/src/Avalonia.Base/Media/Color.cs @@ -478,7 +478,6 @@ namespace Avalonia.Media /// The HSL equivalent color. public HslColor ToHsl() { - // Don't use the HslColor(Color) constructor to avoid an extra HslColor return Color.ToHsl(R, G, B, A); } @@ -488,7 +487,6 @@ namespace Avalonia.Media /// The HSV equivalent color. public HsvColor ToHsv() { - // Don't use the HsvColor(Color) constructor to avoid an extra HsvColor return Color.ToHsv(R, G, B, A); } @@ -517,21 +515,6 @@ namespace Avalonia.Media } } - /// - /// Converts the given RGB color to its HSL color equivalent. - /// - /// The color in the RGB color model. - /// A new equivalent to the given RGBA values. - public static HslColor ToHsl(Color color) - { - // Normalize RGBA components into the 0..1 range - return Color.ToHsl( - (byteToDouble * color.R), - (byteToDouble * color.G), - (byteToDouble * color.B), - (byteToDouble * color.A)); - } - /// /// Converts the given RGBA color component values to their HSL color equivalent. /// @@ -606,21 +589,6 @@ namespace Avalonia.Media return new HslColor(a, 60 * h1, saturation, lightness, clampValues: false); } - /// - /// Converts the given RGB color to its HSV color equivalent. - /// - /// The color in the RGB color model. - /// A new equivalent to the given RGBA values. - public static HsvColor ToHsv(Color color) - { - // Normalize RGBA components into the 0..1 range - return Color.ToHsv( - (byteToDouble * color.R), - (byteToDouble * color.G), - (byteToDouble * color.B), - (byteToDouble * color.A)); - } - /// /// Converts the given RGBA color component values to their HSV color equivalent. /// diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index 612f468bfb..350d8817f1 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -155,6 +155,8 @@ namespace Avalonia.Media /// public Rect Bounds => new Rect(new Size(Metrics.WidthIncludingTrailingWhitespace, Metrics.Height)); + public Rect InkBounds => PlatformImpl.Item.Bounds; + /// /// /// @@ -728,7 +730,7 @@ namespace Avalonia.Media clusterLength++; i--; - if(characterIndex >= 0) + if (characterIndex >= 0) { codepoint = Codepoint.ReadAt(charactersSpan, characterIndex, out characterLength); @@ -827,8 +829,7 @@ namespace Avalonia.Media GlyphTypeface, FontRenderingEmSize, GlyphInfos, - BaselineOrigin, - Bounds); + BaselineOrigin); _platformImpl = RefCountable.Create(platformImpl); diff --git a/src/Avalonia.Base/Media/HslColor.cs b/src/Avalonia.Base/Media/HslColor.cs index 897c883875..84f2149367 100644 --- a/src/Avalonia.Base/Media/HslColor.cs +++ b/src/Avalonia.Base/Media/HslColor.cs @@ -90,7 +90,7 @@ namespace Avalonia.Media /// The RGB color to convert to HSL. public HslColor(Color color) { - var hsl = Color.ToHsl(color); + var hsl = color.ToHsl(); A = hsl.A; H = hsl.H; @@ -165,10 +165,18 @@ namespace Avalonia.Media /// The RGB equivalent color. public Color ToRgb() { - // Use the by-component conversion method directly for performance return HslColor.ToRgb(H, S, L, A); } + /// + /// Returns the HSV color model equivalent of this HSL color. + /// + /// The HSV equivalent color. + public HsvColor ToHsv() + { + return HslColor.ToHsv(H, S, L, A); + } + /// public override string ToString() { @@ -349,16 +357,6 @@ namespace Avalonia.Media return new HslColor(1.0, h, s, l); } - /// - /// Converts the given HSL color to its RGB color equivalent. - /// - /// The color in the HSL color model. - /// A new RGB equivalent to the given HSLA values. - public static Color ToRgb(HslColor hslColor) - { - return HslColor.ToRgb(hslColor.H, hslColor.S, hslColor.L, hslColor.A); - } - /// /// Converts the given HSLA color component values to their RGB color equivalent. /// @@ -442,13 +440,67 @@ namespace Avalonia.Media b1 = x; } - return Color.FromArgb( + return new Color( (byte)Math.Round(255 * alpha), (byte)Math.Round(255 * (r1 + m)), (byte)Math.Round(255 * (g1 + m)), (byte)Math.Round(255 * (b1 + m))); } + /// + /// Converts the given HSLA color component values to their HSV color equivalent. + /// + /// The Hue component in the HSL color model in the range from 0..360. + /// The Saturation component in the HSL color model in the range from 0..1. + /// The Lightness component in the HSL color model in the range from 0..1. + /// The Alpha component in the range from 0..1. + /// A new equivalent to the given HSLA values. + public static HsvColor ToHsv( + double hue, + double saturation, + double lightness, + double alpha = 1.0) + { + // We want the hue to be between 0 and 359, + // so we first ensure that that's the case. + while (hue >= 360.0) + { + hue -= 360.0; + } + + while (hue < 0.0) + { + hue += 360.0; + } + + // We similarly clamp saturation, lightness and alpha between 0 and 1. + saturation = saturation < 0.0 ? 0.0 : saturation; + saturation = saturation > 1.0 ? 1.0 : saturation; + + lightness = lightness < 0.0 ? 0.0 : lightness; + lightness = lightness > 1.0 ? 1.0 : lightness; + + alpha = alpha < 0.0 ? 0.0 : alpha; + alpha = alpha > 1.0 ? 1.0 : alpha; + + // The conversion algorithm is from the below link + // https://en.wikipedia.org/wiki/HSL_and_HSV#Interconversion + + double s; + double v = lightness + (saturation * Math.Min(lightness, 1.0 - lightness)); + + if (v <= 0) + { + s = 0; + } + else + { + s = 2.0 * (1.0 - (lightness / v)); + } + + return new HsvColor(alpha, hue, s, v); + } + /// /// Indicates whether the values of two specified objects are equal. /// diff --git a/src/Avalonia.Base/Media/HsvColor.cs b/src/Avalonia.Base/Media/HsvColor.cs index df68252065..03949d32aa 100644 --- a/src/Avalonia.Base/Media/HsvColor.cs +++ b/src/Avalonia.Base/Media/HsvColor.cs @@ -90,7 +90,7 @@ namespace Avalonia.Media /// The RGB color to convert to HSV. public HsvColor(Color color) { - var hsv = Color.ToHsv(color); + var hsv = color.ToHsv(); A = hsv.A; H = hsv.H; @@ -195,10 +195,18 @@ namespace Avalonia.Media /// The RGB equivalent color. public Color ToRgb() { - // Use the by-component conversion method directly for performance return HsvColor.ToRgb(H, S, V, A); } + /// + /// Returns the HSL color model equivalent of this HSV color. + /// + /// The HSL equivalent color. + public HslColor ToHsl() + { + return HsvColor.ToHsl(H, S, V, A); + } + /// public override string ToString() { @@ -379,16 +387,6 @@ namespace Avalonia.Media return new HsvColor(1.0, h, s, v); } - /// - /// Converts the given HSV color to its RGB color equivalent. - /// - /// The color in the HSV color model. - /// A new RGB equivalent to the given HSVA values. - public static Color ToRgb(HsvColor hsvColor) - { - return HsvColor.ToRgb(hsvColor.H, hsvColor.S, hsvColor.V, hsvColor.A); - } - /// /// Converts the given HSVA color component values to their RGB color equivalent. /// @@ -520,13 +518,67 @@ namespace Avalonia.Media break; } - return Color.FromArgb( + return new Color( (byte)Math.Round(alpha * 255), (byte)Math.Round(r * 255), (byte)Math.Round(g * 255), (byte)Math.Round(b * 255)); } + /// + /// Converts the given HSVA color component values to their HSL color equivalent. + /// + /// The Hue component in the HSV color model in the range from 0..360. + /// The Saturation component in the HSV color model in the range from 0..1. + /// The Value component in the HSV color model in the range from 0..1. + /// The Alpha component in the range from 0..1. + /// A new equivalent to the given HSVA values. + public static HslColor ToHsl( + double hue, + double saturation, + double value, + double alpha = 1.0) + { + // We want the hue to be between 0 and 359, + // so we first ensure that that's the case. + while (hue >= 360.0) + { + hue -= 360.0; + } + + while (hue < 0.0) + { + hue += 360.0; + } + + // We similarly clamp saturation, value and alpha between 0 and 1. + saturation = saturation < 0.0 ? 0.0 : saturation; + saturation = saturation > 1.0 ? 1.0 : saturation; + + value = value < 0.0 ? 0.0 : value; + value = value > 1.0 ? 1.0 : value; + + alpha = alpha < 0.0 ? 0.0 : alpha; + alpha = alpha > 1.0 ? 1.0 : alpha; + + // The conversion algorithm is from the below link + // https://en.wikipedia.org/wiki/HSL_and_HSV#Interconversion + + double s; + double l = value * (1.0 - (saturation / 2.0)); + + if (l <= 0 || l >= 1) + { + s = 0.0; + } + else + { + s = (value - l) / Math.Min(l, 1.0 - l); + } + + return new HslColor(alpha, hue, s, l); + } + /// /// Indicates whether the values of two specified objects are equal. /// diff --git a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs index 2f28c3f954..ef528ba73b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs +++ b/src/Avalonia.Base/Media/TextFormatting/ShapedTextRun.cs @@ -64,7 +64,7 @@ namespace Avalonia.Media.TextFormatting if (Properties.BackgroundBrush != null) { - drawingContext.DrawRectangle(Properties.BackgroundBrush, null, new Rect(Size)); + drawingContext.DrawRectangle(Properties.BackgroundBrush, null, GlyphRun.Bounds); } drawingContext.DrawGlyphRun(Properties.ForegroundBrush, GlyphRun); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index f373e0178a..0697831987 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -13,6 +13,7 @@ namespace Avalonia.Media.TextFormatting private readonly TextParagraphProperties _paragraphProperties; private readonly TextTrimming _textTrimming; private readonly TextLine[] _textLines; + private readonly CachedMetrics _metrics = new(); private int _textSourceLength; @@ -151,12 +152,95 @@ namespace Avalonia.Media.TextFormatting => _textLines; /// - /// Gets the bounds of the layout. + /// The distance from the top of the first line to the bottom of the last line. /// - /// - /// The bounds. - /// - public Rect Bounds { get; private set; } + public double Height + { + get + { + return _metrics.Height; + } + } + + /// + /// The distance from the topmost black pixel of the first line + /// to the bottommost black pixel of the last line. + /// + public double Extent + { + get + { + return _metrics.Extent; + } + } + + /// + /// The distance from the top of the first line to the baseline of the first line. + /// + public double Baseline + { + get + { + return _metrics.Baseline; + } + } + + /// + /// The distance from the bottom of the last line to the extent bottom. + /// + public double OverhangAfter + { + get + { + return _metrics.OverhangAfter; + } + } + + /// + /// The maximum distance from the leading black pixel to the leading alignment point of a line. + /// + public double OverhangLeading + { + get + { + return _metrics.OverhangLeading; + } + } + + /// + /// The maximum distance from the trailing black pixel to the trailing alignment point of a line. + /// + public double OverhangTrailing + { + get + { + return _metrics.OverhangTrailing; + } + } + + /// + /// The maximum advance width between the leading and trailing alignment points of a line, + /// excluding the width of whitespace characters at the end of the line. + /// + public double Width + { + get + { + return _metrics.Width; + } + } + + /// + /// The maximum advance width between the leading and trailing alignment points of a line, + /// including the width of whitespace characters at the end of the line. + /// + public double WidthIncludingTrailingWhitespace + { + get + { + return _metrics.WidthIncludingTrailingWhitespace; + } + } /// /// Draws the text layout. @@ -382,7 +466,7 @@ namespace Avalonia.Media.TextFormatting var textPosition = characterHit.FirstCharacterIndex + characterHit.TrailingLength; var isTrailing = lastTrailingIndex == textPosition && characterHit.TrailingLength > 0 || - y > Bounds.Bottom; + y > Height; if (textPosition == textLine.FirstTextSourceIndex + textLine.Length) { @@ -422,41 +506,25 @@ namespace Avalonia.Media.TextFormatting textRunStyle, textWrapping, lineHeight, 0, letterSpacing); } - /// - /// Updates the current bounds. - /// - /// The text line. - /// The current left. - /// The current width. - /// The current height. - private static void UpdateBounds(TextLine textLine, ref double left, ref double width, ref double height) + private TextLine[] CreateTextLines() { - var lineWidth = textLine.WidthIncludingTrailingWhitespace; - - if (width < lineWidth) - { - width = lineWidth; - } + var objectPool = FormattingObjectPool.Instance; - var start = textLine.Start; + var lineStartOfLongestLine = double.MaxValue; + var origin = new Point(); + var first = true; - if (left > start) - { - left = start; - } - - height += textLine.Height; - } + double accBlackBoxLeft, accBlackBoxTop, accBlackBoxRight, accBlackBoxBottom; - private TextLine[] CreateTextLines() - { - var objectPool = FormattingObjectPool.Instance; + accBlackBoxLeft = accBlackBoxTop = double.MaxValue; + accBlackBoxRight = accBlackBoxBottom = double.MinValue; if (MathUtilities.IsZero(MaxWidth) || MathUtilities.IsZero(MaxHeight)) { var textLine = TextFormatterImpl.CreateEmptyTextLine(0, double.PositiveInfinity, _paragraphProperties); - Bounds = new Rect(0, 0, 0, textLine.Height); + UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first, + ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom); return new TextLine[] { textLine }; } @@ -465,8 +533,6 @@ namespace Avalonia.Media.TextFormatting try { - double left = double.PositiveInfinity, width = 0.0, height = 0.0; - _textSourceLength = 0; TextLine? previousLine = null; @@ -487,7 +553,8 @@ namespace Avalonia.Media.TextFormatting textLines.Add(emptyTextLine); - UpdateBounds(emptyTextLine, ref left, ref width, ref height); + UpdateMetrics(emptyTextLine, ref lineStartOfLongestLine, ref origin, ref first, + ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom); } break; @@ -497,7 +564,7 @@ namespace Avalonia.Media.TextFormatting //Fulfill max height constraint if (textLines.Count > 0 && !double.IsPositiveInfinity(MaxHeight) - && height + textLine.Height > MaxHeight) + && Height + textLine.Height > MaxHeight) { if (previousLine?.TextLineBreak != null && _textTrimming != TextTrimming.None) { @@ -519,7 +586,8 @@ namespace Avalonia.Media.TextFormatting textLines.Add(textLine); - UpdateBounds(textLine, ref left, ref width, ref height); + UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first, + ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom); previousLine = textLine; @@ -528,7 +596,7 @@ namespace Avalonia.Media.TextFormatting { if (textLine.TextLineBreak is { IsSplit: true }) { - textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(width)); + textLines[textLines.Count - 1] = textLine.Collapse(GetCollapsingProperties(WidthIncludingTrailingWhitespace)); } break; @@ -546,18 +614,17 @@ namespace Avalonia.Media.TextFormatting textLines.Add(textLine); - UpdateBounds(textLine, ref left, ref width, ref height); + UpdateMetrics(textLine, ref lineStartOfLongestLine, ref origin, ref first, + ref accBlackBoxLeft, ref accBlackBoxTop, ref accBlackBoxRight, ref accBlackBoxBottom); } - Bounds = new Rect(left, 0, width, height); - if (_paragraphProperties.TextAlignment == TextAlignment.Justify) { var justificationWidth = MaxWidth; if (_paragraphProperties.TextWrapping != TextWrapping.NoWrap) { - justificationWidth = width; + justificationWidth = WidthIncludingTrailingWhitespace; } if (justificationWidth > 0) @@ -582,6 +649,46 @@ namespace Avalonia.Media.TextFormatting } } + private void UpdateMetrics( + TextLine currentLine, + ref double lineStartOfLongestLine, + ref Point origin, + ref bool first, + ref double accBlackBoxLeft, + ref double accBlackBoxTop, + ref double accBlackBoxRight, + ref double accBlackBoxBottom) + { + var blackBoxLeft = origin.X + currentLine.Start + currentLine.OverhangLeading; + var blackBoxRight = origin.X + currentLine.Start + currentLine.Width - currentLine.OverhangTrailing; + var blackBoxBottom = origin.Y + currentLine.Height + currentLine.OverhangAfter; + var blackBoxTop = blackBoxBottom - currentLine.Extent; + + accBlackBoxLeft = Math.Min(accBlackBoxLeft, blackBoxLeft); + accBlackBoxRight = Math.Max(accBlackBoxRight, blackBoxRight); + accBlackBoxBottom = Math.Max(accBlackBoxBottom, blackBoxBottom); + accBlackBoxTop = Math.Min(accBlackBoxTop, blackBoxTop); + + _metrics.OverhangAfter = currentLine.OverhangAfter; + + _metrics.Height += currentLine.Height; + _metrics.Width = Math.Max(_metrics.Width, currentLine.Width); + _metrics.WidthIncludingTrailingWhitespace = Math.Max(_metrics.WidthIncludingTrailingWhitespace, currentLine.WidthIncludingTrailingWhitespace); + lineStartOfLongestLine = Math.Min(lineStartOfLongestLine, currentLine.Start); + + _metrics.Extent = accBlackBoxBottom - accBlackBoxTop; + _metrics.OverhangLeading = accBlackBoxLeft - lineStartOfLongestLine; + _metrics.OverhangTrailing = _metrics.Width - (accBlackBoxRight - lineStartOfLongestLine); + + if (first) + { + _metrics.Baseline = currentLine.Baseline; + first = false; + } + + origin = origin.WithY(origin.Y + currentLine.Height); + } + /// /// Gets the for current text trimming mode. /// @@ -605,5 +712,24 @@ namespace Avalonia.Media.TextFormatting line.Dispose(); } } + + private class CachedMetrics + { + // vertical + public double Height; + public double Baseline; + + // horizontal + public double Width; + public double WidthIncludingTrailingWhitespace; + + // vertical bounding box metrics + public double Extent; + public double OverhangAfter; + + // horizontal bounding box metrics + public double OverhangLeading; + public double OverhangTrailing; + } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs index 1234067844..a0d7cabefd 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs @@ -51,7 +51,7 @@ namespace Avalonia.Media.TextFormatting public override double Baseline => _textLineMetrics.TextBaseline; /// - public override double Extent => _textLineMetrics.Height; + public override double Extent => _textLineMetrics.Extent; /// public override double Height => _textLineMetrics.Height; @@ -60,13 +60,13 @@ namespace Avalonia.Media.TextFormatting public override int NewLineLength => _textLineMetrics.NewlineLength; /// - public override double OverhangAfter => 0; + public override double OverhangAfter => _textLineMetrics.OverhangAfter; /// - public override double OverhangLeading => 0; + public override double OverhangLeading => _textLineMetrics.OverhangLeading; /// - public override double OverhangTrailing => 0; + public override double OverhangTrailing => _textLineMetrics.OverhangTrailing; /// public override int TrailingWhitespaceLength => _textLineMetrics.TrailingWhitespaceLength; @@ -87,13 +87,18 @@ namespace Avalonia.Media.TextFormatting foreach (var textRun in _textRuns) { - if (textRun is DrawableTextRun drawable) + switch (textRun) { - var offsetY = GetBaselineOffset(this, drawable); + case DrawableTextRun drawableTextRun: + { + var offsetY = GetBaselineOffset(this, drawableTextRun); + + drawableTextRun.Draw(drawingContext, new Point(currentX, currentY + offsetY)); - drawable.Draw(drawingContext, new Point(currentX, currentY + offsetY)); + currentX += drawableTextRun.Size.Width; - currentX += drawable.Size.Width; + break; + } } } } @@ -174,10 +179,12 @@ namespace Avalonia.Media.TextFormatting distance -= Start; var lastIndex = _textRuns.Length - 1; + var lineLength = Length; - if (_textRuns[lastIndex] is TextEndOfLine) + if (_textRuns[lastIndex] is TextEndOfLine textEndOfLine) { lastIndex--; + lineLength -= textEndOfLine.Length; } var currentPosition = FirstTextSourceIndex; @@ -205,7 +212,7 @@ namespace Avalonia.Media.TextFormatting if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight) { - currentPosition = Length - lastRun.Length; + currentPosition = lineLength - lastRun.Length; } return GetRunCharacterHit(lastRun, currentPosition, distance); @@ -703,7 +710,7 @@ namespace Avalonia.Media.TextFormatting //In case a run only contains a linebreak we don't want to skip it. if (currentRun is ShapedTextRun shaped) { - if(currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0) + if (currentRun.Length - shaped.GlyphRun.Metrics.NewLineLength > 0) { continue; } @@ -1431,9 +1438,10 @@ namespace Avalonia.Media.TextFormatting var lineGap = fontMetrics.LineGap * scale; var height = descent - ascent + lineGap; - var lineHeight = _paragraphProperties.LineHeight; + var bounds = new Rect(); + for (var index = 0; index < _textRuns.Length; index++) { switch (_textRuns[index]) @@ -1441,6 +1449,9 @@ namespace Avalonia.Media.TextFormatting case ShapedTextRun textRun: { var textMetrics = textRun.TextMetrics; + var glyphRun = textRun.GlyphRun; + + bounds = bounds.Union(glyphRun.InkBounds); if (fontRenderingEmSize < textMetrics.FontRenderingEmSize) { @@ -1486,18 +1497,22 @@ namespace Avalonia.Media.TextFormatting ascent = -drawableTextRun.Baseline; } + bounds = bounds.Union(new Rect(new Point(bounds.Right, 0), drawableTextRun.Size)); + break; } } } + var overhangAfter = Math.Max(0, bounds.Bottom - height); + var width = widthIncludingWhitespace; for (var i = _textRuns.Length - 1; i >= 0; i--) { var currentRun = _textRuns[i]; - if(currentRun is ShapedTextRun shapedText) + if (currentRun is ShapedTextRun shapedText) { var glyphRun = shapedText.GlyphRun; var glyphRunMetrics = glyphRun.Metrics; @@ -1518,6 +1533,9 @@ namespace Avalonia.Media.TextFormatting } var start = GetParagraphOffsetX(width, widthIncludingWhitespace); + var overhangLeading = Math.Max(0, bounds.Left - start); + var overhangTrailing = Math.Max(0, bounds.Width - widthIncludingWhitespace); + var hasOverflowed = overhangLeading + widthIncludingWhitespace + overhangTrailing > _paragraphWidth; if (!double.IsNaN(lineHeight) && !MathUtilities.IsZero(lineHeight)) { @@ -1527,8 +1545,21 @@ namespace Avalonia.Media.TextFormatting } } - return new TextLineMetrics(widthIncludingWhitespace > _paragraphWidth, height, newLineLength, start, - -ascent, trailingWhitespaceLength, width, widthIncludingWhitespace); + return new TextLineMetrics + { + HasOverflowed = hasOverflowed, + Height = height, + Extent = bounds.Height, + NewlineLength = newLineLength, + Start = start, + TextBaseline = -ascent, + TrailingWhitespaceLength = trailingWhitespaceLength, + Width = width, + WidthIncludingTrailingWhitespace = widthIncludingWhitespace, + OverhangLeading= overhangLeading, + OverhangTrailing= overhangTrailing, + OverhangAfter = overhangAfter + }; } /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineMetrics.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineMetrics.cs index cb21e27696..a1a6d309dd 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLineMetrics.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLineMetrics.cs @@ -5,59 +5,65 @@ /// that holds information about ascent, descent, line gap, size and origin of the text line. /// public readonly record struct TextLineMetrics - { - public TextLineMetrics(bool hasOverflowed, double height, int newlineLength, double start, double textBaseline, - int trailingWhitespaceLength, double width, - double widthIncludingTrailingWhitespace) - { - HasOverflowed = hasOverflowed; - Height = height; - NewlineLength = newlineLength; - Start = start; - TextBaseline = textBaseline; - TrailingWhitespaceLength = trailingWhitespaceLength; - Width = width; - WidthIncludingTrailingWhitespace = widthIncludingTrailingWhitespace; - } - + { /// /// Gets a value that indicates whether content of the line overflows the specified paragraph width. /// - public bool HasOverflowed { get; } + public bool HasOverflowed { get; init; } /// /// Gets the height of a line of text. /// - public double Height { get; } + public double Height { get; init; } /// /// Gets the number of newline characters at the end of a line. /// - public int NewlineLength { get; } + public int NewlineLength { get; init; } /// /// Gets the distance from the start of a paragraph to the starting point of a line. /// - public double Start { get; } + public double Start { get; init; } /// /// Gets the distance from the top to the baseline of the line of text. /// - public double TextBaseline { get; } + public double TextBaseline { get; init; } /// /// Gets the number of whitespace code points beyond the last non-blank character in a line. /// - public int TrailingWhitespaceLength { get; } + public int TrailingWhitespaceLength { get; init; } /// /// Gets the width of a line of text, excluding trailing whitespace characters. /// - public double Width { get; } + public double Width { get; init; } /// /// Gets the width of a line of text, including trailing whitespace characters. /// - public double WidthIncludingTrailingWhitespace { get; } + public double WidthIncludingTrailingWhitespace { get; init; } + + /// + /// Gets the distance from the top-most to bottom-most black pixel in a line. + /// + public double Extent { get; init; } + + /// + /// Gets the distance that black pixels extend beyond the bottom alignment edge of a line. + /// + public double OverhangAfter { get; init; } + + /// + /// Gets the distance that black pixels extend prior to the left leading alignment edge of the line. + /// + public double OverhangLeading { get; init; } + + /// + /// Gets the distance that black pixels extend following the right trailing alignment edge of the line. + /// + public double OverhangTrailing { get; init; } } } diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index b0d17f9c85..6f62c3be1d 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -169,9 +169,8 @@ namespace Avalonia.Platform /// The font rendering em size. /// The list of glyphs. /// The baseline origin of the run. Can be null. - /// the conservative bounding box of the run /// An . - IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos, Point baselineOrigin, Rect bounds); + IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphInfos, Point baselineOrigin); /// /// Creates a backend-specific object using a low-level API graphics context diff --git a/src/Avalonia.Base/StyledElement.cs b/src/Avalonia.Base/StyledElement.cs index 196fa850d6..1a0b3bcea6 100644 --- a/src/Avalonia.Base/StyledElement.cs +++ b/src/Avalonia.Base/StyledElement.cs @@ -420,12 +420,12 @@ namespace Avalonia internal StyleDiagnostics GetStyleDiagnosticsInternal() { - var styles = new List(); + var styles = new List(); foreach (var frame in GetValueStore().Frames) { if (frame is IStyleInstance style) - styles.Add(style); + styles.Add(new(style)); } return new StyleDiagnostics(styles); diff --git a/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs b/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs index 487198a861..7bc0777c61 100644 --- a/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs +++ b/src/Avalonia.Base/Styling/Activators/IStyleActivator.cs @@ -15,8 +15,7 @@ namespace Avalonia.Styling.Activators /// - The activation state can be re-evaluated at any time by calling /// - No error or completion messages /// - [Unstable] - public interface IStyleActivator : IDisposable + internal interface IStyleActivator : IDisposable { /// /// Gets a value indicating whether the style is subscribed. diff --git a/src/Avalonia.Base/Styling/Activators/IStyleActivatorSink.cs b/src/Avalonia.Base/Styling/Activators/IStyleActivatorSink.cs index 142a3c3517..6d49485c20 100644 --- a/src/Avalonia.Base/Styling/Activators/IStyleActivatorSink.cs +++ b/src/Avalonia.Base/Styling/Activators/IStyleActivatorSink.cs @@ -5,8 +5,7 @@ namespace Avalonia.Styling.Activators /// /// Receives notifications from an . /// - [Unstable] - public interface IStyleActivatorSink + internal interface IStyleActivatorSink { /// /// Called when the subscribed activator value changes. diff --git a/src/Avalonia.Base/Styling/ChildSelector.cs b/src/Avalonia.Base/Styling/ChildSelector.cs index 400ba18530..ac28d2bc46 100644 --- a/src/Avalonia.Base/Styling/ChildSelector.cs +++ b/src/Avalonia.Base/Styling/ChildSelector.cs @@ -19,13 +19,13 @@ namespace Avalonia.Styling } /// - public override bool InTemplate => _parent.InTemplate; + internal override bool InTemplate => _parent.InTemplate; /// - public override bool IsCombinator => true; + internal override bool IsCombinator => true; /// - public override Type? TargetType => null; + internal override Type? TargetType => null; public override string ToString(Style? owner) { @@ -37,7 +37,7 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) + private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { var controlParent = ((ILogical)control).LogicalParent; @@ -64,7 +64,7 @@ namespace Avalonia.Styling } } - protected override Selector? MovePrevious() => null; - protected override Selector? MovePreviousOrParent() => _parent; + private protected override Selector? MovePrevious() => null; + private protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/DescendentSelector.cs b/src/Avalonia.Base/Styling/DescendentSelector.cs index 20874a6877..6706eb4441 100644 --- a/src/Avalonia.Base/Styling/DescendentSelector.cs +++ b/src/Avalonia.Base/Styling/DescendentSelector.cs @@ -17,13 +17,13 @@ namespace Avalonia.Styling } /// - public override bool IsCombinator => true; + internal override bool IsCombinator => true; /// - public override bool InTemplate => _parent.InTemplate; + internal override bool InTemplate => _parent.InTemplate; /// - public override Type? TargetType => null; + internal override Type? TargetType => null; public override string ToString(Style? owner) { @@ -35,7 +35,7 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) + private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { var c = (ILogical)control; var descendantMatches = new OrActivatorBuilder(); @@ -69,7 +69,7 @@ namespace Avalonia.Styling } } - protected override Selector? MovePrevious() => null; - protected override Selector? MovePreviousOrParent() => _parent; + private protected override Selector? MovePrevious() => null; + private protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/ISetter.cs b/src/Avalonia.Base/Styling/ISetter.cs deleted file mode 100644 index 22af90b446..0000000000 --- a/src/Avalonia.Base/Styling/ISetter.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using Avalonia.Metadata; - -namespace Avalonia.Styling -{ - /// - /// Represents a setter for a . - /// - [NotClientImplementable] - public interface ISetter - { - /// - /// Instances a setter on a control. - /// - /// The style which contains the setter. - /// The control. - /// An . - /// - /// This method should return an which can be used to apply - /// the setter to the specified control. - /// - ISetterInstance Instance(IStyleInstance styleInstance, StyledElement target); - } -} diff --git a/src/Avalonia.Base/Styling/ISetterInstance.cs b/src/Avalonia.Base/Styling/ISetterInstance.cs index 4a65d6deeb..f8a7e7d346 100644 --- a/src/Avalonia.Base/Styling/ISetterInstance.cs +++ b/src/Avalonia.Base/Styling/ISetterInstance.cs @@ -3,7 +3,7 @@ namespace Avalonia.Styling { /// - /// Represents an that has been instanced on a control. + /// Represents a that has been instanced on a control. /// [Unstable] public interface ISetterInstance diff --git a/src/Avalonia.Base/Styling/ISetterValue.cs b/src/Avalonia.Base/Styling/ISetterValue.cs index 0fd245a429..800d8275b5 100644 --- a/src/Avalonia.Base/Styling/ISetterValue.cs +++ b/src/Avalonia.Base/Styling/ISetterValue.cs @@ -3,13 +3,13 @@ namespace Avalonia.Styling { /// - /// Customizes the behavior of a class when added as a value to an . + /// Customizes the behavior of a class when added as a value to a . /// public interface ISetterValue { /// /// Notifies that the object has been added as a setter value. /// - void Initialize(ISetter setter); + void Initialize(SetterBase setter); } } diff --git a/src/Avalonia.Base/Styling/IStyleInstance.cs b/src/Avalonia.Base/Styling/IStyleInstance.cs index 749a2c84d5..72cc3d6901 100644 --- a/src/Avalonia.Base/Styling/IStyleInstance.cs +++ b/src/Avalonia.Base/Styling/IStyleInstance.cs @@ -5,8 +5,7 @@ namespace Avalonia.Styling /// /// Represents a that has been instanced on a control. /// - [Unstable] - public interface IStyleInstance + internal interface IStyleInstance { /// /// Gets the source style. diff --git a/src/Avalonia.Base/Styling/NestingSelector.cs b/src/Avalonia.Base/Styling/NestingSelector.cs index deb688ca4d..4a6b0cfda9 100644 --- a/src/Avalonia.Base/Styling/NestingSelector.cs +++ b/src/Avalonia.Base/Styling/NestingSelector.cs @@ -7,13 +7,13 @@ namespace Avalonia.Styling /// internal class NestingSelector : Selector { - public override bool InTemplate => false; - public override bool IsCombinator => false; - public override Type? TargetType => null; + internal override bool InTemplate => false; + internal override bool IsCombinator => false; + internal override Type? TargetType => null; public override string ToString(Style? owner) => owner?.Parent?.ToString() ?? "^"; - protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) + private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { if (parent is Style s && s.Selector is not null) { @@ -32,7 +32,7 @@ namespace Avalonia.Styling "Nesting selector was specified but cannot determine parent selector."); } - protected override Selector? MovePrevious() => null; - protected override Selector? MovePreviousOrParent() => null; + private protected override Selector? MovePrevious() => null; + private protected override Selector? MovePreviousOrParent() => null; } } diff --git a/src/Avalonia.Base/Styling/NotSelector.cs b/src/Avalonia.Base/Styling/NotSelector.cs index f6b1288ac5..9a541cbba7 100644 --- a/src/Avalonia.Base/Styling/NotSelector.cs +++ b/src/Avalonia.Base/Styling/NotSelector.cs @@ -26,13 +26,13 @@ namespace Avalonia.Styling } /// - public override bool InTemplate => _argument.InTemplate; + internal override bool InTemplate => _argument.InTemplate; /// - public override bool IsCombinator => false; + internal override bool IsCombinator => false; /// - public override Type? TargetType => _previous?.TargetType; + internal override Type? TargetType => _previous?.TargetType; /// public override string ToString(Style? owner) @@ -45,7 +45,7 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) + private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { var innerResult = _argument.Match(control, parent, subscribe); @@ -66,7 +66,7 @@ namespace Avalonia.Styling } } - protected override Selector? MovePrevious() => _previous; - protected override Selector? MovePreviousOrParent() => _previous; + private protected override Selector? MovePrevious() => _previous; + private protected override Selector? MovePreviousOrParent() => _previous; } } diff --git a/src/Avalonia.Base/Styling/NthChildSelector.cs b/src/Avalonia.Base/Styling/NthChildSelector.cs index 532179bb2c..bf6247aba1 100644 --- a/src/Avalonia.Base/Styling/NthChildSelector.cs +++ b/src/Avalonia.Base/Styling/NthChildSelector.cs @@ -12,7 +12,7 @@ namespace Avalonia.Styling /// /// Element indices are 1-based. /// - public class NthChildSelector : Selector + internal class NthChildSelector : Selector { private const string NthChildSelectorName = "nth-child"; private const string NthLastChildSelectorName = "nth-last-child"; @@ -39,16 +39,16 @@ namespace Avalonia.Styling } - public override bool InTemplate => _previous?.InTemplate ?? false; + internal override bool InTemplate => _previous?.InTemplate ?? false; - public override bool IsCombinator => false; + internal override bool IsCombinator => false; - public override Type? TargetType => _previous?.TargetType; + internal override Type? TargetType => _previous?.TargetType; public int Step { get; } public int Offset { get; } - protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) + private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { if (!(control is ILogical logical)) { @@ -103,8 +103,8 @@ namespace Avalonia.Styling return match ? SelectorMatch.AlwaysThisInstance : SelectorMatch.NeverThisInstance; } - protected override Selector? MovePrevious() => _previous; - protected override Selector? MovePreviousOrParent() => _previous; + private protected override Selector? MovePrevious() => _previous; + private protected override Selector? MovePreviousOrParent() => _previous; public override string ToString(Style? owner) { diff --git a/src/Avalonia.Base/Styling/NthLastChildSelector.cs b/src/Avalonia.Base/Styling/NthLastChildSelector.cs index 6f6abbae6a..aa62ad2b2c 100644 --- a/src/Avalonia.Base/Styling/NthLastChildSelector.cs +++ b/src/Avalonia.Base/Styling/NthLastChildSelector.cs @@ -8,7 +8,7 @@ namespace Avalonia.Styling /// /// Element indices are 1-based. /// - public class NthLastChildSelector : NthChildSelector + internal class NthLastChildSelector : NthChildSelector { /// /// Creates an instance of diff --git a/src/Avalonia.Base/Styling/OrSelector.cs b/src/Avalonia.Base/Styling/OrSelector.cs index 53e4baa2c4..cc77aa9fcf 100644 --- a/src/Avalonia.Base/Styling/OrSelector.cs +++ b/src/Avalonia.Base/Styling/OrSelector.cs @@ -36,13 +36,13 @@ namespace Avalonia.Styling } /// - public override bool InTemplate => false; + internal override bool InTemplate => false; /// - public override bool IsCombinator => false; + internal override bool IsCombinator => false; /// - public override Type? TargetType => _targetType ??= EvaluateTargetType(); + internal override Type? TargetType => _targetType ??= EvaluateTargetType(); /// public override string ToString(Style? owner) @@ -55,7 +55,7 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) + private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { var activators = new OrActivatorBuilder(); var neverThisInstance = false; @@ -94,8 +94,8 @@ namespace Avalonia.Styling } } - protected override Selector? MovePrevious() => null; - protected override Selector? MovePreviousOrParent() => null; + private protected override Selector? MovePrevious() => null; + private protected override Selector? MovePreviousOrParent() => null; internal override void ValidateNestingSelector(bool inControlTheme) { diff --git a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs index 0865a7a8b0..3a50923094 100644 --- a/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs +++ b/src/Avalonia.Base/Styling/PropertyEqualsSelector.cs @@ -28,13 +28,13 @@ namespace Avalonia.Styling } /// - public override bool InTemplate => _previous?.InTemplate ?? false; + internal override bool InTemplate => _previous?.InTemplate ?? false; /// - public override bool IsCombinator => false; + internal override bool IsCombinator => false; /// - public override Type? TargetType => _previous?.TargetType; + internal override Type? TargetType => _previous?.TargetType; /// public override string ToString(Style? owner) @@ -73,7 +73,7 @@ namespace Avalonia.Styling } /// - protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) + private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { if (subscribe) { @@ -88,8 +88,8 @@ namespace Avalonia.Styling } - protected override Selector? MovePrevious() => _previous; - protected override Selector? MovePreviousOrParent() => _previous; + private protected override Selector? MovePrevious() => _previous; + private protected override Selector? MovePreviousOrParent() => _previous; [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)] [UnconditionalSuppressMessage("Trimming", "IL2067", Justification = TrimmingMessages.TypeConvertionSupressWarningMessage)] diff --git a/src/Avalonia.Base/Styling/Selector.cs b/src/Avalonia.Base/Styling/Selector.cs index c83950f72d..4e1c338553 100644 --- a/src/Avalonia.Base/Styling/Selector.cs +++ b/src/Avalonia.Base/Styling/Selector.cs @@ -14,7 +14,7 @@ namespace Avalonia.Styling /// Gets a value indicating whether either this selector or a previous selector has moved /// into a template. /// - public abstract bool InTemplate { get; } + internal abstract bool InTemplate { get; } /// /// Gets a value indicating whether this selector is a combinator. @@ -22,12 +22,12 @@ namespace Avalonia.Styling /// /// A combinator is a selector such as Child or Descendent which links simple selectors. /// - public abstract bool IsCombinator { get; } + internal abstract bool IsCombinator { get; } /// /// Gets the target type of the selector, if available. /// - public abstract Type? TargetType { get; } + internal abstract Type? TargetType { get; } /// /// Tries to match the selector with a control. @@ -41,7 +41,7 @@ namespace Avalonia.Styling /// or simply return an immediate result. /// /// A . - public SelectorMatch Match(StyledElement control, IStyle? parent = null, bool subscribe = true) + internal SelectorMatch Match(StyledElement control, IStyle? parent = null, bool subscribe = true) { // First match the selector until a combinator is found. Selectors are stored from // right-to-left, so MatchUntilCombinator reverses this order because the type selector @@ -88,17 +88,17 @@ namespace Avalonia.Styling /// or simply return an immediate result. /// /// A . - protected abstract SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe); + private protected abstract SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe); /// /// Moves to the previous selector. /// - protected abstract Selector? MovePrevious(); + private protected abstract Selector? MovePrevious(); /// /// Moves to the previous selector or the parent selector. /// - protected abstract Selector? MovePreviousOrParent(); + private protected abstract Selector? MovePreviousOrParent(); internal virtual void ValidateNestingSelector(bool inControlTheme) { diff --git a/src/Avalonia.Base/Styling/SelectorMatch.cs b/src/Avalonia.Base/Styling/SelectorMatch.cs index 2eac04301a..cbcab612d6 100644 --- a/src/Avalonia.Base/Styling/SelectorMatch.cs +++ b/src/Avalonia.Base/Styling/SelectorMatch.cs @@ -8,7 +8,7 @@ namespace Avalonia.Styling /// /// Describes how a matches a control and its type. /// - public enum SelectorMatchResult + internal enum SelectorMatchResult { /// /// The selector never matches this type. @@ -43,7 +43,7 @@ namespace Avalonia.Styling /// A selector match describes whether and how a matches a control, and /// in addition whether the selector can ever match a control of the same type. /// - public readonly record struct SelectorMatch + internal readonly record struct SelectorMatch { /// /// A selector match with the result of . diff --git a/src/Avalonia.Base/Styling/Setter.cs b/src/Avalonia.Base/Styling/Setter.cs index 9b009be6d2..e5b2bed738 100644 --- a/src/Avalonia.Base/Styling/Setter.cs +++ b/src/Avalonia.Base/Styling/Setter.cs @@ -14,7 +14,7 @@ namespace Avalonia.Styling /// A is used to set a value on a /// depending on a condition. /// - public class Setter : ISetter, IValueEntry, ISetterInstance, IAnimationSetter + public class Setter : SetterBase, IValueEntry, ISetterInstance, IAnimationSetter { private object? _value; private DirectPropertySetterInstance? _direct; @@ -66,7 +66,7 @@ namespace Avalonia.Styling void IValueEntry.Unsubscribe() { } [UnconditionalSuppressMessage("Trimming", "IL2026", Justification = TrimmingMessages.ImplicitTypeConvertionSupressWarningMessage)] - ISetterInstance ISetter.Instance(IStyleInstance instance, StyledElement target) + internal override ISetterInstance Instance(IStyleInstance instance, StyledElement target) { if (target is not AvaloniaObject ao) throw new InvalidOperationException("Don't know how to instance a style on this type."); diff --git a/src/Avalonia.Base/Styling/SetterBase.cs b/src/Avalonia.Base/Styling/SetterBase.cs new file mode 100644 index 0000000000..24cd525130 --- /dev/null +++ b/src/Avalonia.Base/Styling/SetterBase.cs @@ -0,0 +1,12 @@ +namespace Avalonia.Styling +{ + /// + /// Represents the base class for value setters. + /// + public abstract class SetterBase + { + internal abstract ISetterInstance Instance( + IStyleInstance styleInstance, + StyledElement target); + } +} diff --git a/src/Avalonia.Base/Styling/StyleBase.cs b/src/Avalonia.Base/Styling/StyleBase.cs index 7dfa516bce..318e8d6890 100644 --- a/src/Avalonia.Base/Styling/StyleBase.cs +++ b/src/Avalonia.Base/Styling/StyleBase.cs @@ -16,7 +16,7 @@ namespace Avalonia.Styling private IResourceHost? _owner; private StyleChildren? _children; private IResourceDictionary? _resources; - private List? _setters; + private List? _setters; private List? _animations; private StyleInstance? _sharedInstance; @@ -60,7 +60,7 @@ namespace Avalonia.Styling } } - public IList Setters => _setters ??= new List(); + public IList Setters => _setters ??= new(); public IList Animations => _animations ??= new List(); bool IResourceNode.HasResources => _resources?.Count > 0; @@ -69,7 +69,7 @@ namespace Avalonia.Styling internal bool HasChildren => _children?.Count > 0; internal bool HasSettersOrAnimations => _setters?.Count > 0 || _animations?.Count > 0; - public void Add(ISetter setter) => Setters.Add(setter); + public void Add(SetterBase setter) => Setters.Add(setter); public void Add(IStyle style) => Children.Add(style); public event EventHandler? OwnerChanged; diff --git a/src/Avalonia.Base/Styling/TemplateSelector.cs b/src/Avalonia.Base/Styling/TemplateSelector.cs index b253efc6d2..1fa2ca2d0f 100644 --- a/src/Avalonia.Base/Styling/TemplateSelector.cs +++ b/src/Avalonia.Base/Styling/TemplateSelector.cs @@ -18,13 +18,13 @@ namespace Avalonia.Styling } /// - public override bool InTemplate => true; + internal override bool InTemplate => true; /// - public override bool IsCombinator => true; + internal override bool IsCombinator => true; /// - public override Type? TargetType => null; + internal override Type? TargetType => null; public override string ToString(Style? owner) { @@ -36,7 +36,7 @@ namespace Avalonia.Styling return _selectorString; } - protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) + private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { var templatedParent = control.TemplatedParent as StyledElement; @@ -48,7 +48,7 @@ namespace Avalonia.Styling return _parent.Match(templatedParent, parent, subscribe); } - protected override Selector? MovePrevious() => null; - protected override Selector? MovePreviousOrParent() => _parent; + private protected override Selector? MovePrevious() => null; + private protected override Selector? MovePreviousOrParent() => _parent; } } diff --git a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs index 2bd05242f5..81b204761b 100644 --- a/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs +++ b/src/Avalonia.Base/Styling/TypeNameAndClassSelector.cs @@ -58,7 +58,7 @@ namespace Avalonia.Styling } /// - public override bool InTemplate => _previous?.InTemplate ?? false; + internal override bool InTemplate => _previous?.InTemplate ?? false; /// /// Gets the name of the control to match. @@ -66,10 +66,10 @@ namespace Avalonia.Styling public string? Name { get; set; } /// - public override Type? TargetType => _targetType ?? _previous?.TargetType; + internal override Type? TargetType => _targetType ?? _previous?.TargetType; /// - public override bool IsCombinator => false; + internal override bool IsCombinator => false; /// /// Whether the selector matches the concrete or any object which @@ -89,7 +89,7 @@ namespace Avalonia.Styling } /// - protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) + private protected override SelectorMatch Evaluate(StyledElement control, IStyle? parent, bool subscribe) { if (TargetType != null) { @@ -134,8 +134,8 @@ namespace Avalonia.Styling return Name == null ? SelectorMatch.AlwaysThisType : SelectorMatch.AlwaysThisInstance; } - protected override Selector? MovePrevious() => _previous; - protected override Selector? MovePreviousOrParent() => _previous; + private protected override Selector? MovePrevious() => _previous; + private protected override Selector? MovePreviousOrParent() => _previous; private string BuildSelectorString(Style? owner) { diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs index e2a34a7f90..a065a7f826 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.Properties.cs @@ -41,11 +41,19 @@ namespace Avalonia.Controls.Primitives defaultBindingMode: BindingMode.TwoWay); /// - /// Defines the property. + /// Defines the property. /// - public static readonly StyledProperty IsAlphaMaxForcedProperty = + public static readonly StyledProperty IsAlphaVisibleProperty = AvaloniaProperty.Register( - nameof(IsAlphaMaxForced), + nameof(IsAlphaVisible), + false); + + /// + /// Defines the property. + /// + public static readonly StyledProperty IsPerceptiveProperty = + AvaloniaProperty.Register( + nameof(IsPerceptive), true); /// @@ -56,14 +64,6 @@ namespace Avalonia.Controls.Primitives nameof(IsRoundingEnabled), false); - /// - /// Defines the property. - /// - public static readonly StyledProperty IsSaturationValueMaxForcedProperty = - AvaloniaProperty.Register( - nameof(IsSaturationValueMaxForced), - true); - /// /// Gets or sets the currently selected color in the RGB color model. /// @@ -109,14 +109,41 @@ namespace Avalonia.Controls.Primitives } /// - /// Gets or sets a value indicating whether the alpha component is always forced to maximum for components - /// other than . - /// This ensures that the background is always visible and never transparent regardless of the actual color. + /// Gets or sets a value indicating whether the alpha component is visible and rendered. + /// When false, this ensures that the gradient is always visible and never transparent regardless of + /// the actual color. This property is ignored when the alpha component itself is being displayed. + /// + /// + /// Setting to false means the alpha component is always forced to maximum for components other than + /// during rendering. This doesn't change the value of the alpha component + /// in the color – it is only for display. + /// + public bool IsAlphaVisible + { + get => GetValue(IsAlphaVisibleProperty); + set => SetValue(IsAlphaVisibleProperty, value); + } + + /// + /// Gets or sets a value indicating whether the slider adapts rendering to improve user-perception + /// over exactness. /// - public bool IsAlphaMaxForced + /// + /// When true in the HSVA color model, this ensures that the gradient is always visible and + /// never washed out regardless of the actual color. When true in the RGBA color model, this ensures + /// the gradient always appears as red, green or blue. + ///

+ /// For example, with Hue in the HSVA color model, the Saturation and Value components are always forced + /// to maximum values during rendering. In the RGBA color model, all components other than + /// are forced to minimum values during rendering. + ///

+ /// Note this property will only adjust components other than during rendering. + /// This also doesn't change the values of any components in the actual color – it is only for display. + ///
+ public bool IsPerceptive { - get => GetValue(IsAlphaMaxForcedProperty); - set => SetValue(IsAlphaMaxForcedProperty, value); + get => GetValue(IsPerceptiveProperty); + set => SetValue(IsPerceptiveProperty, value); } /// @@ -131,16 +158,5 @@ namespace Avalonia.Controls.Primitives get => GetValue(IsRoundingEnabledProperty); set => SetValue(IsRoundingEnabledProperty, value); } - - /// - /// Gets or sets a value indicating whether the saturation and value components are always forced to maximum values - /// when using the HSVA color model. Only component values other than will be changed. - /// This ensures, for example, that the Hue background is always visible and never washed out regardless of the actual color. - /// - public bool IsSaturationValueMaxForced - { - get => GetValue(IsSaturationValueMaxForcedProperty); - set => SetValue(IsSaturationValueMaxForcedProperty, value); - } } } diff --git a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs index ce47a797ec..a6d9ca459f 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSlider/ColorSlider.cs @@ -52,8 +52,7 @@ namespace Avalonia.Controls.Primitives // This means under a certain alpha threshold, neither a white or black selector thumb // should be shown and instead the default slider thumb color should be used instead. if (Color.A < 128 && - (IsAlphaMaxForced == false || - ColorComponent == ColorComponent.Alpha)) + (IsAlphaVisible || ColorComponent == ColorComponent.Alpha)) { PseudoClasses.Set(pcDarkSelector, false); PseudoClasses.Set(pcLightSelector, false); @@ -64,11 +63,11 @@ namespace Avalonia.Controls.Primitives if (ColorModel == ColorModel.Hsva) { - perceivedColor = GetEquivalentBackgroundColor(HsvColor).ToRgb(); + perceivedColor = GetPerceptiveBackgroundColor(HsvColor).ToRgb(); } else { - perceivedColor = GetEquivalentBackgroundColor(Color); + perceivedColor = GetPerceptiveBackgroundColor(Color); } if (ColorHelper.GetRelativeLuminance(perceivedColor) <= 0.5) @@ -108,7 +107,7 @@ namespace Avalonia.Controls.Primitives { // As a fallback, attempt to calculate using the overall control size // This shouldn't happen as a track is a required template part of a slider - // However, if it does, the spectrum will still be shown + // However, if it does, the spectrum gradient will still be shown pixelWidth = Convert.ToInt32(Bounds.Width * scale); pixelHeight = Convert.ToInt32(Bounds.Height * scale); } @@ -122,8 +121,8 @@ namespace Avalonia.Controls.Primitives ColorModel, ColorComponent, HsvColor, - IsAlphaMaxForced, - IsSaturationValueMaxForced); + IsAlphaVisible, + IsPerceptive); if (_backgroundBitmap != null) { @@ -316,40 +315,35 @@ namespace Avalonia.Controls.Primitives /// /// The actual color to get the equivalent background color for. /// The equivalent, perceived background color. - private HsvColor GetEquivalentBackgroundColor(HsvColor hsvColor) + private HsvColor GetPerceptiveBackgroundColor(HsvColor hsvColor) { var component = ColorComponent; - var isAlphaMaxForced = IsAlphaMaxForced; - var isSaturationValueMaxForced = IsSaturationValueMaxForced; + var isAlphaVisible = IsAlphaVisible; + var isPerceptive = IsPerceptive; - if (isAlphaMaxForced && + if (isAlphaVisible == false && component != ColorComponent.Alpha) { hsvColor = new HsvColor(1.0, hsvColor.H, hsvColor.S, hsvColor.V); } - switch (component) + if (isPerceptive) { - case ColorComponent.Component1: - return new HsvColor( - hsvColor.A, - hsvColor.H, - isSaturationValueMaxForced ? 1.0 : hsvColor.S, - isSaturationValueMaxForced ? 1.0 : hsvColor.V); - case ColorComponent.Component2: - return new HsvColor( - hsvColor.A, - hsvColor.H, - hsvColor.S, - isSaturationValueMaxForced ? 1.0 : hsvColor.V); - case ColorComponent.Component3: - return new HsvColor( - hsvColor.A, - hsvColor.H, - isSaturationValueMaxForced ? 1.0 : hsvColor.S, - hsvColor.V); - default: - return hsvColor; + switch (component) + { + case ColorComponent.Component1: + return new HsvColor(hsvColor.A, hsvColor.H, 1.0, 1.0); + case ColorComponent.Component2: + return new HsvColor(hsvColor.A, hsvColor.H, hsvColor.S, 1.0); + case ColorComponent.Component3: + return new HsvColor(hsvColor.A, hsvColor.H, 1.0, hsvColor.V); + default: + return hsvColor; + } + } + else + { + return hsvColor; } } @@ -359,18 +353,36 @@ namespace Avalonia.Controls.Primitives ///
/// The actual color to get the equivalent background color for. /// The equivalent, perceived background color. - private Color GetEquivalentBackgroundColor(Color rgbColor) + private Color GetPerceptiveBackgroundColor(Color rgbColor) { var component = ColorComponent; - var isAlphaMaxForced = IsAlphaMaxForced; + var isAlphaVisible = IsAlphaVisible; + var isPerceptive = IsPerceptive; - if (isAlphaMaxForced && + if (isAlphaVisible == false && component != ColorComponent.Alpha) { rgbColor = new Color(255, rgbColor.R, rgbColor.G, rgbColor.B); } - return rgbColor; + if (isPerceptive) + { + switch (component) + { + case ColorComponent.Component1: + return new Color(rgbColor.A, rgbColor.R, 0, 0); + case ColorComponent.Component2: + return new Color(rgbColor.A, 0, rgbColor.G, 0); + case ColorComponent.Component3: + return new Color(rgbColor.A, 0, 0, rgbColor.B); + default: + return rgbColor; + } + } + else + { + return rgbColor; + } } /// @@ -401,8 +413,8 @@ namespace Avalonia.Controls.Primitives } else if (change.Property == ColorComponentProperty || change.Property == ColorModelProperty || - change.Property == IsAlphaMaxForcedProperty || - change.Property == IsSaturationValueMaxForcedProperty) + change.Property == IsAlphaVisibleProperty || + change.Property == IsPerceptiveProperty) { ignorePropertyChanged = true; diff --git a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs index 9198a2f237..6683346eeb 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorSpectrum/ColorSpectrum.cs @@ -45,6 +45,7 @@ namespace Avalonia.Controls.Primitives private bool _updatingColor = false; private bool _updatingHsvColor = false; + private bool _coercedInitialColor = false; private bool _isPointerPressed = false; private bool _shouldShowLargeSelection = false; private List _hsvValues = new List(); @@ -601,14 +602,102 @@ namespace Avalonia.Controls.Primitives } } + /// + /// Changes the currently selected color (always in HSV) and applies all necessary updates. + /// + /// + /// Some additional logic is applied in certain situations to coerce and sync color values. + /// Use this method instead of update the or directly. + /// + /// The new HSV color to change to. private void UpdateColor(Hsv newHsv) { _updatingColor = true; _updatingHsvColor = true; - Rgb newRgb = newHsv.ToRgb(); double alpha = HsvColor.A; + // It is common for the ColorPicker (and therefore the Spectrum) to be initialized + // with a #00000000 color value in some use cases. This is usually used to indicate + // that no color has been selected by the user. Note that #00000000 is different than + // #00FFFFFF (Transparent). + // + // In this situation, the first time the user clicks on the spectrum the third + // component and alpha component will remain zero. This is because the spectrum only + // controls two components at any given time. + // + // This is very unintuitive from a user-standpoint as after the user clicks on the + // spectrum they must then increase the alpha and then the third component sliders + // to the desired value. In fact, until they increase these slider values no color + // will show at all since it is fully transparent and black. In almost all cases + // though the desired value is simply full color. + // + // To work around this usability issue with an initial #00000000 color, the selected + // color is coerced (only the first time) into a color with maximum third component + // value and maximum alpha. This can only happen once and only if those two components + // are already zero. + // + // Also note this is NOT currently done for #00FFFFFF (Transparent) but based on + // further usability study that case may need to be handled here as well. Right now + // Transparent is treated as a normal color value with the alpha intentionally set + // to zero so the alpha slider must still be adjusted after the spectrum. + if (!_coercedInitialColor && + IsLoaded) + { + bool isAlphaComponentZero = (alpha == 0.0); + bool isThirdComponentZero = false; + + switch (Components) + { + case ColorSpectrumComponents.HueValue: + case ColorSpectrumComponents.ValueHue: + isThirdComponentZero = (newHsv.S == 0.0); + break; + + case ColorSpectrumComponents.HueSaturation: + case ColorSpectrumComponents.SaturationHue: + isThirdComponentZero = (newHsv.V == 0.0); + break; + + case ColorSpectrumComponents.ValueSaturation: + case ColorSpectrumComponents.SaturationValue: + isThirdComponentZero = (newHsv.H == 0.0); + break; + } + + if (isAlphaComponentZero && isThirdComponentZero) + { + alpha = 1.0; + + switch (Components) + { + case ColorSpectrumComponents.HueValue: + case ColorSpectrumComponents.ValueHue: + newHsv.S = 1.0; + break; + + case ColorSpectrumComponents.HueSaturation: + case ColorSpectrumComponents.SaturationHue: + newHsv.V = 1.0; + break; + + case ColorSpectrumComponents.ValueSaturation: + case ColorSpectrumComponents.SaturationValue: + // Hue is mathematically NOT a special case; however, is one conceptually. + // It doesn't make sense to change the selected Hue value, so why is it set here? + // Setting to 360.0 is equivalent to the max set for other components and is + // internally wrapped back to 0.0 (since 360 degrees = 0 degrees). + // This means effectively there is no change to the hue component value. + newHsv.H = 360.0; + break; + } + + _coercedInitialColor = true; + } + } + + Rgb newRgb = newHsv.ToRgb(); + SetCurrentValue(ColorProperty, newRgb.ToColor(alpha)); SetCurrentValue(HsvColorProperty, newHsv.ToHsvColor(alpha)); diff --git a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs index 532e87a9fc..701dab97f7 100644 --- a/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs +++ b/src/Avalonia.Controls.ColorPicker/ColorView/ColorView.Properties.cs @@ -504,6 +504,12 @@ namespace Avalonia.Controls /// /// Gets or sets the index of the selected tab/panel/page (subview). /// + /// + /// When using the default control theme, this property is designed to be used with the + /// enum. The enum defines the + /// index values of each of the three standard tabs. + /// Use like `SelectedIndex = (int)ColorViewTab.Palette`. + /// public int SelectedIndex { get => GetValue(SelectedIndexProperty); diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs index dbd92d4ac5..c2332751af 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/ColorPickerHelpers.cs @@ -29,11 +29,10 @@ namespace Avalonia.Controls.Primitives /// The color model being used: RGBA or HSVA. /// The specific color component to sweep. /// The base HSV color used for components not being changed. - /// Fix the alpha component value to maximum during calculation. - /// This will remove any alpha/transparency from the other component backgrounds. - /// Fix the saturation and value components to maximum - /// during calculation with the HSVA color model. - /// This will ensure colors are always discernible regardless of saturation/value. + /// Whether the alpha component is visible and rendered in the bitmap. + /// This property is ignored when the alpha component itself is being rendered. + /// Whether the slider adapts rendering to improve user-perception over exactness. + /// This will ensure colors are always discernible. /// A new bitmap representing a gradient of color component values. public static async Task> CreateComponentBitmapAsync( int width, @@ -42,8 +41,8 @@ namespace Avalonia.Controls.Primitives ColorModel colorModel, ColorComponent component, HsvColor baseHsvColor, - bool isAlphaMaxForced, - bool isSaturationValueMaxForced) + bool isAlphaVisible, + bool isPerceptive) { if (width == 0 || height == 0) { @@ -67,7 +66,7 @@ namespace Avalonia.Controls.Primitives bgraPixelDataWidth = width * 4; // Maximize alpha component value - if (isAlphaMaxForced && + if (isAlphaVisible == false && component != ColorComponent.Alpha) { baseHsvColor = new HsvColor(1.0, baseHsvColor.H, baseHsvColor.S, baseHsvColor.V); @@ -79,22 +78,41 @@ namespace Avalonia.Controls.Primitives baseRgbColor = baseHsvColor.ToRgb(); } - // Maximize Saturation and Value components when in HSVA mode - if (isSaturationValueMaxForced && - colorModel == ColorModel.Hsva && + // Apply any perceptive adjustments to the color + if (isPerceptive && component != ColorComponent.Alpha) { - switch (component) + if (colorModel == ColorModel.Hsva) { - case ColorComponent.Component1: - baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, 1.0); - break; - case ColorComponent.Component2: - baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, baseHsvColor.S, 1.0); - break; - case ColorComponent.Component3: - baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, baseHsvColor.V); - break; + // Maximize Saturation and Value components + switch (component) + { + case ColorComponent.Component1: // Hue + baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, 1.0); + break; + case ColorComponent.Component2: // Saturation + baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, baseHsvColor.S, 1.0); + break; + case ColorComponent.Component3: // Value + baseHsvColor = new HsvColor(baseHsvColor.A, baseHsvColor.H, 1.0, baseHsvColor.V); + break; + } + } + else + { + // Minimize component values other than the current one + switch (component) + { + case ColorComponent.Component1: // Red + baseRgbColor = new Color(baseRgbColor.A, baseRgbColor.R, 0, 0); + break; + case ColorComponent.Component2: // Green + baseRgbColor = new Color(baseRgbColor.A, 0, baseRgbColor.G, 0); + break; + case ColorComponent.Component3: // Blue + baseRgbColor = new Color(baseRgbColor.A, 0, 0, baseRgbColor.B); + break; + } } } diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/Hsv.cs b/src/Avalonia.Controls.ColorPicker/Helpers/Hsv.cs index 8a425b9581..76f33b3d83 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/Hsv.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/Hsv.cs @@ -11,7 +11,7 @@ namespace Avalonia.Controls.Primitives /// Contains and allows modification of Hue, Saturation and Value components. ///
/// - /// The is a specialized struct optimized for permanence and memory: + /// The is a specialized struct optimized for performance and memory: /// /// This is not a read-only struct like and allows editing the fields /// Removes the alpha component unnecessary in core calculations diff --git a/src/Avalonia.Controls.ColorPicker/Helpers/Rgb.cs b/src/Avalonia.Controls.ColorPicker/Helpers/Rgb.cs index 72e3821c2b..ee81a22ecf 100644 --- a/src/Avalonia.Controls.ColorPicker/Helpers/Rgb.cs +++ b/src/Avalonia.Controls.ColorPicker/Helpers/Rgb.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls.Primitives /// Contains and allows modification of Red, Green and Blue components. ///
/// - /// The is a specialized struct optimized for permanence and memory: + /// The is a specialized struct optimized for performance and memory: /// /// This is not a read-only struct like and allows editing the fields /// Removes the alpha component unnecessary in core calculations diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml index b3c7cd9f9c..f3100a648a 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml @@ -113,8 +113,8 @@ - + @@ -502,6 +502,23 @@ + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml index e05fa5a907..fabc5d0349 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPreviewer.xaml @@ -8,7 +8,9 @@ - + + + @@ -21,7 +23,6 @@ Height="{StaticResource ColorPreviewerAccentSectionHeight}" Width="{StaticResource ColorPreviewerAccentSectionWidth}" ColumnDefinitions="*,*" - Margin="0,0,-10,0" VerticalAlignment="Center"> + CornerRadius="{TemplateBinding CornerRadius}"> @@ -82,8 +80,7 @@ + VerticalAlignment="Stretch"> diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml index acd2c7ff15..8793467b36 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml @@ -360,8 +360,8 @@ - + + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml index ff4e1d93a8..d9ba8bb9d2 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPicker.xaml @@ -112,8 +112,8 @@ - + @@ -501,6 +501,23 @@ + + + diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml index a39dd91f52..9e123b2a1f 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml @@ -8,7 +8,9 @@ - + + + @@ -21,7 +23,6 @@ Height="{StaticResource ColorPreviewerAccentSectionHeight}" Width="{StaticResource ColorPreviewerAccentSectionWidth}" ColumnDefinitions="*,*" - Margin="0,0,-10,0" VerticalAlignment="Center"> + CornerRadius="{TemplateBinding CornerRadius}"> @@ -82,8 +80,7 @@ + VerticalAlignment="Stretch"> diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml index a26d3179b5..d4f02933f2 100644 --- a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml +++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorView.xaml @@ -322,8 +322,8 @@ - + + + + diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs index 2e3f1f96ce..e81ed8072e 100644 --- a/src/Avalonia.Controls/Calendar/CalendarItem.cs +++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs @@ -171,7 +171,7 @@ namespace Avalonia.Controls.Primitives var childCount = Calendar.RowsPerMonth + Calendar.RowsPerMonth * Calendar.ColumnsPerMonth; using var children = new PooledList(childCount); - for (int i = 0; i < Calendar.RowsPerMonth; i++) + for (int i = 0; i < Calendar.ColumnsPerMonth; i++) { if (DayTitleTemplate?.Build() is Control cell) { diff --git a/src/Avalonia.Controls/ContextMenu.cs b/src/Avalonia.Controls/ContextMenu.cs index 98b85dc31e..a9d8e4c9c7 100644 --- a/src/Avalonia.Controls/ContextMenu.cs +++ b/src/Avalonia.Controls/ContextMenu.cs @@ -285,7 +285,7 @@ namespace Avalonia.Controls } } - void ISetterValue.Initialize(ISetter setter) + void ISetterValue.Initialize(SetterBase setter) { // ContextMenu can be assigned to the ContextMenu property in a setter. This overrides // the behavior defined in Control which requires controls to be wrapped in a