Browse Source

Merge branch 'master' into fix-4733-property-accessor

pull/4746/head
Dariusz Komosiński 6 years ago
parent
commit
ba622ec262
  1. 14
      nukebuild/Build.cs
  2. 17
      samples/ControlCatalog/Pages/DataGridPage.xaml
  3. 3
      samples/RenderDemo/Pages/GlyphRunPage.xaml.cs
  4. 2
      src/Avalonia.Animation/KeySplineTypeConverter.cs
  5. 4
      src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs
  6. 5
      src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt
  7. 24
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  8. 34
      src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs
  9. 39
      src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs
  10. 231
      src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs
  11. 21
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  12. 22
      src/Avalonia.Controls.DataGrid/Utils/DataGridHelper.cs
  13. 9
      src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
  14. 4
      src/Avalonia.Controls/ComboBox.cs
  15. 6
      src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs
  16. 4
      src/Avalonia.Controls/Primitives/AccessText.cs
  17. 21
      src/Avalonia.Controls/Primitives/Popup.cs
  18. 5
      src/Avalonia.Controls/TextBlock.cs
  19. 2
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  20. 8
      src/Avalonia.Native/Avalonia.Native.csproj
  21. 4
      src/Avalonia.Themes.Default/ComboBox.xaml
  22. 2
      src/Avalonia.Themes.Default/MenuItem.xaml
  23. 7
      src/Avalonia.Themes.Default/PathIcon.xaml
  24. 33
      src/Avalonia.Themes.Fluent/CheckBox.xaml
  25. 3
      src/Avalonia.Themes.Fluent/ComboBox.xaml
  26. 2
      src/Avalonia.Themes.Fluent/ContextMenu.xaml
  27. 85
      src/Avalonia.Themes.Fluent/ListBoxItem.xaml
  28. 4
      src/Avalonia.Themes.Fluent/MenuItem.xaml
  29. 7
      src/Avalonia.Themes.Fluent/PathIcon.xaml
  30. 92
      src/Avalonia.Themes.Fluent/ScrollViewer.xaml
  31. 20
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  32. 5
      src/Avalonia.Visuals/Media/DrawingContext.cs
  33. 49
      src/Avalonia.Visuals/Media/GlyphRun.cs
  34. 13
      src/Avalonia.Visuals/Media/GlyphRunDrawing.cs
  35. 17
      src/Avalonia.Visuals/Media/TextDecoration.cs
  36. 7
      src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs
  37. 11
      src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs
  38. 6
      src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs
  39. 10
      src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs
  40. 3
      src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs
  41. 29
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  42. 2
      src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs
  43. 3
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  44. 19
      src/Avalonia.Visuals/Point.cs
  45. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  46. 13
      src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs
  47. 19
      src/Avalonia.Visuals/Size.cs
  48. 23
      src/Avalonia.Visuals/Thickness.cs
  49. 19
      src/Avalonia.Visuals/Vector.cs
  50. 8
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  51. 7
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  52. 4
      tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs
  53. 2
      tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs
  54. 10
      tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs
  55. 45
      tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs
  56. 2
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs
  57. 50
      tests/Avalonia.Visuals.UnitTests/Media/GlyphRunTests.cs

14
nukebuild/Build.cs

@ -138,9 +138,19 @@ partial class Build : NukeBuild
.SetWorkingDirectory(webappDir) .SetWorkingDirectory(webappDir)
.SetCommand("dist")); .SetCommand("dist"));
}); });
Target Compile => _ => _ Target CompileNative => _ => _
.DependsOn(Clean) .DependsOn(Clean)
.OnlyWhenStatic(() => EnvironmentInfo.IsOsx)
.Executes(() =>
{
var project = $"{RootDirectory}/native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/";
var args = $"-project {project} -configuration {Parameters.Configuration} CONFIGURATION_BUILD_DIR={RootDirectory}/Build/Products/Release";
ProcessTasks.StartProcess("xcodebuild", args).AssertZeroExitCode();
});
Target Compile => _ => _
.DependsOn(Clean, CompileNative)
.DependsOn(CompileHtmlPreviewer) .DependsOn(CompileHtmlPreviewer)
.Executes(async () => .Executes(async () =>
{ {

17
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -11,12 +11,17 @@
<Setter Property="Background" Value="{Binding Path=GDP, Mode=OneWay, Converter={StaticResource GDPConverter}}" /> <Setter Property="Background" Value="{Binding Path=GDP, Mode=OneWay, Converter={StaticResource GDPConverter}}" />
</Style> </Style>
</UserControl.Styles> </UserControl.Styles>
<Grid RowDefinitions="Auto,*"> <Grid RowDefinitions="Auto,Auto,*">
<StackPanel Orientation="Vertical" Spacing="4" Grid.Row="0"> <StackPanel Orientation="Vertical" Spacing="4" Grid.Row="0">
<TextBlock Classes="h1">DataGrid</TextBlock> <TextBlock Classes="h1">DataGrid</TextBlock>
<TextBlock Classes="h2">A control for displaying and interacting with a data source.</TextBlock> <TextBlock Classes="h2">A control for displaying and interacting with a data source.</TextBlock>
</StackPanel> </StackPanel>
<TabControl Grid.Row="1"> <StackPanel Grid.Row="1" Spacing="4" Orientation="Horizontal" IsVisible="{Binding #EditableTab.IsSelected}">
<TextBlock Text="FontSize:" VerticalAlignment="Center"/>
<Slider x:Name="FontSizeSlider" Minimum="5" Maximum="30" Value="14" Width="100" VerticalAlignment="Center" />
<CheckBox x:Name="IsThreeStateCheckBox" IsChecked="False" Content="IsThreeState"/>
</StackPanel>
<TabControl Grid.Row="2">
<TabItem Header="DataGrid"> <TabItem Header="DataGrid">
<DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True" CanUserSortColumns="True" HeadersVisibility="All"> <DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True" CanUserSortColumns="True" HeadersVisibility="All">
<DataGrid.Columns> <DataGrid.Columns>
@ -39,13 +44,13 @@
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
</TabItem> </TabItem>
<TabItem Header="Editable"> <TabItem x:Name="EditableTab" Header="Editable">
<Grid RowDefinitions="*,Auto"> <Grid RowDefinitions="*,Auto">
<DataGrid Name="dataGridEdit" Margin="12" Grid.Row="0"> <DataGrid Name="dataGridEdit" Margin="12" Grid.Row="0">
<DataGrid.Columns> <DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" Width="2*" /> <DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" Width="2*" FontSize="{Binding #FontSizeSlider.Value, Mode=OneWay}" />
<DataGridTextColumn Header="Last" Binding="{Binding LastName}" Width="2*" /> <DataGridTextColumn Header="Last" Binding="{Binding LastName}" Width="2*" FontSize="{Binding #FontSizeSlider.Value, Mode=OneWay}" />
<DataGridCheckBoxColumn Header="Is Banned" Binding="{Binding IsBanned}" Width="*" /> <DataGridCheckBoxColumn Header="Is Banned" Binding="{Binding IsBanned}" Width="*" IsThreeState="{Binding #IsThreeStateCheckBox.IsChecked, Mode=OneWay}" />
</DataGrid.Columns> </DataGrid.Columns>
</DataGrid> </DataGrid>
<Button Grid.Row="1" Name="btnAdd" Margin="12,0,12,12" Content="Add" HorizontalAlignment="Right" /> <Button Grid.Row="1" Name="btnAdd" Margin="12,0,12,12" Content="Add" HorizontalAlignment="Right" />

3
samples/RenderDemo/Pages/GlyphRunPage.xaml.cs

@ -61,7 +61,6 @@ namespace RenderDemo.Pages
{ {
Foreground = Brushes.Black, Foreground = Brushes.Black,
GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _glyphIndices), GlyphRun = new GlyphRun(_glyphTypeface, _fontSize, _glyphIndices),
BaselineOrigin = new Point(0, -_glyphTypeface.Ascent * scale)
}; };
drawingGroup.Children.Add(glyphRunDrawing); drawingGroup.Children.Add(glyphRunDrawing);
@ -69,7 +68,7 @@ namespace RenderDemo.Pages
var geometryDrawing = new GeometryDrawing var geometryDrawing = new GeometryDrawing
{ {
Pen = new Pen(Brushes.Black), Pen = new Pen(Brushes.Black),
Geometry = new RectangleGeometry { Rect = glyphRunDrawing.GlyphRun.Bounds } Geometry = new RectangleGeometry { Rect = new Rect(glyphRunDrawing.GlyphRun.Size) }
}; };
drawingGroup.Children.Add(geometryDrawing); drawingGroup.Children.Add(geometryDrawing);

2
src/Avalonia.Animation/KeySplineTypeConverter.cs

@ -19,7 +19,7 @@ namespace Avalonia.Animation
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{ {
return KeySpline.Parse((string)value, culture); return KeySpline.Parse((string)value, CultureInfo.InvariantCulture);
} }
} }
} }

4
src/Avalonia.Base/Data/Core/Plugins/DataAnnotationsValidationPlugin.cs

@ -63,12 +63,12 @@ namespace Avalonia.Data.Core.Plugins
{ {
if (errors.Count == 1) if (errors.Count == 1)
{ {
return new ValidationException(errors[0].ErrorMessage); return new DataValidationException(errors[0].ErrorMessage);
} }
else else
{ {
return new AggregateException( return new AggregateException(
errors.Select(x => new ValidationException(x.ErrorMessage))); errors.Select(x => new DataValidationException(x.ErrorMessage)));
} }
} }
} }

5
src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt

@ -0,0 +1,5 @@
Compat issues with assembly Avalonia.Controls.DataGrid:
MembersMustExist : Member 'public Avalonia.StyledProperty<System.String> Avalonia.StyledProperty<System.String> Avalonia.Controls.DataGridTextColumn.FontFamilyProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public System.String Avalonia.Controls.DataGridTextColumn.FontFamily.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Controls.DataGridTextColumn.FontFamily.set(System.String)' does not exist in the implementation but it does exist in the contract.
Total Issues: 3

24
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -31,7 +31,7 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Displays data in a customizable grid. /// Displays data in a customizable grid.
/// </summary> /// </summary>
[PseudoClasses(":invalid")] [PseudoClasses(":invalid", ":empty-rows", ":empty-columns")]
public partial class DataGrid : TemplatedControl public partial class DataGrid : TemplatedControl
{ {
private const string DATAGRID_elementRowsPresenterName = "PART_RowsPresenter"; private const string DATAGRID_elementRowsPresenterName = "PART_RowsPresenter";
@ -711,6 +711,7 @@ namespace Avalonia.Controls
DisplayData = new DataGridDisplayData(this); DisplayData = new DataGridDisplayData(this);
ColumnsInternal = CreateColumnsInstance(); ColumnsInternal = CreateColumnsInstance();
ColumnsInternal.CollectionChanged += ColumnsInternal_CollectionChanged;
RowHeightEstimate = DATAGRID_defaultRowHeight; RowHeightEstimate = DATAGRID_defaultRowHeight;
RowDetailsHeightEstimate = 0; RowDetailsHeightEstimate = 0;
@ -727,6 +728,8 @@ namespace Avalonia.Controls
CurrentCellCoordinates = new DataGridCellCoordinates(-1, -1); CurrentCellCoordinates = new DataGridCellCoordinates(-1, -1);
RowGroupHeaderHeightEstimate = DATAGRID_defaultRowHeight; RowGroupHeaderHeightEstimate = DATAGRID_defaultRowHeight;
UpdatePseudoClasses();
} }
private void SetValueNoCallback<T>(AvaloniaProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue) private void SetValueNoCallback<T>(AvaloniaProperty<T> property, T value, BindingPriority priority = BindingPriority.LocalValue)
@ -851,9 +854,27 @@ namespace Avalonia.Controls
// can be set when the DataGrid is not part of the visual tree // can be set when the DataGrid is not part of the visual tree
_measured = false; _measured = false;
InvalidateMeasure(); InvalidateMeasure();
UpdatePseudoClasses();
} }
} }
private void ColumnsInternal_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add
|| e.Action == NotifyCollectionChangedAction.Remove
|| e.Action == NotifyCollectionChangedAction.Reset)
{
UpdatePseudoClasses();
}
}
internal void UpdatePseudoClasses()
{
PseudoClasses.Set(":empty-columns", !ColumnsInternal.GetVisibleColumns().Any());
PseudoClasses.Set(":empty-rows", !DataConnection.Any());
}
private void OnSelectedIndexChanged(AvaloniaPropertyChangedEventArgs e) private void OnSelectedIndexChanged(AvaloniaPropertyChangedEventArgs e)
{ {
if (!_areHandlersSuspended) if (!_areHandlersSuspended)
@ -1348,7 +1369,6 @@ namespace Avalonia.Controls
internal DataGridColumnCollection ColumnsInternal internal DataGridColumnCollection ColumnsInternal
{ {
get; get;
private set;
} }
internal int AnchorSlot internal int AnchorSlot

34
src/Avalonia.Controls.DataGrid/DataGridCheckBoxColumn.cs

@ -17,9 +17,7 @@ namespace Avalonia.Controls
/// </summary> /// </summary>
public class DataGridCheckBoxColumn : DataGridBoundColumn public class DataGridCheckBoxColumn : DataGridBoundColumn
{ {
private bool _beganEditWithKeyboard; private bool _beganEditWithKeyboard;
private bool _isThreeState;
private CheckBox _currentCheckBox; private CheckBox _currentCheckBox;
private DataGrid _owningGrid; private DataGrid _owningGrid;
@ -31,6 +29,12 @@ namespace Avalonia.Controls
BindingTarget = CheckBox.IsCheckedProperty; BindingTarget = CheckBox.IsCheckedProperty;
} }
/// <summary>
/// Defines the <see cref="IsThreeState"/> property.
/// </summary>
public static StyledProperty<bool> IsThreeStateProperty =
CheckBox.IsThreeStateProperty.AddOwner<DataGridCheckBoxColumn>();
/// <summary> /// <summary>
/// Gets or sets a value that indicates whether the hosted <see cref="T:System.Windows.Controls.CheckBox" /> controls allow three states or two. /// Gets or sets a value that indicates whether the hosted <see cref="T:System.Windows.Controls.CheckBox" /> controls allow three states or two.
/// </summary> /// </summary>
@ -39,17 +43,17 @@ namespace Avalonia.Controls
/// </returns> /// </returns>
public bool IsThreeState public bool IsThreeState
{ {
get get => GetValue(IsThreeStateProperty);
{ set => SetValue(IsThreeStateProperty, value);
return _isThreeState; }
}
set protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
if (change.Property == IsThreeStateProperty)
{ {
if (_isThreeState != value) NotifyPropertyChanged(change.Property.Name);
{
_isThreeState = value;
NotifyPropertyChanged(nameof(IsThreeState));
}
} }
} }
@ -203,9 +207,9 @@ namespace Avalonia.Controls
{ {
throw new ArgumentNullException("element"); throw new ArgumentNullException("element");
} }
if(element is CheckBox checkBox) if (element is CheckBox checkBox)
{ {
checkBox.IsThreeState = IsThreeState; DataGridHelper.SyncColumnProperty(this, checkBox, IsThreeStateProperty);
} }
else else
{ {
@ -229,7 +233,7 @@ namespace Avalonia.Controls
{ {
checkBox.HorizontalAlignment = HorizontalAlignment.Center; checkBox.HorizontalAlignment = HorizontalAlignment.Center;
checkBox.VerticalAlignment = VerticalAlignment.Center; checkBox.VerticalAlignment = VerticalAlignment.Center;
checkBox.IsThreeState = IsThreeState; DataGridHelper.SyncColumnProperty(this, checkBox, IsThreeStateProperty);
} }
private bool EnsureOwningGrid() private bool EnsureOwningGrid()

39
src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs

@ -77,24 +77,7 @@ namespace Avalonia.Controls
private set; private set;
} }
public int Count public int Count => GetCount(true);
{
get
{
IList list = List;
if (list != null)
{
return list.Count;
}
if(DataSource is DataGridCollectionView cv)
{
return cv.Count;
}
return DataSource?.Cast<object>().Count() ?? 0;
}
}
public bool DataIsPrimitive public bool DataIsPrimitive
{ {
@ -210,6 +193,24 @@ namespace Avalonia.Controls
} }
} }
internal bool Any()
{
return GetCount(false) > 0;
}
/// <param name="allowSlow">When "allowSlow" is false, method will not use Linq.Count() method and will return 0 or 1 instead.</param>
private int GetCount(bool allowSlow)
{
return DataSource switch
{
ICollection collection => collection.Count,
DataGridCollectionView cv => cv.Count,
IEnumerable enumerable when allowSlow => enumerable.Cast<object>().Count(),
IEnumerable enumerable when !allowSlow => enumerable.Cast<object>().Any() ? 1 : 0,
_ => 0
};
}
/// <summary> /// <summary>
/// Puts the entity into editing mode if possible /// Puts the entity into editing mode if possible
/// </summary> /// </summary>
@ -675,6 +676,8 @@ namespace Avalonia.Controls
} }
break; break;
} }
_owner.UpdatePseudoClasses();
} }
private void UpdateDataProperties() private void UpdateDataProperties()

231
src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs

@ -20,11 +20,6 @@ namespace Avalonia.Controls
{ {
private const string DATAGRID_TextColumnCellTextBlockMarginKey = "DataGridTextColumnCellTextBlockMargin"; private const string DATAGRID_TextColumnCellTextBlockMarginKey = "DataGridTextColumnCellTextBlockMargin";
private double? _fontSize;
private FontStyle? _fontStyle;
private FontWeight? _fontWeight;
private IBrush _foreground;
/// <summary> /// <summary>
/// Initializes a new instance of the <see cref="T:Avalonia.Controls.DataGridTextColumn" /> class. /// Initializes a new instance of the <see cref="T:Avalonia.Controls.DataGridTextColumn" /> class.
/// </summary> /// </summary>
@ -36,18 +31,24 @@ namespace Avalonia.Controls
/// <summary> /// <summary>
/// Identifies the FontFamily dependency property. /// Identifies the FontFamily dependency property.
/// </summary> /// </summary>
public static readonly StyledProperty<string> FontFamilyProperty = public static readonly AttachedProperty<FontFamily> FontFamilyProperty =
AvaloniaProperty.Register<DataGridTextColumn, string>(nameof(FontFamily)); TextBlock.FontFamilyProperty.AddOwner<DataGridTextColumn>();
/// <summary> /// <summary>
/// Gets or sets the font name. /// Gets or sets the font name.
/// </summary> /// </summary>
public string FontFamily public FontFamily FontFamily
{ {
get { return GetValue(FontFamilyProperty); } get => GetValue(FontFamilyProperty);
set { SetValue(FontFamilyProperty, value); } set => SetValue(FontFamilyProperty, value);
} }
/// <summary>
/// Identifies the FontSize dependency property.
/// </summary>
public static readonly AttachedProperty<double> FontSizeProperty =
TextBlock.FontSizeProperty.AddOwner<DataGridTextColumn>();
/// <summary> /// <summary>
/// Gets or sets the font size. /// Gets or sets the font size.
/// </summary> /// </summary>
@ -55,74 +56,66 @@ namespace Avalonia.Controls
[DefaultValue(double.NaN)] [DefaultValue(double.NaN)]
public double FontSize public double FontSize
{ {
get get => GetValue(FontSizeProperty);
{ set => SetValue(FontSizeProperty, value);
return _fontSize ?? Double.NaN;
}
set
{
if (_fontSize != value)
{
_fontSize = value;
NotifyPropertyChanged(nameof(FontSize));
}
}
} }
/// <summary>
/// Identifies the FontStyle dependency property.
/// </summary>
public static readonly AttachedProperty<FontStyle> FontStyleProperty =
TextBlock.FontStyleProperty.AddOwner<DataGridTextColumn>();
/// <summary> /// <summary>
/// Gets or sets the font style. /// Gets or sets the font style.
/// </summary> /// </summary>
public FontStyle FontStyle public FontStyle FontStyle
{ {
get get => GetValue(FontStyleProperty);
{ set => SetValue(FontStyleProperty, value);
return _fontStyle ?? FontStyle.Normal;
}
set
{
if (_fontStyle != value)
{
_fontStyle = value;
NotifyPropertyChanged(nameof(FontStyle));
}
}
} }
/// <summary>
/// Identifies the FontWeight dependency property.
/// </summary>
public static readonly AttachedProperty<FontWeight> FontWeightProperty =
TextBlock.FontWeightProperty.AddOwner<DataGridTextColumn>();
/// <summary> /// <summary>
/// Gets or sets the font weight or thickness. /// Gets or sets the font weight or thickness.
/// </summary> /// </summary>
public FontWeight FontWeight public FontWeight FontWeight
{ {
get get => GetValue(FontWeightProperty);
{ set => SetValue(FontWeightProperty, value);
return _fontWeight ?? FontWeight.Normal;
}
set
{
if (_fontWeight != value)
{
_fontWeight = value;
NotifyPropertyChanged(nameof(FontWeight));
}
}
} }
/// <summary>
/// Identifies the Foreground dependency property.
/// </summary>
public static readonly AttachedProperty<IBrush> ForegroundProperty =
TextBlock.ForegroundProperty.AddOwner<DataGridTextColumn>();
/// <summary> /// <summary>
/// Gets or sets a brush that describes the foreground of the column cells. /// Gets or sets a brush that describes the foreground of the column cells.
/// </summary> /// </summary>
public IBrush Foreground public IBrush Foreground
{ {
get get => GetValue(ForegroundProperty);
{ set => SetValue(ForegroundProperty, value);
return _foreground; }
}
set protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
if (change.Property == FontFamilyProperty
|| change.Property == FontSizeProperty
|| change.Property == FontStyleProperty
|| change.Property == FontWeightProperty
|| change.Property == ForegroundProperty)
{ {
if (_foreground != value) NotifyPropertyChanged(change.Property.Name);
{
_foreground = value;
NotifyPropertyChanged(nameof(Foreground));
}
} }
} }
@ -154,26 +147,7 @@ namespace Avalonia.Controls
Background = new SolidColorBrush(Colors.Transparent) Background = new SolidColorBrush(Colors.Transparent)
}; };
if (IsSet(FontFamilyProperty)) SyncProperties(textBox);
{
textBox.FontFamily = FontFamily;
}
if (_fontSize.HasValue)
{
textBox.FontSize = _fontSize.Value;
}
if (_fontStyle.HasValue)
{
textBox.FontStyle = _fontStyle.Value;
}
if (_fontWeight.HasValue)
{
textBox.FontWeight = _fontWeight.Value;
}
if (_foreground != null)
{
textBox.Foreground = _foreground;
}
return textBox; return textBox;
} }
@ -192,26 +166,8 @@ namespace Avalonia.Controls
VerticalAlignment = VerticalAlignment.Center VerticalAlignment = VerticalAlignment.Center
}; };
if (IsSet(FontFamilyProperty)) SyncProperties(textBlockElement);
{
textBlockElement.FontFamily = FontFamily;
}
if (_fontSize.HasValue)
{
textBlockElement.FontSize = _fontSize.Value;
}
if (_fontStyle.HasValue)
{
textBlockElement.FontStyle = _fontStyle.Value;
}
if (_fontWeight.HasValue)
{
textBlockElement.FontWeight = _fontWeight.Value;
}
if (_foreground != null)
{
textBlockElement.Foreground = _foreground;
}
if (Binding != null) if (Binding != null)
{ {
textBlockElement.Bind(TextBlock.TextProperty, Binding); textBlockElement.Bind(TextBlock.TextProperty, Binding);
@ -261,99 +217,42 @@ namespace Avalonia.Controls
throw new ArgumentNullException("element"); throw new ArgumentNullException("element");
} }
if(element is TextBox textBox) if (element is AvaloniaObject content)
{ {
if (propertyName == nameof(FontFamily)) if (propertyName == nameof(FontFamily))
{ {
textBox.FontFamily = FontFamily; DataGridHelper.SyncColumnProperty(this, content, FontFamilyProperty);
} }
else if (propertyName == nameof(FontSize)) else if (propertyName == nameof(FontSize))
{ {
SetTextFontSize(textBox, TextBox.FontSizeProperty); DataGridHelper.SyncColumnProperty(this, content, FontSizeProperty);
} }
else if (propertyName == nameof(FontStyle)) else if (propertyName == nameof(FontStyle))
{ {
textBox.FontStyle = FontStyle; DataGridHelper.SyncColumnProperty(this, content, FontStyleProperty);
} }
else if (propertyName == nameof(FontWeight)) else if (propertyName == nameof(FontWeight))
{ {
textBox.FontWeight = FontWeight; DataGridHelper.SyncColumnProperty(this, content, FontWeightProperty);
} }
else if (propertyName == nameof(Foreground)) else if (propertyName == nameof(Foreground))
{ {
textBox.Foreground = Foreground; DataGridHelper.SyncColumnProperty(this, content, ForegroundProperty);
}
else
{
if (FontFamily != null)
{
textBox.FontFamily = FontFamily;
}
SetTextFontSize(textBox, TextBox.FontSizeProperty);
textBox.FontStyle = FontStyle;
textBox.FontWeight = FontWeight;
if (Foreground != null)
{
textBox.Foreground = Foreground;
}
}
}
else if (element is TextBlock textBlock)
{
if (propertyName == nameof(FontFamily))
{
textBlock.FontFamily = FontFamily;
}
else if (propertyName == nameof(FontSize))
{
SetTextFontSize(textBlock, TextBlock.FontSizeProperty);
}
else if (propertyName == nameof(FontStyle))
{
textBlock.FontStyle = FontStyle;
}
else if (propertyName == nameof(FontWeight))
{
textBlock.FontWeight = FontWeight;
}
else if (propertyName == nameof(Foreground))
{
textBlock.Foreground = Foreground;
}
else
{
if (FontFamily != null)
{
textBlock.FontFamily = FontFamily;
}
SetTextFontSize(textBlock, TextBlock.FontSizeProperty);
textBlock.FontStyle = FontStyle;
textBlock.FontWeight = FontWeight;
if (Foreground != null)
{
textBlock.Foreground = Foreground;
}
} }
} }
else else
{ {
throw DataGridError.DataGrid.ValueIsNotAnInstanceOfEitherOr("element", typeof(TextBox), typeof(TextBlock)); throw DataGridError.DataGrid.ValueIsNotAnInstanceOf("element", typeof(AvaloniaObject));
} }
} }
private void SetTextFontSize(AvaloniaObject textElement, AvaloniaProperty fontSizeProperty) private void SyncProperties(AvaloniaObject content)
{ {
double newFontSize = FontSize; DataGridHelper.SyncColumnProperty(this, content, FontFamilyProperty);
if (double.IsNaN(newFontSize)) DataGridHelper.SyncColumnProperty(this, content, FontSizeProperty);
{ DataGridHelper.SyncColumnProperty(this, content, FontStyleProperty);
textElement.ClearValue(fontSizeProperty); DataGridHelper.SyncColumnProperty(this, content, FontWeightProperty);
} DataGridHelper.SyncColumnProperty(this, content, ForegroundProperty);
else
{
textElement.SetValue(fontSizeProperty, newFontSize);
}
} }
} }
} }

21
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@ -588,18 +588,11 @@
<!--<DataGridColumnHeader Name="PART_TopRightCornerHeader" <!--<DataGridColumnHeader Name="PART_TopRightCornerHeader"
Grid.Column="2" Grid.Column="2"
Template="{StaticResource TopRightHeaderTemplate}" />--> Template="{StaticResource TopRightHeaderTemplate}" />-->
<!--<Rectangle Name="PART_ColumnHeadersAndRowsSeparator" <Rectangle Name="PART_ColumnHeadersAndRowsSeparator"
Grid.ColumnSpan="3" Grid.ColumnSpan="3"
VerticalAlignment="Bottom" VerticalAlignment="Bottom"
StrokeThickness="1"
Height="1" Height="1"
Fill="{DynamicResource DataGridGridLinesBrush}" />--> Fill="{DynamicResource DataGridGridLinesBrush}" />
<Border Name="PART_ColumnHeadersAndRowsSeparator"
Grid.ColumnSpan="3"
Height="2"
VerticalAlignment="Bottom"
BorderThickness="0,0,0,1"
BorderBrush="{DynamicResource DataGridGridLinesBrush}" />
<DataGridRowsPresenter Name="PART_RowsPresenter" <DataGridRowsPresenter Name="PART_RowsPresenter"
Grid.Row="1" Grid.Row="1"
@ -642,4 +635,14 @@
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="DataGrid:empty-columns /template/ DataGridColumnHeader#PART_TopLeftCornerHeader">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGrid:empty-columns /template/ DataGridColumnHeadersPresenter#PART_ColumnHeadersPresenter">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGrid:empty-columns /template/ Rectangle#PART_ColumnHeadersAndRowsSeparator">
<Setter Property="IsVisible" Value="False" />
</Style>
</Styles> </Styles>

22
src/Avalonia.Controls.DataGrid/Utils/DataGridHelper.cs

@ -0,0 +1,22 @@
namespace Avalonia.Controls
{
internal static class DataGridHelper
{
internal static void SyncColumnProperty<T>(AvaloniaObject column, AvaloniaObject content, AvaloniaProperty<T> property)
{
SyncColumnProperty(column, content, property, property);
}
internal static void SyncColumnProperty<T>(AvaloniaObject column, AvaloniaObject content, AvaloniaProperty<T> contentProperty, AvaloniaProperty<T> columnProperty)
{
if (!column.IsSet(columnProperty))
{
content.ClearValue(contentProperty);
}
else
{
content.SetValue(contentProperty, column.GetValue(columnProperty));
}
}
}
}

9
src/Avalonia.Controls/Calendar/CalendarDatePicker.cs

@ -420,7 +420,7 @@ namespace Avalonia.Controls
_calendar.DayButtonMouseUp -= Calendar_DayButtonMouseUp; _calendar.DayButtonMouseUp -= Calendar_DayButtonMouseUp;
_calendar.DisplayDateChanged -= Calendar_DisplayDateChanged; _calendar.DisplayDateChanged -= Calendar_DisplayDateChanged;
_calendar.SelectedDatesChanged -= Calendar_SelectedDatesChanged; _calendar.SelectedDatesChanged -= Calendar_SelectedDatesChanged;
_calendar.PointerPressed -= Calendar_PointerPressed; _calendar.PointerReleased -= Calendar_PointerReleased;
_calendar.KeyDown -= Calendar_KeyDown; _calendar.KeyDown -= Calendar_KeyDown;
} }
_calendar = e.NameScope.Find<Calendar>(ElementCalendar); _calendar = e.NameScope.Find<Calendar>(ElementCalendar);
@ -435,7 +435,7 @@ namespace Avalonia.Controls
_calendar.DayButtonMouseUp += Calendar_DayButtonMouseUp; _calendar.DayButtonMouseUp += Calendar_DayButtonMouseUp;
_calendar.DisplayDateChanged += Calendar_DisplayDateChanged; _calendar.DisplayDateChanged += Calendar_DisplayDateChanged;
_calendar.SelectedDatesChanged += Calendar_SelectedDatesChanged; _calendar.SelectedDatesChanged += Calendar_SelectedDatesChanged;
_calendar.PointerPressed += Calendar_PointerPressed; _calendar.PointerReleased += Calendar_PointerReleased;
_calendar.KeyDown += Calendar_KeyDown; _calendar.KeyDown += Calendar_KeyDown;
//_calendar.SizeChanged += new SizeChangedEventHandler(Calendar_SizeChanged); //_calendar.SizeChanged += new SizeChangedEventHandler(Calendar_SizeChanged);
//_calendar.IsTabStop = true; //_calendar.IsTabStop = true;
@ -831,9 +831,10 @@ namespace Avalonia.Controls
} }
} }
} }
private void Calendar_PointerPressed(object sender, PointerPressedEventArgs e) private void Calendar_PointerReleased(object sender, PointerReleasedEventArgs e)
{ {
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
if (e.InitialPressMouseButton == MouseButton.Left)
{ {
e.Handled = true; e.Handled = true;
} }

4
src/Avalonia.Controls/ComboBox.cs

@ -257,7 +257,7 @@ namespace Avalonia.Controls
} }
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnPointerPressed(PointerPressedEventArgs e) protected override void OnPointerReleased(PointerReleasedEventArgs e)
{ {
if (!e.Handled) if (!e.Handled)
{ {
@ -276,7 +276,7 @@ namespace Avalonia.Controls
} }
} }
base.OnPointerPressed(e); base.OnPointerReleased(e);
} }
/// <inheritdoc/> /// <inheritdoc/>

6
src/Avalonia.Controls/Generators/ItemContainerGenerator`1.cs

@ -47,11 +47,7 @@ namespace Avalonia.Controls.Generators
{ {
var container = item as T; var container = item as T;
if (item == null) if (container != null)
{
return null;
}
else if (container != null)
{ {
return container; return container;
} }

4
src/Avalonia.Controls/Primitives/AccessText.cs

@ -126,7 +126,7 @@ namespace Avalonia.Controls.Primitives
if (shapedTextCharacters.GlyphRun.Characters.End < textPosition) if (shapedTextCharacters.GlyphRun.Characters.End < textPosition)
{ {
currentX += shapedTextCharacters.GlyphRun.Bounds.Width; currentX += shapedTextCharacters.Size.Width;
continue; continue;
} }
@ -143,7 +143,7 @@ namespace Avalonia.Controls.Primitives
width = 0.0; width = 0.0;
} }
return new Rect(currentX, currentY, width, shapedTextCharacters.GlyphRun.Bounds.Height); return new Rect(currentX, currentY, width, shapedTextCharacters.Size.Height);
} }
} }

21
src/Avalonia.Controls/Primitives/Popup.cs

@ -128,6 +128,7 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty<bool> TopmostProperty = public static readonly StyledProperty<bool> TopmostProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(Topmost)); AvaloniaProperty.Register<Popup, bool>(nameof(Topmost));
private bool _isOpenRequested = false;
private bool _isOpen; private bool _isOpen;
private bool _ignoreIsOpenChanged; private bool _ignoreIsOpenChanged;
private PopupOpenState? _openState; private PopupOpenState? _openState;
@ -361,17 +362,19 @@ namespace Avalonia.Controls.Primitives
if (placementTarget == null) if (placementTarget == null)
{ {
throw new InvalidOperationException("Popup has no logical parent and PlacementTarget is null"); _isOpenRequested = true;
return;
} }
var topLevel = placementTarget.VisualRoot as TopLevel; var topLevel = placementTarget.VisualRoot as TopLevel;
if (topLevel == null) if (topLevel == null)
{ {
throw new InvalidOperationException( _isOpenRequested = true;
"Attempted to open a popup not attached to a TopLevel"); return;
} }
_isOpenRequested = false;
var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver); var popupHost = OverlayPopupHost.CreatePopupHost(placementTarget, DependencyResolver);
var handlerCleanup = new CompositeDisposable(5); var handlerCleanup = new CompositeDisposable(5);
@ -492,6 +495,17 @@ namespace Avalonia.Controls.Primitives
return new Size(); return new Size();
} }
/// <inheritdoc/>
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (_isOpenRequested)
{
Open();
}
}
/// <inheritdoc/> /// <inheritdoc/>
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e) protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{ {
@ -552,6 +566,7 @@ namespace Avalonia.Controls.Primitives
private void CloseCore() private void CloseCore()
{ {
_isOpenRequested = false;
if (_openState is null) if (_openState is null)
{ {
using (BeginIgnoringIsOpen()) using (BeginIgnoringIsOpen())

5
src/Avalonia.Controls/TextBlock.cs

@ -434,7 +434,10 @@ namespace Avalonia.Controls
var padding = Padding; var padding = Padding;
TextLayout.Draw(context, new Point(padding.Left + offsetX, padding.Top)); using (context.PushPostTransform(Matrix.CreateTranslation(padding.Left + offsetX, padding.Top)))
{
TextLayout.Draw(context);
}
} }
/// <summary> /// <summary>

2
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -385,7 +385,7 @@ namespace Avalonia.Headless
} }
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin) public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{ {
} }

8
src/Avalonia.Native/Avalonia.Native.csproj

@ -1,7 +1,8 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<IsPackable>false</IsPackable> <PackAvaloniaNative Condition="'$(PackAvaloniaNative)' == ''">$([MSBuild]::IsOSPlatform(OSX))</PackAvaloniaNative>
<IsPackable>$(PackAvaloniaNative)</IsPackable>
<IsPackable Condition="'$([MSBuild]::IsOSPlatform(OSX))' == 'True'">true</IsPackable> <IsPackable Condition="'$([MSBuild]::IsOSPlatform(OSX))' == 'True'">true</IsPackable>
<TargetFramework>netstandard2.0</TargetFramework> <TargetFramework>netstandard2.0</TargetFramework>
<CastXmlPath Condition="Exists('/usr/bin/castxml')">/usr/bin/castxml</CastXmlPath> <CastXmlPath Condition="Exists('/usr/bin/castxml')">/usr/bin/castxml</CastXmlPath>
@ -10,8 +11,9 @@
<SharpGenGenerateConsumerBindMapping>false</SharpGenGenerateConsumerBindMapping> <SharpGenGenerateConsumerBindMapping>false</SharpGenGenerateConsumerBindMapping>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(Configuration)' == 'Release' AND '$([MSBuild]::IsOSPlatform(OSX))' == 'true'"> <ItemGroup Condition="'$(PackAvaloniaNative)' == 'true'">
<Content Include="../../Build/Products/Release/libAvalonia.Native.OSX.dylib"> <Content Include="../../Build/Products/Release/libAvalonia.Native.OSX.dylib">
<Link>libAvaloniaNative.dylib</Link>
<PackagePath>runtimes/osx/native/libAvaloniaNative.dylib</PackagePath> <PackagePath>runtimes/osx/native/libAvaloniaNative.dylib</PackagePath>
<Pack>true</Pack> <Pack>true</Pack>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@ -26,4 +28,4 @@
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" /> <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" /> <ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

4
src/Avalonia.Themes.Default/ComboBox.xaml

@ -26,6 +26,7 @@
<Setter Property="Padding" Value="4" /> <Setter Property="Padding" Value="4" />
<Setter Property="MinHeight" Value="20" /> <Setter Property="MinHeight" Value="20" />
<Setter Property="PlaceholderForeground" Value="{DynamicResource ThemeForegroundBrush}" /> <Setter Property="PlaceholderForeground" Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border Name="border" <Border Name="border"
@ -69,7 +70,8 @@
IsLightDismissEnabled="True"> IsLightDismissEnabled="True">
<Border BorderBrush="{DynamicResource ThemeBorderMidBrush}" <Border BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="1"> BorderThickness="1">
<ScrollViewer> <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"

2
src/Avalonia.Themes.Default/MenuItem.xaml

@ -64,7 +64,7 @@
<Border Background="{TemplateBinding Background}" <Border Background="{TemplateBinding Background}"
BorderBrush="{DynamicResource ThemeBorderMidBrush}" BorderBrush="{DynamicResource ThemeBorderMidBrush}"
BorderThickness="{TemplateBinding BorderThickness}"> BorderThickness="{TemplateBinding BorderThickness}">
<ScrollViewer> <ScrollViewer Classes="menuscroller">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"

7
src/Avalonia.Themes.Default/PathIcon.xaml

@ -2,14 +2,15 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="PathIcon"> <Style Selector="PathIcon">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" /> <Setter Property="Foreground" Value="{DynamicResource ThemeForegroundColor}" />
<Setter Property="Height" Value="16" /> <Setter Property="Height" Value="{DynamicResource IconElementThemeHeight}" />
<Setter Property="Width" Value="16" /> <Setter Property="Width" Value="{DynamicResource IconElementThemeWidth}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Viewbox Height="{TemplateBinding Height}" <Viewbox Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"> Width="{TemplateBinding Width}">
<Path Fill="{TemplateBinding Foreground}" <Path Fill="{TemplateBinding Foreground}"
Data="{TemplateBinding Data}" /> Data="{TemplateBinding Data}"
Stretch="Uniform" />
</Viewbox> </Viewbox>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>

33
src/Avalonia.Themes.Fluent/CheckBox.xaml

@ -1,13 +1,10 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="20"> <Border Padding="20">
<CheckBox IsThreeState="True" IsChecked="True" /> <CheckBox IsThreeState="True" IsChecked="True" Content="Content" Foreground="Gold" />
</Border> </Border>
</Design.PreviewWith> </Design.PreviewWith>
<Style Selector="CheckBox"> <Style Selector="CheckBox">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUnchecked}" />
<Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUnchecked}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUnchecked}" />
<Setter Property="Padding" Value="8,0,0,0" /> <Setter Property="Padding" Value="8,0,0,0" />
<Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="VerticalAlignment" Value="Center" />
@ -21,10 +18,12 @@
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Grid x:Name="RootGrid" ColumnDefinitions="20,*"> <Grid x:Name="RootGrid" ColumnDefinitions="20,*">
<Border x:Name="PART_Border" Background="{TemplateBinding Background}" <Border x:Name="PART_Border"
BorderBrush="{TemplateBinding BorderBrush}" Grid.ColumnSpan="2"
BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}"
CornerRadius="{DynamicResource ControlCornerRadius}" /> BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
CornerRadius="{DynamicResource ControlCornerRadius}" />
<Grid VerticalAlignment="Top" Height="32"> <Grid VerticalAlignment="Top" Height="32">
<Border x:Name="NormalRectangle" <Border x:Name="NormalRectangle"
@ -55,11 +54,11 @@
</Style> </Style>
<!-- Unchecked Normal State --> <!-- Unchecked Normal State -->
<Style Selector="CheckBox /template/ ContentPresenter#ContentPresenter"> <Style Selector="CheckBox">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CheckBoxForegroundUnchecked}" /> <Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundUnchecked}" />
</Style> </Style>
<Style Selector="CheckBox /template/ Border#PART_Border"> <Style Selector="CheckBox">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUnchecked}" /> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundUnchecked}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUnchecked}" /> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushUnchecked}" />
</Style> </Style>
@ -133,11 +132,11 @@
<!-- Checked Normal State --> <!-- Checked Normal State -->
<Style Selector="CheckBox:checked /template/ ContentPresenter#ContentPresenter"> <Style Selector="CheckBox:checked">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CheckBoxForegroundChecked}" /> <Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundChecked}" />
</Style> </Style>
<Style Selector="CheckBox:checked /template/ Border#PART_Border"> <Style Selector="CheckBox:checked">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundChecked}" /> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundChecked}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushChecked}" /> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushChecked}" />
</Style> </Style>
@ -213,11 +212,11 @@
<!-- Indeterminate Normal State --> <!-- Indeterminate Normal State -->
<Style Selector="CheckBox:indeterminate /template/ ContentPresenter#ContentPresenter"> <Style Selector="CheckBox:indeterminate">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminate}" /> <Setter Property="Foreground" Value="{DynamicResource CheckBoxForegroundIndeterminate}" />
</Style> </Style>
<Style Selector="CheckBox:indeterminate /template/ Border#PART_Border"> <Style Selector="CheckBox:indeterminate">
<Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminate}" /> <Setter Property="Background" Value="{DynamicResource CheckBoxBackgroundIndeterminate}" />
<Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminate}" /> <Setter Property="BorderBrush" Value="{DynamicResource CheckBoxBorderBrushIndeterminate}" />
</Style> </Style>

3
src/Avalonia.Themes.Fluent/ComboBox.xaml

@ -133,7 +133,8 @@
Padding="{DynamicResource ComboBoxDropdownBorderPadding}" Padding="{DynamicResource ComboBoxDropdownBorderPadding}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
CornerRadius="{DynamicResource OverlayCornerRadius}"> CornerRadius="{DynamicResource OverlayCornerRadius}">
<ScrollViewer> <ScrollViewer HorizontalScrollBarVisibility="{TemplateBinding ScrollViewer.HorizontalScrollBarVisibility}"
VerticalScrollBarVisibility="{TemplateBinding ScrollViewer.VerticalScrollBarVisibility}">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
Margin="{DynamicResource ComboBoxDropdownContentMargin}" Margin="{DynamicResource ComboBoxDropdownContentMargin}"

2
src/Avalonia.Themes.Fluent/ContextMenu.xaml

@ -49,7 +49,7 @@
MinHeight="{TemplateBinding MinHeight}" MinHeight="{TemplateBinding MinHeight}"
HorizontalAlignment="{TemplateBinding HorizontalAlignment}" HorizontalAlignment="{TemplateBinding HorizontalAlignment}"
CornerRadius="{DynamicResource OverlayCornerRadius}"> CornerRadius="{DynamicResource OverlayCornerRadius}">
<ScrollViewer> <ScrollViewer Classes="menuscroller">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"

85
src/Avalonia.Themes.Fluent/ListBoxItem.xaml

@ -1,9 +1,13 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Design.PreviewWith> <Design.PreviewWith>
<Border Padding="0"> <Border Padding="0">
<ListBox> <ListBox>
<ListBoxItem IsEnabled="False">Disabled</ListBoxItem> <ListBoxItem IsEnabled="False">Disabled</ListBoxItem>
<ListBoxItem>Test</ListBoxItem> <ListBoxItem Background="#66000000"
Padding="20">
Test
</ListBoxItem>
<ListBoxItem>Test</ListBoxItem> <ListBoxItem>Test</ListBoxItem>
</ListBox> </ListBox>
</Border> </Border>
@ -17,11 +21,7 @@
<Setter Property="HorizontalContentAlignment" Value="Left" /> <Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Border x:Name="LayoutRoot" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}"> <ContentPresenter Name="PART_ContentPresenter"
<Panel>
<Rectangle x:Name="PressedBackground" IsHitTestVisible="False" />
<ContentPresenter Name="PART_ContentPresenter"
Background="{TemplateBinding Background}" Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}" BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}" BorderThickness="{TemplateBinding BorderThickness}"
@ -29,69 +29,66 @@
Content="{TemplateBinding Content}" Content="{TemplateBinding Content}"
Padding="{TemplateBinding Padding}" Padding="{TemplateBinding Padding}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"/> HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}" />
</Panel>
</Border>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>
</Style> </Style>
<Style Selector="ListBoxItem /template/ ContentPresenter"> <Style Selector="ListBoxItem /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.FontWeight" Value="Normal" /> <Setter Property="TextBlock.FontWeight" Value="Normal" />
<Setter Property="TextBlock.FontSize" Value="{DynamicResource ControlContentThemeFontSize}" /> <Setter Property="TextBlock.FontSize" Value="{DynamicResource ControlContentThemeFontSize}" />
</Style> </Style>
<!-- Disabled State --> <!-- Disabled State -->
<Style Selector="ListBoxItem:disabled /template/ ContentPresenter"> <Style Selector="ListBoxItem:disabled /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlDisabledBaseMediumLowBrush}"/> <Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlDisabledBaseMediumLowBrush}" />
</Style> </Style>
<!-- PointerOver State --> <!-- PointerOver State -->
<Style Selector="ListBoxItem:pointerover /template/ Rectangle#PressedBackground"> <Style Selector="ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Fill" Value="{DynamicResource SystemControlHighlightListLowBrush}"/> <Setter Property="Background" Value="{DynamicResource SystemControlHighlightListLowBrush}" />
</Style> </Style>
<Style Selector="ListBoxItem:pointerover /template/ ContentPresenter"> <Style Selector="ListBoxItem:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}"/> <Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style> </Style>
<!-- Pressed State --> <!-- Pressed State -->
<Style Selector="ListBoxItem:pressed /template/ Rectangle#PressedBackground"> <Style Selector="ListBoxItem:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Fill" Value="{DynamicResource SystemControlHighlightListMediumBrush}"/> <Setter Property="Background" Value="{DynamicResource SystemControlHighlightListMediumBrush}" />
</Style> </Style>
<Style Selector="ListBoxItem:pressed /template/ ContentPresenter"> <Style Selector="ListBoxItem:pressed /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}"/> <Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style> </Style>
<!-- Selected State --> <!-- Selected State -->
<Style Selector="ListBoxItem:selected /template/ Rectangle#PressedBackground"> <Style Selector="ListBoxItem:selected /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Fill" Value="{DynamicResource SystemControlHighlightListAccentLowBrush}"/> <Setter Property="Background" Value="{DynamicResource SystemControlHighlightListAccentLowBrush}" />
</Style> </Style>
<Style Selector="ListBoxItem:selected /template/ ContentPresenter"> <Style Selector="ListBoxItem:selected /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}"/> <Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style> </Style>
<!-- Selected Unfocused State --> <!-- Selected Unfocused State -->
<Style Selector="ListBoxItem:selected:not(:focus) /template/ Rectangle#PressedBackground"> <Style Selector="ListBoxItem:selected:not(:focus) /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Fill" Value="{DynamicResource SystemControlHighlightListAccentLowBrush}"/> <Setter Property="Background" Value="{DynamicResource SystemControlHighlightListAccentLowBrush}" />
</Style> </Style>
<Style Selector="ListBoxItem:selected:not(:focus) /template/ ContentPresenter"> <Style Selector="ListBoxItem:selected:not(:focus) /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}"/> <Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style> </Style>
<!-- Selected PointerOver State --> <!-- Selected PointerOver State -->
<Style Selector="ListBoxItem:selected:pointerover /template/ Rectangle#PressedBackground"> <Style Selector="ListBoxItem:selected:pointerover /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Fill" Value="{DynamicResource SystemControlHighlightListAccentMediumBrush}"/> <Setter Property="Background" Value="{DynamicResource SystemControlHighlightListAccentMediumBrush}" />
</Style> </Style>
<Style Selector="ListBoxItem:selected:pointerover /template/ ContentPresenter"> <Style Selector="ListBoxItem:selected:pointerover /template/ ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}"/> <Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style> </Style>
<!-- Selected Pressed State --> <!-- Selected Pressed State -->
<Style Selector="ListBoxItem:selected:pressed /template/ Rectangle#PressedBackground"> <Style Selector="ListBoxItem:selected:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="Fill" Value="{DynamicResource SystemControlHighlightListAccentHighBrush}"/> <Setter Property="Background" Value="{DynamicResource SystemControlHighlightListAccentHighBrush}" />
</Style> </Style>
<Style Selector="ListBoxItem:selected:pressed /template/ ContentPresenter"> <Style Selector="ListBoxItem:selected:pressed /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}"/> <Setter Property="TextBlock.Foreground" Value="{DynamicResource SystemControlHighlightAltBaseHighBrush}" />
</Style> </Style>
</Styles> </Styles>

4
src/Avalonia.Themes.Fluent/MenuItem.xaml

@ -123,7 +123,7 @@
MinHeight="{DynamicResource MenuFlyoutThemeMinHeight}" MinHeight="{DynamicResource MenuFlyoutThemeMinHeight}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
CornerRadius="{DynamicResource OverlayCornerRadius}"> CornerRadius="{DynamicResource OverlayCornerRadius}">
<ScrollViewer> <ScrollViewer Classes="menuscroller">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"
@ -171,7 +171,7 @@
MinHeight="{DynamicResource MenuFlyoutThemeMinHeight}" MinHeight="{DynamicResource MenuFlyoutThemeMinHeight}"
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
CornerRadius="{DynamicResource OverlayCornerRadius}"> CornerRadius="{DynamicResource OverlayCornerRadius}">
<ScrollViewer> <ScrollViewer Classes="menuscroller">
<ItemsPresenter Name="PART_ItemsPresenter" <ItemsPresenter Name="PART_ItemsPresenter"
Items="{TemplateBinding Items}" Items="{TemplateBinding Items}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"

7
src/Avalonia.Themes.Fluent/PathIcon.xaml

@ -10,14 +10,15 @@
</Design.PreviewWith> </Design.PreviewWith>
<Style Selector="PathIcon"> <Style Selector="PathIcon">
<Setter Property="Foreground" Value="{DynamicResource TextControlForeground}" /> <Setter Property="Foreground" Value="{DynamicResource TextControlForeground}" />
<Setter Property="Height" Value="16" /> <Setter Property="Height" Value="{DynamicResource IconElementThemeHeight}" />
<Setter Property="Width" Value="16" /> <Setter Property="Width" Value="{DynamicResource IconElementThemeWidth}" />
<Setter Property="Template"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Viewbox Height="{TemplateBinding Height}" <Viewbox Height="{TemplateBinding Height}"
Width="{TemplateBinding Width}"> Width="{TemplateBinding Width}">
<Path Fill="{TemplateBinding Foreground}" <Path Fill="{TemplateBinding Foreground}"
Data="{TemplateBinding Data}" /> Data="{TemplateBinding Data}"
Stretch="Uniform" />
</Viewbox> </Viewbox>
</ControlTemplate> </ControlTemplate>
</Setter> </Setter>

92
src/Avalonia.Themes.Fluent/ScrollViewer.xaml

@ -1,5 +1,21 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> <Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:converters="clr-namespace:Avalonia.Controls.Converters;assembly=Avalonia.Controls">
<Design.PreviewWith>
<Grid ColumnDefinitions="*,20,*"
Height="300"
Width="200"
Margin="20">
<ScrollViewer>
<Border Height="1000" Background="#ccc"/>
</ScrollViewer>
<ScrollViewer Grid.Column="2" Classes="menuscroller">
<Border Height="1000" Background="#ccc"/>
</ScrollViewer>
</Grid>
</Design.PreviewWith>
<Style Selector="ScrollViewer"> <Style Selector="ScrollViewer">
<Setter Property="Background" <Setter Property="Background"
Value="Transparent" /> Value="Transparent" />
@ -66,4 +82,78 @@
<Setter Property="Opacity" Value="1" /> <Setter Property="Opacity" Value="1" />
</Style> </Style>
<Style Selector="ScrollViewer.menuscroller">
<Setter Property="Template">
<ControlTemplate>
<DockPanel>
<RepeatButton DockPanel.Dock="Top"
BorderThickness="0"
Background="Transparent"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
RenderTransform="{x:Null}"
Command="{Binding LineUp, RelativeSource={RelativeSource TemplatedParent}}">
<RepeatButton.IsVisible>
<MultiBinding Converter="{x:Static converters:MenuScrollingVisibilityConverter.Instance}"
ConverterParameter="0">
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="VerticalScrollBarVisibility"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Offset.Y"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Extent.Height"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Viewport.Height"/>
</MultiBinding>
</RepeatButton.IsVisible>
<Viewbox Width="{DynamicResource ScrollBarButtonArrowIconFontSize}"
Height="{DynamicResource ScrollBarButtonArrowIconFontSize}">
<Path VerticalAlignment="Center"
HorizontalAlignment="Center"
Data="M 19.091797 14.970703 L 10 5.888672 L 0.908203 14.970703 L 0.029297 14.091797 L 10 4.111328 L 19.970703 14.091797 Z"
Width="20"
Height="20" />
</Viewbox>
</RepeatButton>
<RepeatButton DockPanel.Dock="Bottom"
BorderThickness="0"
Background="Transparent"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Center"
RenderTransform="{x:Null}"
Command="{Binding LineDown, RelativeSource={RelativeSource TemplatedParent}}">
<RepeatButton.IsVisible>
<MultiBinding Converter="{x:Static converters:MenuScrollingVisibilityConverter.Instance}"
ConverterParameter="100">
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="VerticalScrollBarVisibility"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Offset.Y"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Extent.Height"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Viewport.Height"/>
</MultiBinding>
</RepeatButton.IsVisible>
<Viewbox Width="{DynamicResource ScrollBarButtonArrowIconFontSize}"
Height="{DynamicResource ScrollBarButtonArrowIconFontSize}">
<Path VerticalAlignment="Center"
HorizontalAlignment="Center"
Data="M 18.935547 4.560547 L 19.814453 5.439453 L 10 15.253906 L 0.185547 5.439453 L 1.064453 4.560547 L 10 13.496094 Z"
Width="20"
Height="20" />
</Viewbox>
</RepeatButton>
<ScrollContentPresenter Name="PART_ContentPresenter"
CanHorizontallyScroll="{TemplateBinding CanHorizontallyScroll}"
CanVerticallyScroll="{TemplateBinding CanVerticallyScroll}"
Content="{TemplateBinding Content}"
Extent="{TemplateBinding Extent, Mode=TwoWay}"
Margin="{TemplateBinding Padding}"
Offset="{TemplateBinding Offset, Mode=TwoWay}"
Viewport="{TemplateBinding Viewport, Mode=TwoWay}"/>
</DockPanel>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="ScrollViewer.menuscroller /template/ RepeatButton > Viewbox > Path">
<Setter Property="Fill" Value="{DynamicResource ScrollBarButtonArrowForeground}" />
</Style>
<Style Selector="ScrollViewer.menuscroller /template/ RepeatButton:pointerover > Viewbox > Path">
<Setter Property="Fill" Value="{DynamicResource ScrollBarButtonArrowForegroundPointerOver}" />
</Style>
</Styles> </Styles>

20
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -1,15 +1,33 @@
Compat issues with assembly Avalonia.Visuals: Compat issues with assembly Avalonia.Visuals:
MembersMustExist : Member 'public void Avalonia.Media.DrawingContext.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.GetOrAddTypeface(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.GetOrAddTypeface(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.MatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.Typeface Avalonia.Media.FontManager.MatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.GlyphRun.Bounds.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<Avalonia.Point> Avalonia.StyledProperty<Avalonia.Point> Avalonia.Media.GlyphRunDrawing.BaselineOriginProperty' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Point Avalonia.Media.GlyphRunDrawing.BaselineOrigin.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.GlyphRunDrawing.BaselineOrigin.set(Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
CannotSealType : Type 'Avalonia.Media.Typeface' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract. CannotSealType : Type 'Avalonia.Media.Typeface' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
TypeCannotChangeClassification : Type 'Avalonia.Media.Typeface' is a 'struct' in the implementation but is a 'class' in the contract. TypeCannotChangeClassification : Type 'Avalonia.Media.Typeface' is a 'struct' in the implementation but is a 'class' in the contract.
CannotMakeMemberNonVirtual : Member 'public System.Boolean Avalonia.Media.Typeface.Equals(System.Object)' is non-virtual in the implementation but is virtual in the contract. CannotMakeMemberNonVirtual : Member 'public System.Boolean Avalonia.Media.Typeface.Equals(System.Object)' is non-virtual in the implementation but is virtual in the contract.
CannotMakeMemberNonVirtual : Member 'public System.Int32 Avalonia.Media.Typeface.GetHashCode()' is non-virtual in the implementation but is virtual in the contract. CannotMakeMemberNonVirtual : Member 'public System.Int32 Avalonia.Media.Typeface.GetHashCode()' is non-virtual in the implementation but is virtual in the contract.
TypesMustExist : Type 'Avalonia.Media.Fonts.FontKey' does not exist in the implementation but it does exist in the contract. TypesMustExist : Type 'Avalonia.Media.Fonts.FontKey' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Size Avalonia.Media.TextFormatting.DrawableTextRun.Size' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.TextFormatting.DrawableTextRun.Bounds.get()' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Size Avalonia.Media.TextFormatting.DrawableTextRun.Size.get()' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public Avalonia.Rect Avalonia.Media.TextFormatting.ShapedTextCharacters.Bounds.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.ShapedTextCharacters.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLayout.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak' is abstract in the implementation but is missing in the contract. CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak' is abstract in the implementation but is missing in the contract.
CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext)' is abstract in the implementation but is missing in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.TextLine.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.LineBreak.get()' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract. CannotAddAbstractMembers : Member 'public Avalonia.Media.TextFormatting.TextLineBreak Avalonia.Media.TextFormatting.TextLine.TextLineBreak.get()' is abstract in the implementation but is missing in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Platform.IDrawingContextImpl.DrawGlyphRun(Avalonia.Media.IBrush, Avalonia.Media.GlyphRun, Avalonia.Point)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' is present in the contract but not in the implementation. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Fonts.FontKey)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Boolean Avalonia.Platform.IFontManagerImpl.TryMatchCharacter(System.Int32, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight, Avalonia.Media.FontFamily, System.Globalization.CultureInfo, Avalonia.Media.Typeface)' is present in the implementation but not in the contract.
Total Issues: 13 Total Issues: 31

5
src/Avalonia.Visuals/Media/DrawingContext.cs

@ -206,14 +206,13 @@ namespace Avalonia.Media
/// </summary> /// </summary>
/// <param name="foreground">The foreground brush.</param> /// <param name="foreground">The foreground brush.</param>
/// <param name="glyphRun">The glyph run.</param> /// <param name="glyphRun">The glyph run.</param>
/// <param name="baselineOrigin">The baseline origin of the glyph run.</param> public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin)
{ {
Contract.Requires<ArgumentNullException>(glyphRun != null); Contract.Requires<ArgumentNullException>(glyphRun != null);
if (foreground != null) if (foreground != null)
{ {
PlatformImpl.DrawGlyphRun(foreground, glyphRun, baselineOrigin); PlatformImpl.DrawGlyphRun(foreground, glyphRun);
} }
} }

49
src/Avalonia.Visuals/Media/GlyphRun.cs

@ -16,8 +16,9 @@ namespace Avalonia.Media
private IGlyphRunImpl _glyphRunImpl; private IGlyphRunImpl _glyphRunImpl;
private GlyphTypeface _glyphTypeface; private GlyphTypeface _glyphTypeface;
private double _fontRenderingEmSize; private double _fontRenderingEmSize;
private Rect? _bounds; private Size? _size;
private int _biDiLevel; private int _biDiLevel;
private Point? _baselineOrigin;
private ReadOnlySlice<ushort> _glyphIndices; private ReadOnlySlice<ushort> _glyphIndices;
private ReadOnlySlice<double> _glyphAdvances; private ReadOnlySlice<double> _glyphAdvances;
@ -89,6 +90,20 @@ namespace Avalonia.Media
set => Set(ref _fontRenderingEmSize, value); set => Set(ref _fontRenderingEmSize, value);
} }
/// <summary>
/// Gets or sets the baseline origin of the<see cref="GlyphRun"/>.
/// </summary>
public Point BaselineOrigin
{
get
{
_baselineOrigin ??= CalculateBaselineOrigin();
return _baselineOrigin.Value;
}
set => Set(ref _baselineOrigin, value);
}
/// <summary> /// <summary>
/// Gets or sets an array of <see cref="ushort"/> values that represent the glyph indices in the rendering physical font. /// Gets or sets an array of <see cref="ushort"/> values that represent the glyph indices in the rendering physical font.
/// </summary> /// </summary>
@ -156,16 +171,13 @@ namespace Avalonia.Media
/// <summary> /// <summary>
/// Gets or sets the conservative bounding box of the <see cref="GlyphRun"/>. /// Gets or sets the conservative bounding box of the <see cref="GlyphRun"/>.
/// </summary> /// </summary>
public Rect Bounds public Size Size
{ {
get get
{ {
if (_bounds == null) _size ??= CalculateSize();
{
_bounds = CalculateBounds();
}
return _bounds.Value; return _size.Value;
} }
} }
@ -200,7 +212,7 @@ namespace Avalonia.Media
if (characterHit.FirstCharacterIndex + characterHit.TrailingLength > Characters.End) if (characterHit.FirstCharacterIndex + characterHit.TrailingLength > Characters.End)
{ {
return Bounds.Width; return Size.Width;
} }
var glyphIndex = FindGlyphIndex(characterHit.FirstCharacterIndex); var glyphIndex = FindGlyphIndex(characterHit.FirstCharacterIndex);
@ -257,7 +269,7 @@ namespace Avalonia.Media
} }
//After //After
if (distance > Bounds.Size.Width) if (distance > Size.Width)
{ {
isInside = false; isInside = false;
@ -529,12 +541,21 @@ namespace Avalonia.Media
} }
/// <summary> /// <summary>
/// Calculates the bounds of the <see cref="GlyphRun"/>. /// Calculates the default baseline origin of the <see cref="GlyphRun"/>.
/// </summary>
/// <returns>The baseline origin.</returns>
private Point CalculateBaselineOrigin()
{
return new Point(0, -GlyphTypeface.Ascent * Scale);
}
/// <summary>
/// Calculates the size of the <see cref="GlyphRun"/>.
/// </summary> /// </summary>
/// <returns> /// <returns>
/// The calculated bounds. /// The calculated bounds.
/// </returns> /// </returns>
private Rect CalculateBounds() private Size CalculateSize()
{ {
var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
@ -555,7 +576,7 @@ namespace Avalonia.Media
} }
} }
return new Rect(0, GlyphTypeface.Ascent * Scale, width, height); return new Size(width, height);
} }
private void Set<T>(ref T field, T value) private void Set<T>(ref T field, T value)
@ -590,11 +611,15 @@ namespace Avalonia.Media
throw new InvalidOperationException(); throw new InvalidOperationException();
} }
_baselineOrigin = new Point(0, -GlyphTypeface.Ascent * Scale);
var platformRenderInterface = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>(); var platformRenderInterface = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>();
_glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width); _glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width);
var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale; var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
_size = new Size(width, height);
} }
void IDisposable.Dispose() void IDisposable.Dispose()

13
src/Avalonia.Visuals/Media/GlyphRunDrawing.cs

@ -8,9 +8,6 @@
public static readonly StyledProperty<GlyphRun> GlyphRunProperty = public static readonly StyledProperty<GlyphRun> GlyphRunProperty =
AvaloniaProperty.Register<GlyphRunDrawing, GlyphRun>(nameof(GlyphRun)); AvaloniaProperty.Register<GlyphRunDrawing, GlyphRun>(nameof(GlyphRun));
public static readonly StyledProperty<Point> BaselineOriginProperty =
AvaloniaProperty.Register<GlyphRunDrawing, Point>(nameof(BaselineOrigin));
public IBrush Foreground public IBrush Foreground
{ {
get => GetValue(ForegroundProperty); get => GetValue(ForegroundProperty);
@ -23,12 +20,6 @@
set => SetValue(GlyphRunProperty, value); set => SetValue(GlyphRunProperty, value);
} }
public Point BaselineOrigin
{
get => GetValue(BaselineOriginProperty);
set => SetValue(BaselineOriginProperty, value);
}
public override void Draw(DrawingContext context) public override void Draw(DrawingContext context)
{ {
if (GlyphRun == null) if (GlyphRun == null)
@ -36,12 +27,12 @@
return; return;
} }
context.DrawGlyphRun(Foreground, GlyphRun, BaselineOrigin); context.DrawGlyphRun(Foreground, GlyphRun);
} }
public override Rect GetBounds() public override Rect GetBounds()
{ {
return GlyphRun?.Bounds ?? default; return GlyphRun != null ? new Rect(GlyphRun.Size) : Rect.Empty;
} }
} }
} }

17
src/Avalonia.Visuals/Media/TextDecoration.cs

@ -155,8 +155,7 @@ namespace Avalonia.Media
/// </summary> /// </summary>
/// <param name="drawingContext">The drawing context.</param> /// <param name="drawingContext">The drawing context.</param>
/// <param name="shapedTextCharacters">The shaped characters that are decorated.</param> /// <param name="shapedTextCharacters">The shaped characters that are decorated.</param>
/// <param name="origin">The origin.</param> internal void Draw(DrawingContext drawingContext, ShapedTextCharacters shapedTextCharacters)
internal void Draw(DrawingContext drawingContext, ShapedTextCharacters shapedTextCharacters, Point origin)
{ {
var fontRenderingEmSize = shapedTextCharacters.Properties.FontRenderingEmSize; var fontRenderingEmSize = shapedTextCharacters.Properties.FontRenderingEmSize;
var fontMetrics = shapedTextCharacters.FontMetrics; var fontMetrics = shapedTextCharacters.FontMetrics;
@ -181,16 +180,20 @@ namespace Avalonia.Media
break; break;
} }
var origin = new Point();
switch (Location) switch (Location)
{ {
case TextDecorationLocation.Overline: case TextDecorationLocation.Baseline:
origin += new Point(0, fontMetrics.Ascent); origin += shapedTextCharacters.GlyphRun.BaselineOrigin;
break; break;
case TextDecorationLocation.Strikethrough: case TextDecorationLocation.Strikethrough:
origin += new Point(0, -fontMetrics.StrikethroughPosition); origin += new Point(shapedTextCharacters.GlyphRun.BaselineOrigin.X,
shapedTextCharacters.GlyphRun.BaselineOrigin.Y - fontMetrics.StrikethroughPosition);
break; break;
case TextDecorationLocation.Underline: case TextDecorationLocation.Underline:
origin += new Point(0, -fontMetrics.UnderlinePosition); origin += new Point(shapedTextCharacters.GlyphRun.BaselineOrigin.X,
shapedTextCharacters.GlyphRun.BaselineOrigin.Y - fontMetrics.UnderlinePosition);
break; break;
} }
@ -207,7 +210,7 @@ namespace Avalonia.Media
var pen = new Pen(Stroke ?? shapedTextCharacters.Properties.ForegroundBrush, thickness, var pen = new Pen(Stroke ?? shapedTextCharacters.Properties.ForegroundBrush, thickness,
new DashStyle(StrokeDashArray, StrokeDashOffset), StrokeLineCap); new DashStyle(StrokeDashArray, StrokeDashOffset), StrokeLineCap);
drawingContext.DrawLine(pen, origin, origin + new Point(shapedTextCharacters.Bounds.Width, 0)); drawingContext.DrawLine(pen, origin, origin + new Point(shapedTextCharacters.Size.Width, 0));
} }
} }
} }

7
src/Avalonia.Visuals/Media/TextFormatting/DrawableTextRun.cs

@ -6,15 +6,14 @@
public abstract class DrawableTextRun : TextRun public abstract class DrawableTextRun : TextRun
{ {
/// <summary> /// <summary>
/// Gets the bounds. /// Gets the size.
/// </summary> /// </summary>
public abstract Rect Bounds { get; } public abstract Size Size { get; }
/// <summary> /// <summary>
/// Draws the <see cref="DrawableTextRun"/> at the given origin. /// Draws the <see cref="DrawableTextRun"/> at the given origin.
/// </summary> /// </summary>
/// <param name="drawingContext">The drawing context.</param> /// <param name="drawingContext">The drawing context.</param>
/// <param name="origin">The origin.</param> public abstract void Draw(DrawingContext drawingContext);
public abstract void Draw(DrawingContext drawingContext, Point origin);
} }
} }

11
src/Avalonia.Visuals/Media/TextFormatting/ShapedTextCharacters.cs

@ -26,7 +26,7 @@ namespace Avalonia.Media.TextFormatting
public override int TextSourceLength { get; } public override int TextSourceLength { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override Rect Bounds => GlyphRun.Bounds; public override Size Size => GlyphRun.Size;
/// <summary> /// <summary>
/// Gets the font metrics. /// Gets the font metrics.
@ -45,7 +45,7 @@ namespace Avalonia.Media.TextFormatting
public GlyphRun GlyphRun { get; } public GlyphRun GlyphRun { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override void Draw(DrawingContext drawingContext, Point origin) public override void Draw(DrawingContext drawingContext)
{ {
if (GlyphRun.GlyphIndices.Length == 0) if (GlyphRun.GlyphIndices.Length == 0)
{ {
@ -64,11 +64,10 @@ namespace Avalonia.Media.TextFormatting
if (Properties.BackgroundBrush != null) if (Properties.BackgroundBrush != null)
{ {
drawingContext.DrawRectangle(Properties.BackgroundBrush, null, drawingContext.DrawRectangle(Properties.BackgroundBrush, null, new Rect(Size));
new Rect(origin.X, origin.Y + FontMetrics.Ascent, Bounds.Width, Bounds.Height));
} }
drawingContext.DrawGlyphRun(Properties.ForegroundBrush, GlyphRun, origin); drawingContext.DrawGlyphRun(Properties.ForegroundBrush, GlyphRun);
if (Properties.TextDecorations == null) if (Properties.TextDecorations == null)
{ {
@ -77,7 +76,7 @@ namespace Avalonia.Media.TextFormatting
foreach (var textDecoration in Properties.TextDecorations) foreach (var textDecoration in Properties.TextDecorations)
{ {
textDecoration.Draw(drawingContext, this, origin); textDecoration.Draw(drawingContext, this);
} }
} }

6
src/Avalonia.Visuals/Media/TextFormatting/TextFormatterImpl.cs

@ -52,7 +52,7 @@ namespace Avalonia.Media.TextFormatting
{ {
var glyphRun = textCharacters.GlyphRun; var glyphRun = textCharacters.GlyphRun;
if (glyphRun.Bounds.Width < availableWidth) if (glyphRun.Size.Width < availableWidth)
{ {
return glyphRun.Characters.Length; return glyphRun.Characters.Length;
} }
@ -348,7 +348,7 @@ namespace Avalonia.Media.TextFormatting
{ {
var currentRun = textRuns[runIndex]; var currentRun = textRuns[runIndex];
if (currentWidth + currentRun.GlyphRun.Bounds.Width > availableWidth) if (currentWidth + currentRun.Size.Width > availableWidth)
{ {
var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth); var measuredLength = MeasureCharacters(currentRun, paragraphWidth - currentWidth);
@ -421,7 +421,7 @@ namespace Avalonia.Media.TextFormatting
return new TextLineImpl(splitResult.First, textLineMetrics, lineBreak); return new TextLineImpl(splitResult.First, textLineMetrics, lineBreak);
} }
currentWidth += currentRun.GlyphRun.Bounds.Width; currentWidth += currentRun.Size.Width;
currentLength += currentRun.GlyphRun.Characters.Length; currentLength += currentRun.GlyphRun.Characters.Length;

10
src/Avalonia.Visuals/Media/TextFormatting/TextLayout.cs

@ -115,22 +115,24 @@ namespace Avalonia.Media.TextFormatting
/// Draws the text layout. /// Draws the text layout.
/// </summary> /// </summary>
/// <param name="context">The drawing context.</param> /// <param name="context">The drawing context.</param>
/// <param name="origin">The origin.</param> public void Draw(DrawingContext context)
public void Draw(DrawingContext context, Point origin)
{ {
if (!TextLines.Any()) if (!TextLines.Any())
{ {
return; return;
} }
var currentY = origin.Y; var currentY = 0.0;
foreach (var textLine in TextLines) foreach (var textLine in TextLines)
{ {
var offsetX = TextLine.GetParagraphOffsetX(textLine.LineMetrics.Size.Width, Size.Width, var offsetX = TextLine.GetParagraphOffsetX(textLine.LineMetrics.Size.Width, Size.Width,
_paragraphProperties.TextAlignment); _paragraphProperties.TextAlignment);
textLine.Draw(context, new Point(origin.X + offsetX, currentY)); using (context.PushPostTransform(Matrix.CreateTranslation(offsetX, currentY)))
{
textLine.Draw(context);
}
currentY += textLine.LineMetrics.Size.Height; currentY += textLine.LineMetrics.Size.Height;
} }

3
src/Avalonia.Visuals/Media/TextFormatting/TextLine.cs

@ -51,8 +51,7 @@ namespace Avalonia.Media.TextFormatting
/// Draws the <see cref="TextLine"/> at the given origin. /// Draws the <see cref="TextLine"/> at the given origin.
/// </summary> /// </summary>
/// <param name="drawingContext">The drawing context.</param> /// <param name="drawingContext">The drawing context.</param>
/// <param name="origin">The origin.</param> public abstract void Draw(DrawingContext drawingContext);
public abstract void Draw(DrawingContext drawingContext, Point origin);
/// <summary> /// <summary>
/// Create a collapsed line based on collapsed text properties. /// Create a collapsed line based on collapsed text properties.

29
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@ -33,17 +33,18 @@ namespace Avalonia.Media.TextFormatting
public override bool HasCollapsed { get; } public override bool HasCollapsed { get; }
/// <inheritdoc/> /// <inheritdoc/>
public override void Draw(DrawingContext drawingContext, Point origin) public override void Draw(DrawingContext drawingContext)
{ {
var currentX = origin.X; var currentX = 0.0;
foreach (var textRun in _textRuns) foreach (var textRun in _textRuns)
{ {
var baselineOrigin = new Point(currentX, origin.Y + LineMetrics.TextBaseline); using (drawingContext.PushPostTransform(Matrix.CreateTranslation(currentX, 0)))
{
textRun.Draw(drawingContext, baselineOrigin); textRun.Draw(drawingContext);
}
currentX += textRun.Bounds.Width; currentX += textRun.Size.Width;
} }
} }
@ -64,13 +65,13 @@ namespace Avalonia.Media.TextFormatting
var shapedSymbol = CreateShapedSymbol(collapsingProperties.Symbol); var shapedSymbol = CreateShapedSymbol(collapsingProperties.Symbol);
var availableWidth = collapsingProperties.Width - shapedSymbol.Bounds.Width; var availableWidth = collapsingProperties.Width - shapedSymbol.Size.Width;
while (runIndex < _textRuns.Count) while (runIndex < _textRuns.Count)
{ {
var currentRun = _textRuns[runIndex]; var currentRun = _textRuns[runIndex];
currentWidth += currentRun.GlyphRun.Bounds.Width; currentWidth += currentRun.Size.Width;
if (currentWidth > availableWidth) if (currentWidth > availableWidth)
{ {
@ -125,7 +126,7 @@ namespace Avalonia.Media.TextFormatting
return new TextLineImpl(shapedTextCharacters, textLineMetrics, TextLineBreak, true); return new TextLineImpl(shapedTextCharacters, textLineMetrics, TextLineBreak, true);
} }
availableWidth -= currentRun.GlyphRun.Bounds.Width; availableWidth -= currentRun.Size.Width;
collapsedLength += currentRun.GlyphRun.Characters.Length; collapsedLength += currentRun.GlyphRun.Characters.Length;
@ -133,7 +134,7 @@ namespace Avalonia.Media.TextFormatting
} }
textLineMetrics = textLineMetrics =
new TextLineMetrics(LineMetrics.Size.WithWidth(LineMetrics.Size.Width + shapedSymbol.Bounds.Width), new TextLineMetrics(LineMetrics.Size.WithWidth(LineMetrics.Size.Width + shapedSymbol.Size.Width),
LineMetrics.TextBaseline, TextRange, LineMetrics.HasOverflowed); LineMetrics.TextBaseline, TextRange, LineMetrics.HasOverflowed);
return new TextLineImpl(new List<ShapedTextCharacters>(_textRuns) { shapedSymbol }, textLineMetrics, null, return new TextLineImpl(new List<ShapedTextCharacters>(_textRuns) { shapedSymbol }, textLineMetrics, null,
@ -156,12 +157,12 @@ namespace Avalonia.Media.TextFormatting
{ {
characterHit = run.GlyphRun.GetCharacterHitFromDistance(distance, out _); characterHit = run.GlyphRun.GetCharacterHitFromDistance(distance, out _);
if (distance <= run.Bounds.Width) if (distance <= run.Size.Width)
{ {
break; break;
} }
distance -= run.Bounds.Width; distance -= run.Size.Width;
} }
return characterHit; return characterHit;
@ -229,7 +230,7 @@ namespace Avalonia.Media.TextFormatting
{ {
if (codepointIndex > textRun.Text.End) if (codepointIndex > textRun.Text.End)
{ {
currentDistance += textRun.Bounds.Width; currentDistance += textRun.Size.Width;
continue; continue;
} }
@ -405,7 +406,7 @@ namespace Avalonia.Media.TextFormatting
for (var i = 0; i < shapedTextCharacters.Count; i++) for (var i = 0; i < shapedTextCharacters.Count; i++)
{ {
shapedWidth += shapedTextCharacters[i].Bounds.Width; shapedWidth += shapedTextCharacters[i].Size.Width;
} }
return shapedWidth; return shapedWidth;

2
src/Avalonia.Visuals/Media/TextFormatting/TextLineMetrics.cs

@ -67,7 +67,7 @@ namespace Avalonia.Media.TextFormatting
var fontMetrics = var fontMetrics =
new FontMetrics(shapedRun.Properties.Typeface, shapedRun.Properties.FontRenderingEmSize); new FontMetrics(shapedRun.Properties.Typeface, shapedRun.Properties.FontRenderingEmSize);
lineWidth += shapedRun.Bounds.Width; lineWidth += shapedRun.Size.Width;
if (ascent > fontMetrics.Ascent) if (ascent > fontMetrics.Ascent)
{ {

3
src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs

@ -84,8 +84,7 @@ namespace Avalonia.Platform
/// </summary> /// </summary>
/// <param name="foreground">The foreground.</param> /// <param name="foreground">The foreground.</param>
/// <param name="glyphRun">The glyph run.</param> /// <param name="glyphRun">The glyph run.</param>
/// <param name="baselineOrigin">The baseline origin of the glyph run.</param> void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun);
void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin);
/// <summary> /// <summary>
/// Creates a new <see cref="IRenderTargetBitmapImpl"/> that can be used as a render layer /// Creates a new <see cref="IRenderTargetBitmapImpl"/> that can be used as a render layer

19
src/Avalonia.Visuals/Point.cs

@ -267,5 +267,24 @@ namespace Avalonia
{ {
return new Point(_x, y); return new Point(_x, y);
} }
/// <summary>
/// Deconstructs the point into its X and Y coordinates.
/// </summary>
/// <param name="x">The X coordinate.</param>
/// <param name="y">The Y coordinate.</param>
public void Deconstruct(out double x, out double y)
{
x = this._x;
y = this._y;
}
/// <summary>
/// Gets a value indicating whether the X and Y coordinates are zero.
/// </summary>
public bool IsDefault
{
get { return (_x == 0) && (_y == 0); }
}
} }
} }

4
src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs

@ -204,13 +204,13 @@ namespace Avalonia.Rendering.SceneGraph
} }
/// <inheritdoc/> /// <inheritdoc/>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin) public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{ {
var next = NextDrawAs<GlyphRunNode>(); var next = NextDrawAs<GlyphRunNode>();
if (next == null || !next.Item.Equals(Transform, foreground, glyphRun)) if (next == null || !next.Item.Equals(Transform, foreground, glyphRun))
{ {
Add(new GlyphRunNode(Transform, foreground, glyphRun, baselineOrigin, CreateChildScene(foreground))); Add(new GlyphRunNode(Transform, foreground, glyphRun, CreateChildScene(foreground)));
} }
else else

13
src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs

@ -1,6 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.VisualTree; using Avalonia.VisualTree;
@ -17,20 +18,17 @@ namespace Avalonia.Rendering.SceneGraph
/// <param name="transform">The transform.</param> /// <param name="transform">The transform.</param>
/// <param name="foreground">The foreground brush.</param> /// <param name="foreground">The foreground brush.</param>
/// <param name="glyphRun">The glyph run to draw.</param> /// <param name="glyphRun">The glyph run to draw.</param>
/// <param name="baselineOrigin">The baseline origin of the glyph run.</param>
/// <param name="childScenes">Child scenes for drawing visual brushes.</param> /// <param name="childScenes">Child scenes for drawing visual brushes.</param>
public GlyphRunNode( public GlyphRunNode(
Matrix transform, Matrix transform,
IBrush foreground, IBrush foreground,
GlyphRun glyphRun, GlyphRun glyphRun,
Point baselineOrigin,
IDictionary<IVisual, Scene> childScenes = null) IDictionary<IVisual, Scene> childScenes = null)
: base(glyphRun.Bounds.Translate(baselineOrigin), transform) : base(new Rect(glyphRun.Size), transform)
{ {
Transform = transform; Transform = transform;
Foreground = foreground?.ToImmutable(); Foreground = foreground?.ToImmutable();
GlyphRun = glyphRun; GlyphRun = glyphRun;
BaselineOrigin = baselineOrigin;
ChildScenes = childScenes; ChildScenes = childScenes;
} }
@ -49,11 +47,6 @@ namespace Avalonia.Rendering.SceneGraph
/// </summary> /// </summary>
public GlyphRun GlyphRun { get; } public GlyphRun GlyphRun { get; }
/// <summary>
/// Gets the baseline origin.
/// </summary>
public Point BaselineOrigin { get; set; }
/// <inheritdoc/> /// <inheritdoc/>
public override IDictionary<IVisual, Scene> ChildScenes { get; } public override IDictionary<IVisual, Scene> ChildScenes { get; }
@ -61,7 +54,7 @@ namespace Avalonia.Rendering.SceneGraph
public override void Render(IDrawingContextImpl context) public override void Render(IDrawingContextImpl context)
{ {
context.Transform = Transform; context.Transform = Transform;
context.DrawGlyphRun(Foreground, GlyphRun, BaselineOrigin); context.DrawGlyphRun(Foreground, GlyphRun);
} }
/// <summary> /// <summary>

19
src/Avalonia.Visuals/Size.cs

@ -276,5 +276,24 @@ namespace Avalonia
{ {
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", _width, _height); return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", _width, _height);
} }
/// <summary>
/// Deconstructs the size into its Width and Height values.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public void Deconstruct(out double width, out double height)
{
width = this._width;
height = this._height;
}
/// <summary>
/// Gets a value indicating whether the Width and Height values are zero.
/// </summary>
public bool IsDefault
{
get { return (_width == 0) && (_height == 0); }
}
} }
} }

23
src/Avalonia.Visuals/Thickness.cs

@ -272,5 +272,28 @@ namespace Avalonia
{ {
return $"{_left},{_top},{_right},{_bottom}"; return $"{_left},{_top},{_right},{_bottom}";
} }
/// <summary>
/// Deconstructor the thickness into its left, top, right and bottom thickness values.
/// </summary>
/// <param name="left">The thickness on the left.</param>
/// <param name="top">The thickness on the top.</param>
/// <param name="right">The thickness on the right.</param>
/// <param name="bottom">The thickness on the bottom.</param>
public void Deconstruct(out double left, out double top, out double right, out double bottom)
{
left = this._left;
top = this._top;
right = this._right;
bottom = this._bottom;
}
/// <summary>
/// Gets a value indicating whether the left, top, right and bottom thickness values are zero.
/// </summary>
public bool IsDefault
{
get { return (_left == 0) && (_top == 0) && (_right == 0) && (_bottom == 0); }
}
} }
} }

19
src/Avalonia.Visuals/Vector.cs

@ -333,5 +333,24 @@ namespace Avalonia
/// </summary> /// </summary>
public static Vector UnitY public static Vector UnitY
=> new Vector(0, 1); => new Vector(0, 1);
/// <summary>
/// Deconstructs the vector into its X and Y components.
/// </summary>
/// <param name="x">The X component.</param>
/// <param name="y">The Y component.</param>
public void Deconstruct(out double x, out double y)
{
x = this._x;
y = this._y;
}
/// <summary>
/// Gets a value indicating whether the X and Y components are zero.
/// </summary>
public bool IsDefault
{
get { return (_x == 0) && (_y == 0); }
}
} }
} }

8
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -401,16 +401,16 @@ namespace Avalonia.Skia
} }
/// <inheritdoc /> /// <inheritdoc />
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin) public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{ {
using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Bounds.Size)) using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size))
{ {
var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl; var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl;
ConfigureTextRendering(paintWrapper); ConfigureTextRendering(paintWrapper);
Canvas.DrawText(glyphRunImpl.TextBlob, (float)baselineOrigin.X, Canvas.DrawText(glyphRunImpl.TextBlob, (float)glyphRun.BaselineOrigin.X,
(float)baselineOrigin.Y, paintWrapper.Paint); (float)glyphRun.BaselineOrigin.Y, paintWrapper.Paint);
} }
} }

7
src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs

@ -324,13 +324,14 @@ namespace Avalonia.Direct2D1.Media
/// <param name="foreground">The foreground.</param> /// <param name="foreground">The foreground.</param>
/// <param name="glyphRun">The glyph run.</param> /// <param name="glyphRun">The glyph run.</param>
/// <param name="baselineOrigin"></param> /// <param name="baselineOrigin"></param>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun, Point baselineOrigin) public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{ {
using (var brush = CreateBrush(foreground, glyphRun.Bounds.Size)) using (var brush = CreateBrush(foreground, glyphRun.Size))
{ {
var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl; var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl;
_renderTarget.DrawGlyphRun(baselineOrigin.ToSharpDX(), glyphRunImpl.GlyphRun, brush.PlatformBrush, MeasuringMode.Natural); _renderTarget.DrawGlyphRun(glyphRun.BaselineOrigin.ToSharpDX(), glyphRunImpl.GlyphRun,
brush.PlatformBrush, MeasuringMode.Natural);
} }
} }

4
tests/Avalonia.Base.UnitTests/Data/Core/Plugins/DataAnnotationsValidationPluginTests.cs

@ -59,12 +59,12 @@ namespace Avalonia.Markup.UnitTests.Data.Plugins
{ {
new BindingNotification(5), new BindingNotification(5),
new BindingNotification( new BindingNotification(
new ValidationException(errmsg), new DataValidationException(errmsg),
BindingErrorType.DataValidationError, BindingErrorType.DataValidationError,
3), 3),
new BindingNotification(7), new BindingNotification(7),
new BindingNotification( new BindingNotification(
new ValidationException(errmsg), new DataValidationException(errmsg),
BindingErrorType.DataValidationError, BindingErrorType.DataValidationError,
11), 11),
}, result); }, result);

2
tests/Avalonia.Controls.UnitTests/ComboBoxTests.cs

@ -2,7 +2,6 @@ using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes; using Avalonia.Controls.Shapes;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Media; using Avalonia.Media;
using Avalonia.UnitTests; using Avalonia.UnitTests;
@ -27,6 +26,7 @@ namespace Avalonia.Controls.UnitTests
Assert.True(target.IsDropDownOpen); Assert.True(target.IsDropDownOpen);
_helper.Down(target); _helper.Down(target);
_helper.Up(target);
Assert.False(target.IsDropDownOpen); Assert.False(target.IsDropDownOpen);
} }

10
tests/Avalonia.Controls.UnitTests/Generators/ItemContainerGeneratorTests.cs

@ -144,6 +144,16 @@ namespace Avalonia.Controls.UnitTests.Generators
Assert.Equal("bar", container.Content); Assert.Equal("bar", container.Content);
} }
[Fact]
public void Materialize_Should_Create_Containers_When_Item_Is_Null()
{
var owner = new Decorator();
var target = new ItemContainerGenerator<ListBoxItem>(owner, ListBoxItem.ContentProperty, null);
var container = (ListBoxItem)target.Materialize(0, null).ContainerControl;
Assert.True(container != null, "The containers is not materialized.");
}
private IList<ItemContainerInfo> Materialize( private IList<ItemContainerInfo> Materialize(
IItemContainerGenerator generator, IItemContainerGenerator generator,
int index, int index,

45
tests/Avalonia.Controls.UnitTests/Primitives/PopupTests.cs

@ -21,7 +21,50 @@ namespace Avalonia.Controls.UnitTests.Primitives
public class PopupTests public class PopupTests
{ {
protected bool UsePopupHost; protected bool UsePopupHost;
[Fact]
public void Popup_Open_Without_Target_Should_Attach_Itself_Later()
{
using (CreateServices())
{
int openedEvent = 0;
var target = new Popup();
target.Opened += (s, a) => openedEvent++;
target.IsOpen = true;
var window = PreparedWindow(target);
window.Show();
Assert.Equal(1, openedEvent);
}
}
[Fact]
public void Popup_Without_TopLevel_Shouldnt_Call_Open()
{
int openedEvent = 0;
var target = new Popup();
target.Opened += (s, a) => openedEvent++;
target.IsOpen = true;
Assert.Equal(0, openedEvent);
}
[Fact]
public void Opening_Popup_Shouldnt_Throw_When_Not_In_Visual_Tree()
{
var target = new Popup();
target.IsOpen = true;
}
[Fact]
public void Opening_Popup_Shouldnt_Throw_When_In_Tree_Without_TopLevel()
{
Control c = new Control();
var target = new Popup();
((ISetLogicalParent)target).SetParent(c);
target.IsOpen = true;
}
[Fact] [Fact]
public void Setting_Child_Should_Set_Child_Controls_LogicalParent() public void Setting_Child_Should_Set_Child_Controls_LogicalParent()
{ {

2
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLayoutTests.cs

@ -369,7 +369,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var glyphRun = shapedRun.GlyphRun; var glyphRun = shapedRun.GlyphRun;
var width = glyphRun.Bounds.Width; var width = glyphRun.Size.Width;
var characterHit = glyphRun.GetCharacterHitFromDistance(width, out _); var characterHit = glyphRun.GetCharacterHitFromDistance(width, out _);

50
tests/Avalonia.Visuals.UnitTests/Media/GlyphRunTests.cs

@ -119,6 +119,56 @@ namespace Avalonia.Visuals.UnitTests.Media
} }
} }
[InlineData(new double[] { 10, 10, 10 }, new ushort[] { 0, 0, 0 }, 0)]
[InlineData(new double[] { 10, 10, 10 }, new ushort[] { 0, 0, 0 }, 1)]
[InlineData(new double[] { 10, 10, 10, 10 }, new ushort[] { 0, 0, 0, 3 }, 0)]
[InlineData(new double[] { 10, 10, 10, 10 }, new ushort[] { 3, 0, 0, 0 }, 1)]
[InlineData(new double[] { 10, 10, 10, 10, 10 }, new ushort[] { 0, 1, 1, 1, 4 }, 0)]
[InlineData(new double[] { 10, 10, 10, 10, 10 }, new ushort[] { 4, 1, 1, 1, 0 }, 1)]
[Theory]
public void Should_Find_Glyph_Index(double[] advances, ushort[] clusters, int bidiLevel)
{
using (var glyphRun = CreateGlyphRun(advances, clusters, bidiLevel))
{
if (glyphRun.IsLeftToRight)
{
for (var i = 0; i < clusters.Length; i++)
{
var cluster = clusters[i];
var found = glyphRun.FindGlyphIndex(cluster);
var expected = i;
while (expected - 1 >= 0 && clusters[expected - 1] == cluster)
{
expected--;
}
Assert.Equal(expected, found);
}
}
else
{
for (var i = clusters.Length - 1; i > 0; i--)
{
var cluster = clusters[i];
var found = glyphRun.FindGlyphIndex(cluster);
var expected = i;
while (expected + 1 < clusters.Length && clusters[expected + 1] == cluster)
{
expected++;
}
Assert.Equal(expected, found);
}
}
}
}
private static GlyphRun CreateGlyphRun(double[] glyphAdvances, ushort[] glyphClusters, int bidiLevel = 0) private static GlyphRun CreateGlyphRun(double[] glyphAdvances, ushort[] glyphClusters, int bidiLevel = 0)
{ {
var count = glyphAdvances.Length; var count = glyphAdvances.Length;

Loading…
Cancel
Save