Browse Source

Merge branch 'master' into fix-generateAvaloniaResources

pull/6569/head
Tako 5 years ago
committed by GitHub
parent
commit
b0a40a2340
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 51
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  2. 49
      src/Avalonia.Controls.DataGrid/DataGridCell.cs
  3. 11
      src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs
  4. 36
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  5. 15
      src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
  6. 17
      src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
  7. 15
      src/Avalonia.Controls/Design.cs
  8. 22
      src/Avalonia.Controls/Primitives/Popup.cs
  9. 4
      src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
  10. 33
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  11. 7
      src/Avalonia.Visuals/Visual.cs
  12. 3
      src/Avalonia.X11/X11Window.cs
  13. 23
      src/Markup/Avalonia.Markup.Xaml/Extensions.cs
  14. 18
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs
  15. 23
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs
  16. 47
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  17. 4
      src/Skia/Avalonia.Skia/FormattedTextImpl.cs
  18. 4
      src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs
  19. 32
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  20. 7
      src/Windows/Avalonia.Direct2D1/Media/LinearGradientBrushImpl.cs
  21. 11
      src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs
  22. 1
      tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj
  23. 73
      tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs
  24. 3
      tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs
  25. 12
      tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs
  26. 3
      tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs

51
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -3039,6 +3039,12 @@ namespace Avalonia.Controls
}
}
//TODO: Ensure right button is checked for
internal bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit)
{
KeyboardHelper.GetMetaKeyState(pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift);
return UpdateStateOnMouseRightButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl);
}
//TODO: Ensure left button is checked for
internal bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit)
{
@ -4489,17 +4495,27 @@ namespace Avalonia.Controls
element = dataGridColumn.GenerateEditingElementInternal(dataGridCell, dataGridRow.DataContext);
if (element != null)
{
// Subscribe to the new element's events
element.Initialized += EditingElement_Initialized;
dataGridCell.Content = element;
if (element.IsInitialized)
{
PreparingCellForEditPrivate(element as Control);
}
else
{
// Subscribe to the new element's events
element.Initialized += EditingElement_Initialized;
}
}
}
else
{
// Generate Element and apply column style if available
element = dataGridColumn.GenerateElementInternal(dataGridCell, dataGridRow.DataContext);
dataGridCell.Content = element;
}
dataGridCell.Content = element;
}
private void PreparingCellForEditPrivate(Control editingElement)
@ -5711,6 +5727,35 @@ namespace Avalonia.Controls
VerticalScroll?.Invoke(sender, e);
}
//TODO: Ensure right button is checked for
private bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit, bool shift, bool ctrl)
{
Debug.Assert(slot >= 0);
if (shift || ctrl)
{
return true;
}
if (IsSlotOutOfBounds(slot))
{
return true;
}
if (GetRowSelection(slot))
{
return true;
}
// Unselect everything except the row that was clicked on
try
{
UpdateSelectionAndCurrency(columnIndex, slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false);
}
finally
{
NoSelectionChangeCount--;
}
return true;
}
//TODO: Ensure left button is checked for
private bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit, bool shift, bool ctrl)
{

49
src/Avalonia.Controls.DataGrid/DataGridCell.cs

@ -161,29 +161,42 @@ namespace Avalonia.Controls
private void DataGridCell_PointerPressed(PointerPressedEventArgs e)
{
// OwningGrid is null for TopLeftHeaderCell and TopRightHeaderCell because they have no OwningRow
if (OwningGrid != null)
if (OwningGrid == null)
{
OwningGrid.OnCellPointerPressed(new DataGridCellPointerPressedEventArgs(this, OwningRow, OwningColumn, e));
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
return;
}
OwningGrid.OnCellPointerPressed(new DataGridCellPointerPressedEventArgs(this, OwningRow, OwningColumn, e));
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)
{
OwningGrid.Focus();
}
if (OwningRow != null)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)
var handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
// Do not handle PointerPressed with touch,
// so we can start scroll gesture on the same event.
if (e.Pointer.Type != PointerType.Touch)
{
OwningGrid.Focus();
e.Handled = handled;
}
if (OwningRow != null)
{
var handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
// Do not handle PointerPressed with touch,
// so we can start scroll gesture on the same event.
if (e.Pointer.Type != PointerType.Touch)
{
e.Handled = handled;
}
OwningGrid.UpdatedStateOnMouseLeftButtonDown = true;
}
OwningGrid.UpdatedStateOnMouseLeftButtonDown = true;
}
}
else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)
{
OwningGrid.Focus();
}
if (OwningRow != null)
{
e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
}
}
}

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

@ -233,7 +233,7 @@ namespace Avalonia.Controls
else
{
editableCollectionView.EditItem(dataItem);
return editableCollectionView.IsEditingItem;
return editableCollectionView.IsEditingItem || editableCollectionView.IsAddingNew;
}
}
@ -314,7 +314,14 @@ namespace Avalonia.Controls
CommittingEdit = true;
try
{
editableCollectionView.CommitEdit();
if (editableCollectionView.IsAddingNew)
{
editableCollectionView.CommitNew();
}
else
{
editableCollectionView.CommitEdit();
}
}
finally
{

36
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@ -378,13 +378,13 @@ namespace Avalonia.Controls
}
}
}
}
}
internal Panel RootElement
{
get;
private set;
}
}
internal int Slot
{
@ -638,7 +638,7 @@ namespace Avalonia.Controls
PseudoClasses.Set(":editing", IsEditing);
PseudoClasses.Set(":invalid", !IsValid);
ApplyHeaderStatus();
}
}
}
//TODO Animation
@ -896,7 +896,7 @@ namespace Avalonia.Controls
_detailsElement.ContentHeight = _detailsDesiredHeight;
}
}
}
}
// Makes sure the _detailsDesiredHeight is initialized. We need to measure it to know what
// height we want to animate to. Subsequently, we just update that height in response to SizeChanged
@ -919,7 +919,7 @@ namespace Avalonia.Controls
//TODO Cleanup
double? _previousDetailsHeight = null;
//TODO Animation
private void DetailsContent_HeightChanged(double newValue)
{
@ -1022,7 +1022,7 @@ namespace Avalonia.Controls
}
}
}
internal void ApplyDetailsTemplate(bool initializeDetailsPreferredHeight)
{
if (_detailsElement != null && AreDetailsVisible)
@ -1066,7 +1066,7 @@ namespace Avalonia.Controls
.Subscribe(DetailsContent_MarginChanged);
}
_detailsElement.Children.Add(_detailsContent);
}
}
@ -1090,6 +1090,28 @@ namespace Avalonia.Controls
}
}
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
if (change.Property == DataContextProperty)
{
var owner = OwningGrid;
if (owner != null && this.IsRecycled)
{
var columns = owner.ColumnsItemsInternal;
var nc = columns.Count;
for (int ci = 0; ci < nc; ci++)
{
if (columns[ci] is DataGridTemplateColumn column)
{
column.RefreshCellContent((Control)this.Cells[column.Index].Content, nameof(DataGridTemplateColumn.CellTemplate));
}
}
}
}
base.OnPropertyChanged(change);
}
}

15
src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs

@ -283,7 +283,11 @@ namespace Avalonia.Controls
//TODO TabStop
private void DataGridRowGroupHeader_PointerPressed(PointerPressedEventArgs e)
{
if (OwningGrid != null && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
if (OwningGrid == null)
{
return;
}
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (OwningGrid.IsDoubleClickRecordsClickOnCall(this) && !e.Handled)
{
@ -300,6 +304,15 @@ namespace Avalonia.Controls
e.Handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, OwningGrid.CurrentColumnIndex, RowGroupInfo.Slot, allowEdit: false);
}
}
else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
{
if (!e.Handled)
{
OwningGrid.Focus();
}
e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, OwningGrid.CurrentColumnIndex, RowGroupInfo.Slot, allowEdit: false);
}
}
private void EnsureChildClip(Visual child, double frozenLeftEdge)

17
src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs

@ -179,12 +179,12 @@ namespace Avalonia.Controls.Primitives
//TODO TabStop
private void DataGridRowHeader_PointerPressed(object sender, PointerPressedEventArgs e)
{
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
if (OwningGrid == null)
{
return;
}
if (OwningGrid != null)
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)
@ -199,6 +199,19 @@ namespace Avalonia.Controls.Primitives
OwningGrid.UpdatedStateOnMouseLeftButtonDown = true;
}
}
else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
{
if (!e.Handled)
{
OwningGrid.Focus();
}
if (OwningRow != null)
{
Debug.Assert(sender is DataGridRowHeader);
Debug.Assert(sender == this);
e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, -1, Slot, false);
}
}
}
}

15
src/Avalonia.Controls/Design.cs

@ -60,6 +60,19 @@ namespace Avalonia.Controls
return target.GetValue(PreviewWithProperty);
}
public static readonly AttachedProperty<IStyle> DesignStyleProperty = AvaloniaProperty
.RegisterAttached<Control, IStyle>("DesignStyle", typeof(Design));
public static void SetDesignStyle(Control control, IStyle value)
{
control.SetValue(DesignStyleProperty, value);
}
public static IStyle GetDesignStyle(Control control)
{
return control.GetValue(DesignStyleProperty);
}
public static void ApplyDesignModeProperties(Control target, Control source)
{
if (source.IsSet(WidthProperty))
@ -68,6 +81,8 @@ namespace Avalonia.Controls
target.Height = source.GetValue(HeightProperty);
if (source.IsSet(DataContextProperty))
target.DataContext = source.GetValue(DataContextProperty);
if (source.IsSet(DesignStyleProperty))
target.Styles.Add(source.GetValue(DesignStyleProperty));
}
}
}

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

@ -145,7 +145,9 @@ namespace Avalonia.Controls.Primitives
{
IsHitTestVisibleProperty.OverrideDefaultValue<Popup>(false);
ChildProperty.Changed.AddClassHandler<Popup>((x, e) => x.ChildChanged(e));
IsOpenProperty.Changed.AddClassHandler<Popup>((x, e) => x.IsOpenChanged((AvaloniaPropertyChangedEventArgs<bool>)e));
IsOpenProperty.Changed.AddClassHandler<Popup>((x, e) => x.IsOpenChanged((AvaloniaPropertyChangedEventArgs<bool>)e));
VerticalOffsetProperty.Changed.AddClassHandler<Popup>((x, _) => x.HandlePositionChange());
HorizontalOffsetProperty.Changed.AddClassHandler<Popup>((x, _) => x.HandlePositionChange());
}
/// <summary>
@ -519,6 +521,24 @@ namespace Avalonia.Controls.Primitives
base.OnDetachedFromLogicalTree(e);
Close();
}
private void HandlePositionChange()
{
if (_openState != null)
{
var placementTarget = PlacementTarget ?? this.FindLogicalAncestorOfType<IControl>();
if (placementTarget == null)
return;
_openState.PopupHost.ConfigurePosition(
placementTarget,
PlacementMode,
new Point(HorizontalOffset, VerticalOffset),
PlacementAnchor,
PlacementGravity,
PlacementConstraintAdjustment,
PlacementRect);
}
}
private static IDisposable SubscribeToEventHandler<T, TEventHandler>(T target, TEventHandler handler, Action<T, TEventHandler> subscribe, Action<T, TEventHandler> unsubscribe)
{

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

@ -75,8 +75,6 @@
</Grid.ColumnDefinitions>
<ContentPresenter Name="PART_IconPresenter"
Content="{TemplateBinding Icon}"
Width="16"
Height="16"
Margin="{DynamicResource MenuIconPresenterMargin}"
HorizontalAlignment="Center"
VerticalAlignment="Center" />
@ -199,6 +197,8 @@
</Style>
<Style Selector="MenuItem /template/ ContentPresenter#PART_IconPresenter">
<Setter Property="Width" Value="16" />
<Setter Property="Height" Value="16" />
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="MenuItem:icon /template/ ContentPresenter#PART_IconPresenter">

33
src/Avalonia.Visuals/Rendering/DeferredRenderer.cs

@ -35,6 +35,8 @@ namespace Avalonia.Rendering
private IRef<IDrawOperation> _currentDraw;
private readonly IDeferredRendererLock _lock;
private readonly object _sceneLock = new object();
private readonly object _startStopLock = new object();
private readonly object _renderLoopIsRenderingLock = new object();
private readonly Action _updateSceneIfNeededDelegate;
/// <summary>
@ -139,6 +141,8 @@ namespace Avalonia.Rendering
}
Stop();
// Wait for any in-progress rendering to complete
lock(_renderLoopIsRenderingLock){}
DisposeRenderTarget();
}
@ -233,20 +237,26 @@ namespace Avalonia.Rendering
/// <inheritdoc/>
public void Start()
{
if (!_running && _renderLoop != null)
lock (_startStopLock)
{
_renderLoop.Add(this);
_running = true;
if (!_running && _renderLoop != null)
{
_renderLoop.Add(this);
_running = true;
}
}
}
/// <inheritdoc/>
public void Stop()
{
if (_running && _renderLoop != null)
lock (_startStopLock)
{
_renderLoop.Remove(this);
_running = false;
if (_running && _renderLoop != null)
{
_renderLoop.Remove(this);
_running = false;
}
}
}
@ -255,7 +265,16 @@ namespace Avalonia.Rendering
void IRenderLoopTask.Update(TimeSpan time) => UpdateScene();
void IRenderLoopTask.Render() => Render(false);
void IRenderLoopTask.Render()
{
lock (_renderLoopIsRenderingLock)
{
lock(_startStopLock)
if(!_running)
return;
Render(false);
}
}
/// <inheritdoc/>
Size IVisualBrushRenderer.GetRenderTargetSize(IVisualBrush brush)

7
src/Avalonia.Visuals/Visual.cs

@ -489,11 +489,8 @@ namespace Avalonia
protected internal sealed override void LogBindingError(AvaloniaProperty property, Exception e)
{
// Don't log a binding error unless the control is attached to a logical or visual tree.
// In theory this should only need to check for logical tree attachment, but in practise
// due to ContentControlMixin only taking effect when the template has finished being
// applied, some controls are attached to the visual tree before the logical tree.
if (((ILogical)this).IsAttachedToLogicalTree || ((IVisual)this).IsAttachedToVisualTree)
// Don't log a binding error unless the control is attached to a logical tree.
if (((ILogical)this).IsAttachedToLogicalTree)
{
if (e is BindingChainException b &&
string.IsNullOrEmpty(b.ExpressionErrorPoint) &&

3
src/Avalonia.X11/X11Window.cs

@ -805,13 +805,14 @@ namespace Avalonia.X11
if (_handle != IntPtr.Zero)
{
XDestroyWindow(_x11.Display, _handle);
_platform.Windows.Remove(_handle);
_platform.XI2?.OnWindowDestroyed(_handle);
var handle = _handle;
_handle = IntPtr.Zero;
Closed?.Invoke();
_mouse.Dispose();
_touch.Dispose();
XDestroyWindow(_x11.Display, handle);
}
if (_useRenderWindow && _renderHandle != IntPtr.Zero)

23
src/Markup/Avalonia.Markup.Xaml/Extensions.cs

@ -2,7 +2,9 @@ using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Markup.Xaml.XamlIl.Runtime;
using Avalonia.Styling;
namespace Avalonia.Markup.Xaml
{
@ -32,5 +34,26 @@ namespace Avalonia.Markup.Xaml
string name = string.IsNullOrEmpty(namespacePrefix) ? type : $"{namespacePrefix}:{type}";
return tr?.Resolve(name);
}
public static object GetDefaultAnchor(this IServiceProvider provider)
{
// If the target is not a control, so we need to find an anchor that will let us look
// up named controls and style resources. First look for the closest IControl in
// the context.
object anchor = provider.GetFirstParent<IControl>();
if (anchor is null)
{
// Try to find IDataContextProvider, this was added to allow us to find
// a datacontext for Application class when using NativeMenuItems.
anchor = provider.GetFirstParent<IDataContextProvider>();
}
// If a control was not found, then try to find the highest-level style as the XAML
// file could be a XAML file containing only styles.
return anchor ??
provider.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
provider.GetLastParent<IStyle>();
}
}
}

18
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindingExtension.cs

@ -1,7 +1,5 @@
using System;
using Avalonia.Data;
using Avalonia.Controls;
using Avalonia.Styling;
using Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings;
using Avalonia.Data.Core;
using Avalonia.Markup.Parsers;
@ -33,24 +31,10 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Priority = Priority,
StringFormat = StringFormat,
Source = Source,
DefaultAnchor = new WeakReference(GetDefaultAnchor(provider))
DefaultAnchor = new WeakReference(provider.GetDefaultAnchor())
};
}
private static object GetDefaultAnchor(IServiceProvider provider)
{
// If the target is not a control, so we need to find an anchor that will let us look
// up named controls and style resources. First look for the closest IControl in
// the context.
object anchor = provider.GetFirstParent<IControl>();
// If a control was not found, then try to find the highest-level style as the XAML
// file could be a XAML file containing only styles.
return anchor ??
provider.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
provider.GetLastParent<IStyle>();
}
protected override ExpressionObserver CreateExpressionObserver(IAvaloniaObject target, AvaloniaProperty targetProperty, object anchor, bool enableDataValidation)
{
if (Source != null)

23
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ReflectionBindingExtension.cs

@ -37,33 +37,12 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
Source = Source,
StringFormat = StringFormat,
RelativeSource = RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor(descriptorContext)),
DefaultAnchor = new WeakReference(descriptorContext.GetDefaultAnchor()),
TargetNullValue = TargetNullValue,
NameScope = new WeakReference<INameScope>(serviceProvider.GetService<INameScope>())
};
}
private static object GetDefaultAnchor(IServiceProvider context)
{
// If the target is not a control, so we need to find an anchor that will let us look
// up named controls and style resources. First look for the closest IControl in
// the context.
object anchor = context.GetFirstParent<IControl>();
if(anchor is null)
{
// Try to find IDataContextProvider, this was added to allow us to find
// a datacontext for Application class when using NativeMenuItems.
anchor = context.GetFirstParent<IDataContextProvider>();
}
// If a control was not found, then try to find the highest-level style as the XAML
// file could be a XAML file containing only styles.
return anchor ??
context.GetService<IRootObjectProvider>()?.RootObject as IStyle ??
context.GetLastParent<IStyle>();
}
public IValueConverter Converter { get; set; }
public object ConverterParameter { get; set; }

47
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -164,7 +164,7 @@ namespace Avalonia.Skia
/// <inheritdoc />
public void DrawLine(IPen pen, Point p1, Point p2)
{
using (var paint = CreatePaint(_strokePaint, pen, new Rect(p1, p2).Normalize()))
using (var paint = CreatePaint(_strokePaint, pen, new Size(Math.Abs(p2.X - p1.X), Math.Abs(p2.Y - p1.Y))))
{
if (paint.Paint is object)
{
@ -177,10 +177,10 @@ namespace Avalonia.Skia
public void DrawGeometry(IBrush brush, IPen pen, IGeometryImpl geometry)
{
var impl = (GeometryImpl) geometry;
var rect = geometry.Bounds;
var size = geometry.Bounds.Size;
using (var fill = brush != null ? CreatePaint(_fillPaint, brush, rect) : default(PaintWrapper))
using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, rect) : default(PaintWrapper))
using (var fill = brush != null ? CreatePaint(_fillPaint, brush, size) : default(PaintWrapper))
using (var stroke = pen?.Brush != null ? CreatePaint(_strokePaint, pen, size) : default(PaintWrapper))
{
if (fill.Paint != null)
{
@ -354,7 +354,7 @@ namespace Avalonia.Skia
if (brush != null)
{
using (var paint = CreatePaint(_fillPaint, brush, rect.Rect))
using (var paint = CreatePaint(_fillPaint, brush, rect.Rect.Size))
{
if (isRounded)
{
@ -397,7 +397,7 @@ namespace Avalonia.Skia
if (pen?.Brush != null)
{
using (var paint = CreatePaint(_strokePaint, pen, rect.Rect))
using (var paint = CreatePaint(_strokePaint, pen, rect.Rect.Size))
{
if (paint.Paint is object)
{
@ -417,7 +417,7 @@ namespace Avalonia.Skia
/// <inheritdoc />
public void DrawText(IBrush foreground, Point origin, IFormattedTextImpl text)
{
using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds))
using (var paint = CreatePaint(_fillPaint, foreground, text.Bounds.Size))
{
var textImpl = (FormattedTextImpl) text;
textImpl.Draw(this, Canvas, origin.ToSKPoint(), paint, _canTextUseLcdRendering);
@ -427,7 +427,7 @@ namespace Avalonia.Skia
/// <inheritdoc />
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
using (var paintWrapper = CreatePaint(_fillPaint, foreground, new Rect(glyphRun.Size)))
using (var paintWrapper = CreatePaint(_fillPaint, foreground, glyphRun.Size))
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl;
@ -537,7 +537,7 @@ namespace Avalonia.Skia
var paint = new SKPaint();
Canvas.SaveLayer(paint);
_maskStack.Push(CreatePaint(paint, mask, bounds, true));
_maskStack.Push(CreatePaint(paint, mask, bounds.Size, true));
}
/// <inheritdoc />
@ -593,19 +593,18 @@ namespace Avalonia.Skia
/// <param name="paintWrapper">Paint wrapper.</param>
/// <param name="targetRect">Target bound rect.</param>
/// <param name="gradientBrush">Gradient brush.</param>
private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Rect targetRect, IGradientBrush gradientBrush)
private void ConfigureGradientBrush(ref PaintWrapper paintWrapper, Size targetSize, IGradientBrush gradientBrush)
{
var tileMode = gradientBrush.SpreadMethod.ToSKShaderTileMode();
var stopColors = gradientBrush.GradientStops.Select(s => s.Color.ToSKColor()).ToArray();
var stopOffsets = gradientBrush.GradientStops.Select(s => (float)s.Offset).ToArray();
var position = targetRect.Position.ToSKPoint();
switch (gradientBrush)
{
case ILinearGradientBrush linearGradient:
{
var start = position + linearGradient.StartPoint.ToPixels(targetRect.Size).ToSKPoint();
var end = position + linearGradient.EndPoint.ToPixels(targetRect.Size).ToSKPoint();
var start = linearGradient.StartPoint.ToPixels(targetSize).ToSKPoint();
var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint();
// would be nice to cache these shaders possibly?
using (var shader =
@ -618,10 +617,10 @@ namespace Avalonia.Skia
}
case IRadialGradientBrush radialGradient:
{
var center = position + radialGradient.Center.ToPixels(targetRect.Size).ToSKPoint();
var radius = (float)(radialGradient.Radius * targetRect.Width);
var center = radialGradient.Center.ToPixels(targetSize).ToSKPoint();
var radius = (float)(radialGradient.Radius * targetSize.Width);
var origin = position + radialGradient.GradientOrigin.ToPixels(targetRect.Size).ToSKPoint();
var origin = radialGradient.GradientOrigin.ToPixels(targetSize).ToSKPoint();
if (origin.Equals(center))
{
@ -666,7 +665,7 @@ namespace Avalonia.Skia
}
case IConicGradientBrush conicGradient:
{
var center = position + conicGradient.Center.ToPixels(targetRect.Size).ToSKPoint();
var center = conicGradient.Center.ToPixels(targetSize).ToSKPoint();
// Skia's default is that angle 0 is from the right hand side of the center point
// but we are matching CSS where the vertical point above the center is 0.
@ -868,10 +867,10 @@ namespace Avalonia.Skia
/// </summary>
/// <param name="paint">The paint to wrap.</param>
/// <param name="brush">Source brush.</param>
/// <param name="targetRect">Target rect.</param>
/// <param name="targetSize">Target size.</param>
/// <param name="disposePaint">Optional dispose of the supplied paint.</param>
/// <returns>Paint wrapper for given brush.</returns>
internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Rect targetRect, bool disposePaint = false)
internal PaintWrapper CreatePaint(SKPaint paint, IBrush brush, Size targetSize, bool disposePaint = false)
{
var paintWrapper = new PaintWrapper(paint, disposePaint);
@ -890,7 +889,7 @@ namespace Avalonia.Skia
if (brush is IGradientBrush gradient)
{
ConfigureGradientBrush(ref paintWrapper, targetRect, gradient);
ConfigureGradientBrush(ref paintWrapper, targetSize, gradient);
return paintWrapper;
}
@ -910,7 +909,7 @@ namespace Avalonia.Skia
if (tileBrush != null && tileBrushImage != null)
{
ConfigureTileBrush(ref paintWrapper, targetRect.Size, tileBrush, tileBrushImage);
ConfigureTileBrush(ref paintWrapper, targetSize, tileBrush, tileBrushImage);
}
else
{
@ -925,10 +924,10 @@ namespace Avalonia.Skia
/// </summary>
/// <param name="paint">The paint to wrap.</param>
/// <param name="pen">Source pen.</param>
/// <param name="targetRect">Target rect.</param>
/// <param name="targetSize">Target size.</param>
/// <param name="disposePaint">Optional dispose of the supplied paint.</param>
/// <returns></returns>
private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Rect targetRect, bool disposePaint = false)
private PaintWrapper CreatePaint(SKPaint paint, IPen pen, Size targetSize, bool disposePaint = false)
{
// In Skia 0 thickness means - use hairline rendering
// and for us it means - there is nothing rendered.
@ -937,7 +936,7 @@ namespace Avalonia.Skia
return default;
}
var rv = CreatePaint(paint, pen.Brush, targetRect, disposePaint);
var rv = CreatePaint(paint, pen.Brush, targetSize, disposePaint);
paint.IsStroke = true;
paint.StrokeWidth = (float) pen.Thickness;

4
src/Skia/Avalonia.Skia/FormattedTextImpl.cs

@ -278,9 +278,9 @@ namespace Avalonia.Skia
if (fb != null)
{
//TODO: figure out how to get the brush rect
//TODO: figure out how to get the brush size
currentWrapper = context.CreatePaint(new SKPaint { IsAntialias = true }, fb,
default);
new Size());
}
else
{

4
src/Windows/Avalonia.Direct2D1/Media/AvaloniaTextRenderer.cs

@ -34,10 +34,10 @@ namespace Avalonia.Direct2D1.Media
{
var wrapper = clientDrawingEffect as BrushWrapper;
// TODO: Work out how to get the rect below rather than passing default.
// TODO: Work out how to get the size below rather than passing new Size().
var brush = (wrapper == null) ?
_foreground :
_context.CreateBrush(wrapper.Brush, default).PlatformBrush;
_context.CreateBrush(wrapper.Brush, new Size()).PlatformBrush;
_renderTarget.DrawGlyphRun(
new RawVector2 { X = baselineOriginX, Y = baselineOriginY },

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

@ -192,7 +192,7 @@ namespace Avalonia.Direct2D1.Media
{
using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect))
using (var d2dOpacityMask = CreateBrush(opacityMask, opacityMaskRect.Size))
using (var geometry = new SharpDX.Direct2D1.RectangleGeometry(Direct2D1Platform.Direct2D1Factory, destRect.ToDirect2D()))
{
if (d2dOpacityMask.PlatformBrush != null)
@ -217,7 +217,9 @@ namespace Avalonia.Direct2D1.Media
{
if (pen != null)
{
using (var d2dBrush = CreateBrush(pen.Brush, new Rect(p1, p2).Normalize()))
var size = new Rect(p1, p2).Size;
using (var d2dBrush = CreateBrush(pen.Brush, size))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (d2dBrush.PlatformBrush != null)
@ -243,7 +245,7 @@ namespace Avalonia.Direct2D1.Media
{
if (brush != null)
{
using (var d2dBrush = CreateBrush(brush, geometry.Bounds))
using (var d2dBrush = CreateBrush(brush, geometry.Bounds.Size))
{
if (d2dBrush.PlatformBrush != null)
{
@ -255,7 +257,7 @@ namespace Avalonia.Direct2D1.Media
if (pen != null)
{
using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen)))
using (var d2dBrush = CreateBrush(pen.Brush, geometry.GetRenderBounds(pen).Size))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (d2dBrush.PlatformBrush != null)
@ -280,7 +282,7 @@ namespace Avalonia.Direct2D1.Media
if (brush != null)
{
using (var b = CreateBrush(brush, rect))
using (var b = CreateBrush(brush, rect.Size))
{
if (b.PlatformBrush != null)
{
@ -309,7 +311,7 @@ namespace Avalonia.Direct2D1.Media
if (pen?.Brush != null)
{
using (var wrapper = CreateBrush(pen.Brush, rect))
using (var wrapper = CreateBrush(pen.Brush, rect.Size))
using (var d2dStroke = pen.ToDirect2DStrokeStyle(_deviceContext))
{
if (wrapper.PlatformBrush != null)
@ -347,7 +349,7 @@ namespace Avalonia.Direct2D1.Media
{
var impl = (FormattedTextImpl)text;
using (var brush = CreateBrush(foreground, impl.Bounds))
using (var brush = CreateBrush(foreground, impl.Bounds.Size))
using (var renderer = new AvaloniaTextRenderer(this, _deviceContext, brush.PlatformBrush))
{
if (brush.PlatformBrush != null)
@ -365,7 +367,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="glyphRun">The glyph run.</param>
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
using (var brush = CreateBrush(foreground, new Rect(glyphRun.Size)))
using (var brush = CreateBrush(foreground, glyphRun.Size))
{
var glyphRunImpl = (GlyphRunImpl)glyphRun.GlyphRunImpl;
@ -456,9 +458,9 @@ namespace Avalonia.Direct2D1.Media
/// Creates a Direct2D brush wrapper for a Avalonia brush.
/// </summary>
/// <param name="brush">The avalonia brush.</param>
/// <param name="destinationRect">The brush's target area.</param>
/// <param name="destinationSize">The size of the brush's target area.</param>
/// <returns>The Direct2D brush wrapper.</returns>
public BrushImpl CreateBrush(IBrush brush, Rect destinationRect)
public BrushImpl CreateBrush(IBrush brush, Size destinationSize)
{
var solidColorBrush = brush as ISolidColorBrush;
var linearGradientBrush = brush as ILinearGradientBrush;
@ -473,11 +475,11 @@ namespace Avalonia.Direct2D1.Media
}
else if (linearGradientBrush != null)
{
return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationRect);
return new LinearGradientBrushImpl(linearGradientBrush, _deviceContext, destinationSize);
}
else if (radialGradientBrush != null)
{
return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationRect);
return new RadialGradientBrushImpl(radialGradientBrush, _deviceContext, destinationSize);
}
else if (conicGradientBrush != null)
{
@ -490,7 +492,7 @@ namespace Avalonia.Direct2D1.Media
imageBrush,
_deviceContext,
(BitmapImpl)imageBrush.Source.PlatformImpl.Item,
destinationRect.Size);
destinationSize);
}
else if (visualBrush != null)
{
@ -521,7 +523,7 @@ namespace Avalonia.Direct2D1.Media
visualBrush,
_deviceContext,
new D2DBitmapImpl(intermediate.Bitmap),
destinationRect.Size);
destinationSize);
}
}
}
@ -573,7 +575,7 @@ namespace Avalonia.Direct2D1.Media
ContentBounds = PrimitiveExtensions.RectangleInfinite,
MaskTransform = PrimitiveExtensions.Matrix3x2Identity,
Opacity = 1,
OpacityBrush = CreateBrush(mask, bounds).PlatformBrush
OpacityBrush = CreateBrush(mask, bounds.Size).PlatformBrush
};
var layer = _layerPool.Count != 0 ? _layerPool.Pop() : new Layer(_deviceContext);
_deviceContext.PushLayer(ref parameters, layer);

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

@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media
public LinearGradientBrushImpl(
ILinearGradientBrush brush,
SharpDX.Direct2D1.RenderTarget target,
Rect destinationRect)
Size destinationSize)
{
if (brush.GradientStops.Count == 0)
{
@ -21,9 +21,8 @@ namespace Avalonia.Direct2D1.Media
Position = (float)s.Offset
}).ToArray();
var position = destinationRect.Position;
var startPoint = position + brush.StartPoint.ToPixels(destinationRect.Size);
var endPoint = position + brush.EndPoint.ToPixels(destinationRect.Size);
var startPoint = brush.StartPoint.ToPixels(destinationSize);
var endPoint = brush.EndPoint.ToPixels(destinationSize);
using (var stops = new SharpDX.Direct2D1.GradientStopCollection(
target,

11
src/Windows/Avalonia.Direct2D1/Media/RadialGradientBrushImpl.cs

@ -8,7 +8,7 @@ namespace Avalonia.Direct2D1.Media
public RadialGradientBrushImpl(
IRadialGradientBrush brush,
SharpDX.Direct2D1.RenderTarget target,
Rect destinationRect)
Size destinationSize)
{
if (brush.GradientStops.Count == 0)
{
@ -21,13 +21,12 @@ namespace Avalonia.Direct2D1.Media
Position = (float)s.Offset
}).ToArray();
var position = destinationRect.Position;
var centerPoint = position + brush.Center.ToPixels(destinationRect.Size);
var gradientOrigin = position + brush.GradientOrigin.ToPixels(destinationRect.Size) - centerPoint;
var centerPoint = brush.Center.ToPixels(destinationSize);
var gradientOrigin = brush.GradientOrigin.ToPixels(destinationSize) - centerPoint;
// Note: Direct2D supports RadiusX and RadiusY but Cairo backend supports only Radius property
var radiusX = brush.Radius * destinationRect.Width;
var radiusY = brush.Radius * destinationRect.Height;
var radiusX = brush.Radius * destinationSize.Width;
var radiusY = brush.Radius * destinationSize.Height;
using (var stops = new SharpDX.Direct2D1.GradientStopCollection(
target,

1
tests/Avalonia.Base.UnitTests/Avalonia.Base.UnitTests.csproj

@ -14,6 +14,7 @@
<Import Project="..\..\build\SharedVersion.props" />
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Base\Avalonia.Base.csproj" />
<ProjectReference Include="..\..\src\Markup\Avalonia.Markup.Xaml.Loader\Avalonia.Markup.Xaml.Loader.csproj" />
<ProjectReference Include="..\Avalonia.UnitTests\Avalonia.UnitTests.csproj" />
</ItemGroup>
<ItemGroup>

73
tests/Avalonia.Base.UnitTests/Logging/LoggingTests.cs

@ -0,0 +1,73 @@
using Avalonia.Controls;
using Avalonia.Controls.Shapes;
using Avalonia.Markup.Xaml;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Base.UnitTests.Logging
{
public class LoggingTests
{
[Fact]
public void Control_Should_Not_Log_Binding_Errors_When_Detached_From_Visual_Tree()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Base.UnitTests.Logging;assembly=Avalonia.UnitTests'>
<Panel Name='panel'>
<Rectangle Name='rect' Fill='{Binding $parent[Window].Background}'/>
</Panel>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var calledTimes = 0;
using var logSink = TestLogSink.Start((l, a, s, m, d) =>
{
if (l >= Avalonia.Logging.LogEventLevel.Warning)
{
calledTimes++;
}
});
var panel = window.FindControl<Panel>("panel");
var rect = window.FindControl<Rectangle>("rect");
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
panel.Children.Remove(rect);
Assert.Equal(0, calledTimes);
}
}
[Fact]
public void Control_Should_Log_Binding_Errors_When_No_Ancestor_With_Such_Name()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Base.UnitTests.Logging;assembly=Avalonia.UnitTests'>
<Panel>
<Rectangle Fill='{Binding $parent[Grid].Background}'/>
</Panel>
</Window>";
var calledTimes = 0;
using var logSink = TestLogSink.Start((l, a, s, m, d) =>
{
if (l >= Avalonia.Logging.LogEventLevel.Warning && s is Rectangle)
{
calledTimes++;
}
});
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
window.ApplyTemplate();
window.Presenter.ApplyTemplate();
Assert.Equal(1, calledTimes);
}
}
}
}

3
tests/Avalonia.RenderTests/Media/ConicGradientBrushTests.cs

@ -200,7 +200,8 @@ namespace Avalonia.Direct2D1.RenderTests.Media
Child = new DrawnControl(c =>
{
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
using (c.PushPreTransform(Matrix.CreateTranslation(100, 100)))
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
}),
};

12
tests/Avalonia.RenderTests/Media/LinearGradientBrushTests.cs

@ -81,10 +81,10 @@ namespace Avalonia.Direct2D1.RenderTests.Media
StartPoint = new RelativePoint(0, 0, RelativeUnit.Relative),
EndPoint = new RelativePoint(1, 1, RelativeUnit.Relative),
GradientStops =
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
}
{
new GradientStop { Color = Colors.Red, Offset = 0 },
new GradientStop { Color = Colors.Blue, Offset = 1 }
}
};
Decorator target = new Decorator
@ -94,7 +94,9 @@ namespace Avalonia.Direct2D1.RenderTests.Media
Child = new DrawnControl(c =>
{
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
using (c.PushPreTransform(Matrix.CreateTranslation(100, 100)))
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
}),
};

3
tests/Avalonia.RenderTests/Media/RadialGradientBrushTests.cs

@ -185,7 +185,8 @@ namespace Avalonia.Direct2D1.RenderTests.Media
Child = new DrawnControl(c =>
{
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
c.DrawRectangle(brush, null, new Rect(100, 100, 100, 100));
using (c.PushPreTransform(Matrix.CreateTranslation(100, 100)))
c.DrawRectangle(brush, null, new Rect(0, 0, 100, 100));
}),
};

Loading…
Cancel
Save