Browse Source

Merge branch 'master' into issues/3356

pull/3357/head
Steven Kirk 6 years ago
committed by GitHub
parent
commit
0f83088f54
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      native/Avalonia.Native/src/OSX/window.mm
  2. 2
      readme.md
  3. 6
      samples/ControlCatalog/MainView.xaml
  4. 85
      samples/ControlCatalog/Pages/ImagePage.xaml
  5. 39
      samples/ControlCatalog/Pages/ImagePage.xaml.cs
  6. 2
      samples/RenderDemo/Pages/RenderTargetBitmapPage.cs
  7. 64
      scripts/avalonia-rename.ps1
  8. 2
      src/Avalonia.Animation/IterationCount.cs
  9. 4
      src/Avalonia.Controls/DrawingPresenter.cs
  10. 45
      src/Avalonia.Controls/Image.cs
  11. 2
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  12. 6
      src/Avalonia.Controls/TextBox.cs
  13. 14
      src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs
  14. 2
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  15. 14
      src/Avalonia.Styling/Controls/ISetResourceParent.cs
  16. 62
      src/Avalonia.Styling/Controls/ResourceDictionary.cs
  17. 4
      src/Avalonia.Styling/StyledElement.cs
  18. 12
      src/Avalonia.Styling/Styling/Style.cs
  19. 20
      src/Avalonia.Styling/Styling/Styles.cs
  20. 6
      src/Avalonia.Visuals/Media/Drawing.cs
  21. 21
      src/Avalonia.Visuals/Media/DrawingContext.cs
  22. 3
      src/Avalonia.Visuals/Media/DrawingGroup.cs
  23. 81
      src/Avalonia.Visuals/Media/DrawingImage.cs
  24. 5
      src/Avalonia.Visuals/Media/GeometryDrawing.cs
  25. 29
      src/Avalonia.Visuals/Media/IImage.cs
  26. 20
      src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
  27. 11
      src/Avalonia.Visuals/Media/Imaging/IBitmap.cs
  28. 90
      src/Avalonia.Visuals/Media/MediaExtensions.cs
  29. 25
      src/Avalonia.Visuals/Media/StretchDirection.cs
  30. 4
      src/Avalonia.Visuals/Platform/IDrawingContextImpl.cs
  31. 6
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  32. 4
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  33. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
  34. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs
  35. 18
      src/Avalonia.X11/Glx/Glx.cs
  36. 2
      src/Avalonia.X11/Glx/GlxDisplay.cs
  37. 2
      src/Avalonia.X11/X11IconLoader.cs
  38. 2
      src/Avalonia.X11/X11Platform.cs
  39. 3
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs
  40. 26
      src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs
  41. 10
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  42. 2
      src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs
  43. 8
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  44. 5
      src/Skia/Avalonia.Skia/FontManagerImpl.cs
  45. 4
      src/Windows/Avalonia.Direct2D1/Media/DrawingContextImpl.cs
  46. 2
      src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs
  47. 3
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
  48. 2
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  49. 14
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  50. 70
      tests/Avalonia.Controls.UnitTests/ImageTests.cs
  51. 24
      tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs
  52. 53
      tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs
  53. 123
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs
  54. 2
      tests/Avalonia.RenderTests/Media/BitmapTests.cs
  55. 18
      tests/Avalonia.Skia.UnitTests/FontManagerImplTests.cs
  56. 45
      tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs
  57. 2
      tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs
  58. 136
      tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs

3
native/Avalonia.Native/src/OSX/window.mm

@ -1283,6 +1283,9 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_closed = false;
_lastScaling = [self backingScaleFactor];
[self setOpaque:NO];
[self setBackgroundColor: [NSColor clearColor]];
[self invalidateShadow];
return self;
}

2
readme.md

@ -22,7 +22,7 @@ Avalonia [Visual Studio Extension](https://marketplace.visualstudio.com/items?it
For those without Visual Studio, a starter guide for .NET Core CLI can be found [here](http://avaloniaui.net/docs/quickstart/create-new-project#net-core).
Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: ([stable(ish)](https://www.nuget.org/packages/Avalonia/), [nightly](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed))
Avalonia is delivered via <b>NuGet</b> package manager. You can find the packages here: [stable(ish)](https://www.nuget.org/packages/Avalonia/)
Use these commands in the Package Manager console to install Avalonia manually:
```

6
samples/ControlCatalog/MainView.xaml

@ -32,7 +32,11 @@
<TabItem Header="DatePicker"><pages:DatePickerPage/></TabItem>
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="Image"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
<pages:ImagePage/>
</TabItem>
<TabItem Header="ItemsRepeater"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">

85
samples/ControlCatalog/Pages/ImagePage.xaml

@ -1,45 +1,52 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.ImagePage">
<StackPanel Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Image</TextBlock>
<TextBlock Classes="h2">Displays an image</TextBlock>
<StackPanel Orientation="Horizontal"
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical">
<TextBlock>No Stretch</TextBlock>
<Image Source="/Assets/delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="None"/>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock>Fill</TextBlock>
<Image Source="/Assets/delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="Fill"/>
</StackPanel>
<DockPanel>
<StackPanel DockPanel.Dock="Top" Orientation="Vertical" Spacing="4">
<TextBlock Classes="h1">Image</TextBlock>
<TextBlock Classes="h2">Displays an image</TextBlock>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock>Uniform</TextBlock>
<Image Source="/Assets/delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="Uniform"/>
</StackPanel>
<Grid ColumnDefinitions="*,*" RowDefinitions="Auto,*" Margin="64">
<DockPanel Grid.Column="0" Grid.Row="1" Margin="16">
<TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Bitmap</TextBlock>
<ComboBox Name="bitmapStretch" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="BitmapStretchChanged">
<ComboBoxItem>None</ComboBoxItem>
<ComboBoxItem>Fill</ComboBoxItem>
<ComboBoxItem>Uniform</ComboBoxItem>
<ComboBoxItem>UniformToFill</ComboBoxItem>
</ComboBox>
<Image Name="bitmapImage"
Source="/Assets/delicate-arch-896885_640.jpg"/>
</DockPanel>
<StackPanel Orientation="Vertical">
<TextBlock>UniformToFill</TextBlock>
<Image Source="/Assets/delicate-arch-896885_640.jpg"
Width="100" Height="200"
Stretch="UniformToFill"/>
</StackPanel>
</StackPanel>
<StackPanel Orientation="Vertical">
<TextBlock>Window Icon as an Image</TextBlock>
<Image Name="Icon" Width="100" Height="200" Stretch="None" />
</StackPanel>
</StackPanel>
<DockPanel Grid.Column="1" Grid.Row="1" Margin="16">
<TextBlock DockPanel.Dock="Top" Classes="h3" Margin="0 8">Drawing</TextBlock>
<ComboBox Name="drawingStretch" DockPanel.Dock="Top" SelectedIndex="2" SelectionChanged="DrawingStretchChanged">
<ComboBoxItem>None</ComboBoxItem>
<ComboBoxItem>Fill</ComboBoxItem>
<ComboBoxItem>Uniform</ComboBoxItem>
<ComboBoxItem>UniformToFill</ComboBoxItem>
</ComboBox>
<Image Name="drawingImage">
<Image.Source>
<DrawingImage>
<GeometryDrawing Brush="Red">
<PathGeometry>
<PathFigure StartPoint="0,0" IsClosed="True">
<QuadraticBezierSegment Point1="50,0" Point2="50,-50" />
<QuadraticBezierSegment Point1="100,-50" Point2="100,0" />
<LineSegment Point="50,0" />
<LineSegment Point="50,50" />
</PathFigure>
</PathGeometry>
</GeometryDrawing>
</DrawingImage>
</Image.Source>
</Image>
</DockPanel>
</Grid>
</DockPanel>
</UserControl>

39
samples/ControlCatalog/Pages/ImagePage.xaml.cs

@ -1,40 +1,41 @@
using System.IO;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using Avalonia.Media;
namespace ControlCatalog.Pages
{
public class ImagePage : UserControl
{
private Image iconImage;
private readonly Image _bitmapImage;
private readonly Image _drawingImage;
public ImagePage()
{
this.InitializeComponent();
InitializeComponent();
_bitmapImage = this.FindControl<Image>("bitmapImage");
_drawingImage = this.FindControl<Image>("drawingImage");
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
iconImage = this.Get<Image>("Icon");
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
public void BitmapStretchChanged(object sender, SelectionChangedEventArgs e)
{
if (_bitmapImage != null)
{
var comboxBox = (ComboBox)sender;
_bitmapImage.Stretch = (Stretch)comboxBox.SelectedIndex;
}
}
public void DrawingStretchChanged(object sender, SelectionChangedEventArgs e)
{
base.OnAttachedToVisualTree(e);
if (iconImage.Source == null)
if (_drawingImage != null)
{
var windowRoot = e.Root as Window;
if (windowRoot != null)
{
using (var stream = new MemoryStream())
{
windowRoot.Icon.Save(stream);
stream.Seek(0, SeekOrigin.Begin);
iconImage.Source = new Bitmap(stream);
}
}
var comboxBox = (ComboBox)sender;
_drawingImage.Stretch = (Stretch)comboxBox.SelectedIndex;
}
}
}

2
samples/RenderDemo/Pages/RenderTargetBitmapPage.cs

@ -39,7 +39,7 @@ namespace RenderDemo.Pages
ctx.FillRectangle(Brushes.Fuchsia, new Rect(50, 50, 100, 100));
}
context.DrawImage(_bitmap, 1,
context.DrawImage(_bitmap,
new Rect(0, 0, 200, 200),
new Rect(0, 0, 200, 200));
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);

64
scripts/avalonia-rename.ps1

@ -1,64 +0,0 @@
function Get-NewDirectoryName {
param ([System.IO.DirectoryInfo]$item)
$name = $item.Name.Replace("perspex", "avalonia")
$name = $name.Replace("Perspex", "Avalonia")
Join-Path $item.Parent.FullName $name
}
function Get-NewFileName {
param ([System.IO.FileInfo]$item)
$name = $item.Name.Replace("perspex", "avalonia")
$name = $name.Replace("Perspex", "Avalonia")
Join-Path $item.DirectoryName $name
}
function Rename-Contents {
param ([System.IO.FileInfo] $file)
$extensions = @(".cs",".xaml",".csproj",".sln",".md",".json",".yml",".partial",".ps1",".nuspec",".htm",".html",".gitmodules".".xml",".plist",".targets",".projitems",".shproj",".xib")
if ($extensions.Contains($file.Extension)) {
$text = [IO.File]::ReadAllText($file.FullName)
$text = $text.Replace("github.com/perspex", "github.com/avaloniaui")
$text = $text.Replace("github.com/Perspex", "github.com/AvaloniaUI")
$text = $text.Replace("perspex", "avalonia")
$text = $text.Replace("Perspex", "Avalonia")
$text = $text.Replace("PERSPEX", "AVALONIA")
[IO.File]::WriteAllText($file.FullName, $text)
}
}
function Process-Files {
param ([System.IO.DirectoryInfo] $item)
$dirs = Get-ChildItem -Path $item.FullName -Directory
$files = Get-ChildItem -Path $item.FullName -File
foreach ($dir in $dirs) {
Process-Files $dir.FullName
}
foreach ($file in $files) {
Rename-Contents $file
$renamed = Get-NewFileName $file
if ($file.FullName -ne $renamed) {
Write-Host git mv $file.FullName $renamed
& git mv $file.FullName $renamed
}
}
$renamed = Get-NewDirectoryName $item
if ($item.FullName -ne $renamed) {
Write-Host git mv $item.FullName $renamed
& git mv $item.FullName $renamed
}
}
& git submodule deinit .
& git clean -xdf
Process-Files .

2
src/Avalonia.Animation/IterationCount.cs

@ -63,7 +63,7 @@ namespace Avalonia.Animation
public IterationType RepeatType => _type;
/// <summary>
/// Gets a value that indicates whether the <see cref="IterationCount"/> is set to loop.
/// Gets a value that indicates whether the <see cref="IterationCount"/> is set to Infinite.
/// </summary>
public bool IsInfinite => _type == IterationType.Infinite;

4
src/Avalonia.Controls/DrawingPresenter.cs

@ -1,9 +1,11 @@
using Avalonia.Controls.Shapes;
using System;
using Avalonia.Controls.Shapes;
using Avalonia.Media;
using Avalonia.Metadata;
namespace Avalonia.Controls
{
[Obsolete("Use Image control with DrawingImage source")]
public class DrawingPresenter : Control
{
static DrawingPresenter()

45
src/Avalonia.Controls/Image.cs

@ -14,8 +14,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the <see cref="Source"/> property.
/// </summary>
public static readonly StyledProperty<IBitmap> SourceProperty =
AvaloniaProperty.Register<Image, IBitmap>(nameof(Source));
public static readonly StyledProperty<IImage> SourceProperty =
AvaloniaProperty.Register<Image, IImage>(nameof(Source));
/// <summary>
/// Defines the <see cref="Stretch"/> property.
@ -23,6 +23,14 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Stretch> StretchProperty =
AvaloniaProperty.Register<Image, Stretch>(nameof(Stretch), Stretch.Uniform);
/// <summary>
/// Defines the <see cref="StretchDirection"/> property.
/// </summary>
public static readonly StyledProperty<StretchDirection> StretchDirectionProperty =
AvaloniaProperty.Register<Image, StretchDirection>(
nameof(StretchDirection),
StretchDirection.Both);
static Image()
{
AffectsRender<Image>(SourceProperty, StretchProperty);
@ -30,9 +38,9 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets or sets the bitmap image that will be displayed.
/// Gets or sets the image that will be displayed.
/// </summary>
public IBitmap Source
public IImage Source
{
get { return GetValue(SourceProperty); }
set { SetValue(SourceProperty, value); }
@ -43,10 +51,19 @@ namespace Avalonia.Controls
/// </summary>
public Stretch Stretch
{
get { return (Stretch)GetValue(StretchProperty); }
get { return GetValue(StretchProperty); }
set { SetValue(StretchProperty, value); }
}
/// <summary>
/// Gets or sets a value controlling in what direction the image will be stretched.
/// </summary>
public StretchDirection StretchDirection
{
get { return GetValue(StretchDirectionProperty); }
set { SetValue(StretchDirectionProperty, value); }
}
/// <summary>
/// Renders the control.
/// </summary>
@ -58,8 +75,8 @@ namespace Avalonia.Controls
if (source != null)
{
Rect viewPort = new Rect(Bounds.Size);
Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize);
Size sourceSize = source.Size;
Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);
Size scaledSize = sourceSize * scale;
Rect destRect = viewPort
.CenterRect(new Rect(scaledSize))
@ -69,7 +86,7 @@ namespace Avalonia.Controls
var interpolationMode = RenderOptions.GetBitmapInterpolationMode(this);
context.DrawImage(source, 1, sourceRect, destRect, interpolationMode);
context.DrawImage(source, sourceRect, destRect, interpolationMode);
}
}
@ -85,15 +102,7 @@ namespace Avalonia.Controls
if (source != null)
{
Size sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
if (double.IsInfinity(availableSize.Width) || double.IsInfinity(availableSize.Height))
{
result = sourceSize;
}
else
{
result = Stretch.CalculateSize(availableSize, sourceSize);
}
result = Stretch.CalculateSize(availableSize, source.Size, StretchDirection);
}
return result;
@ -106,7 +115,7 @@ namespace Avalonia.Controls
if (source != null)
{
var sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
var sourceSize = source.Size;
var result = Stretch.CalculateSize(finalSize, sourceSize);
return result;
}

2
src/Avalonia.Controls/Remote/RemoteWidget.cs

@ -83,7 +83,7 @@ namespace Avalonia.Controls.Remote
Marshal.Copy(_lastFrame.Data, y * _lastFrame.Stride,
new IntPtr(l.Address.ToInt64() + l.RowBytes * y), lineLen);
}
context.DrawImage(_bitmap, 1, new Rect(0, 0, _bitmap.PixelSize.Width, _bitmap.PixelSize.Height),
context.DrawImage(_bitmap, new Rect(0, 0, _bitmap.PixelSize.Width, _bitmap.PixelSize.Height),
new Rect(Bounds.Size));
}
base.Render(context);

6
src/Avalonia.Controls/TextBox.cs

@ -390,8 +390,10 @@ namespace Avalonia.Controls
{
return;
}
_undoRedoHelper.Snapshot();
HandleTextInput(text);
_undoRedoHelper.Snapshot();
}
protected override void OnKeyDown(KeyEventArgs e)
@ -401,12 +403,12 @@ namespace Avalonia.Controls
bool movement = false;
bool selection = false;
bool handled = false;
var modifiers = e.Modifiers;
var modifiers = e.KeyModifiers;
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
bool DetectSelection() => e.Modifiers.HasFlag(keymap.SelectionModifiers);
bool DetectSelection() => e.KeyModifiers.HasFlag(keymap.SelectionModifiers);
if (Match(keymap.SelectAll))
{

14
src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs

@ -4,14 +4,14 @@ namespace Avalonia.Input.Platform
{
public class PlatformHotkeyConfiguration
{
public PlatformHotkeyConfiguration() : this(InputModifiers.Control)
public PlatformHotkeyConfiguration() : this(KeyModifiers.Control)
{
}
public PlatformHotkeyConfiguration(InputModifiers commandModifiers,
InputModifiers selectionModifiers = InputModifiers.Shift,
InputModifiers wholeWordTextActionModifiers = InputModifiers.Control)
public PlatformHotkeyConfiguration(KeyModifiers commandModifiers,
KeyModifiers selectionModifiers = KeyModifiers.Shift,
KeyModifiers wholeWordTextActionModifiers = KeyModifiers.Control)
{
CommandModifiers = commandModifiers;
SelectionModifiers = selectionModifiers;
@ -75,9 +75,9 @@ namespace Avalonia.Input.Platform
};
}
public InputModifiers CommandModifiers { get; set; }
public InputModifiers WholeWordTextActionModifiers { get; set; }
public InputModifiers SelectionModifiers { get; set; }
public KeyModifiers CommandModifiers { get; set; }
public KeyModifiers WholeWordTextActionModifiers { get; set; }
public KeyModifiers SelectionModifiers { get; set; }
public List<KeyGesture> Copy { get; set; }
public List<KeyGesture> Cut { get; set; }
public List<KeyGesture> Paste { get; set; }

2
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -101,7 +101,7 @@ namespace Avalonia.Native
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind<IWindowingPlatformGlFeature>().ToConstant(new GlPlatformFeature(_factory.ObtainGlFeature()))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider());
}

14
src/Avalonia.Styling/Styling/ISetStyleParent.cs → src/Avalonia.Styling/Controls/ISetResourceParent.cs

@ -1,29 +1,27 @@
using Avalonia.Controls;
namespace Avalonia.Styling
namespace Avalonia.Controls
{
/// <summary>
/// Defines an interface through which a <see cref="Style"/>'s parent can be set.
/// Defines an interface through which an <see cref="IResourceNode"/>'s parent can be set.
/// </summary>
/// <remarks>
/// You should not usually need to use this interface - it is for internal use only.
/// </remarks>
public interface ISetStyleParent : IStyle
public interface ISetResourceParent : IResourceNode
{
/// <summary>
/// Sets the style parent.
/// Sets the resource parent.
/// </summary>
/// <param name="parent">The parent.</param>
void SetParent(IResourceNode parent);
/// <summary>
/// Notifies the style that a change has been made to resources that apply to it.
/// Notifies the resource node that a change has been made to the resources in its parent.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// This method will be called automatically by the framework, you should not need to call
/// this method yourself.
/// </remarks>
void NotifyResourcesChanged(ResourcesChangedEventArgs e);
void ParentResourcesChanged(ResourcesChangedEventArgs e);
}
}

62
src/Avalonia.Styling/Controls/ResourceDictionary.cs

@ -12,8 +12,12 @@ namespace Avalonia.Controls
/// <summary>
/// An indexed dictionary of resources.
/// </summary>
public class ResourceDictionary : AvaloniaDictionary<object, object>, IResourceDictionary
public class ResourceDictionary : AvaloniaDictionary<object, object>,
IResourceDictionary,
IResourceNode,
ISetResourceParent
{
private IResourceNode _parent;
private AvaloniaList<IResourceProvider> _mergedDictionaries;
/// <summary>
@ -39,6 +43,12 @@ namespace Avalonia.Controls
_mergedDictionaries.ForEachItem(
x =>
{
if (x is ISetResourceParent setParent)
{
setParent.SetParent(this);
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
{
OnResourcesChanged();
@ -48,11 +58,18 @@ namespace Avalonia.Controls
},
x =>
{
if (x is ISetResourceParent setParent)
{
setParent.SetParent(null);
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
{
OnResourcesChanged();
}
(x as ISetResourceParent)?.SetParent(null);
x.ResourcesChanged -= MergedDictionaryResourcesChanged;
},
() => { });
@ -68,6 +85,27 @@ namespace Avalonia.Controls
get => Count > 0 || (_mergedDictionaries?.Any(x => x.HasResources) ?? false);
}
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => _parent;
/// <inheritdoc/>
void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
{
NotifyMergedDictionariesResourcesChanged(e);
ResourcesChanged?.Invoke(this, e);
}
/// <inheritdoc/>
void ISetResourceParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{
throw new InvalidOperationException("The ResourceDictionary already has a parent.");
}
_parent = parent;
}
/// <inheritdoc/>
public bool TryGetResource(object key, out object value)
{
@ -95,7 +133,27 @@ namespace Avalonia.Controls
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) => OnResourcesChanged();
private void NotifyMergedDictionariesResourcesChanged(ResourcesChangedEventArgs e)
{
if (_mergedDictionaries != null)
{
for (var i = _mergedDictionaries.Count - 1; i >= 0; --i)
{
if (_mergedDictionaries[i] is ISetResourceParent merged)
{
merged.ParentResourcesChanged(e);
}
}
}
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
var ev = new ResourcesChangedEventArgs();
NotifyMergedDictionariesResourcesChanged(ev);
OnResourcesChanged();
}
private void MergedDictionaryResourcesChanged(object sender, ResourcesChangedEventArgs e) => OnResourcesChanged();
}
}

4
src/Avalonia.Styling/StyledElement.cs

@ -223,13 +223,13 @@ namespace Avalonia
{
if (_styles != null)
{
(_styles as ISetStyleParent)?.SetParent(null);
(_styles as ISetResourceParent)?.SetParent(null);
_styles.ResourcesChanged -= ThisResourcesChanged;
}
_styles = value;
if (value is ISetStyleParent setParent && setParent.ResourceParent == null)
if (value is ISetResourceParent setParent && setParent.ResourceParent == null)
{
setParent.SetParent(this);
}

12
src/Avalonia.Styling/Styling/Style.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling
/// <summary>
/// Defines a style.
/// </summary>
public class Style : AvaloniaObject, IStyle, ISetStyleParent
public class Style : AvaloniaObject, IStyle, ISetResourceParent
{
private static Dictionary<IStyleable, CompositeDisposable> _applied =
new Dictionary<IStyleable, CompositeDisposable>();
@ -59,16 +59,16 @@ namespace Avalonia.Styling
if (_resources != null)
{
hadResources = _resources.Count > 0;
hadResources = _resources.HasResources;
_resources.ResourcesChanged -= ResourceDictionaryChanged;
}
_resources = value;
_resources.ResourcesChanged += ResourceDictionaryChanged;
if (hadResources || _resources.Count > 0)
if (hadResources || _resources.HasResources)
{
((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
((ISetResourceParent)this).ParentResourcesChanged(new ResourcesChangedEventArgs());
}
}
}
@ -194,13 +194,13 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)
void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);
}
/// <inheritdoc/>
void ISetStyleParent.SetParent(IResourceNode parent)
void ISetResourceParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{

20
src/Avalonia.Styling/Styling/Styles.cs

@ -14,7 +14,7 @@ namespace Avalonia.Styling
/// <summary>
/// A style that consists of a number of child styles.
/// </summary>
public class Styles : AvaloniaObject, IAvaloniaList<IStyle>, IStyle, ISetStyleParent
public class Styles : AvaloniaObject, IAvaloniaList<IStyle>, IStyle, ISetResourceParent
{
private IResourceNode _parent;
private IResourceDictionary _resources;
@ -27,10 +27,10 @@ namespace Avalonia.Styling
_styles.ForEachItem(
x =>
{
if (x.ResourceParent == null && x is ISetStyleParent setParent)
if (x.ResourceParent == null && x is ISetResourceParent setParent)
{
setParent.SetParent(this);
setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs());
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
@ -43,10 +43,10 @@ namespace Avalonia.Styling
},
x =>
{
if (x.ResourceParent == this && x is ISetStyleParent setParent)
if (x.ResourceParent == this && x is ISetResourceParent setParent)
{
setParent.SetParent(null);
setParent.NotifyResourcesChanged(new ResourcesChangedEventArgs());
setParent.ParentResourcesChanged(new ResourcesChangedEventArgs());
}
if (x.HasResources)
@ -98,7 +98,7 @@ namespace Avalonia.Styling
if (hadResources || _resources.Count > 0)
{
((ISetStyleParent)this).NotifyResourcesChanged(new ResourcesChangedEventArgs());
((ISetResourceParent)this).ParentResourcesChanged(new ResourcesChangedEventArgs());
}
}
}
@ -246,7 +246,7 @@ namespace Avalonia.Styling
IEnumerator IEnumerable.GetEnumerator() => _styles.GetEnumerator();
/// <inheritdoc/>
void ISetStyleParent.SetParent(IResourceNode parent)
void ISetResourceParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{
@ -257,7 +257,7 @@ namespace Avalonia.Styling
}
/// <inheritdoc/>
void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)
void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);
}
@ -266,7 +266,7 @@ namespace Avalonia.Styling
{
foreach (var child in this)
{
(child as ISetStyleParent)?.NotifyResourcesChanged(e);
(child as ISetResourceParent)?.ParentResourcesChanged(e);
}
ResourcesChanged?.Invoke(this, e);
@ -280,7 +280,7 @@ namespace Avalonia.Styling
{
if (foundSource)
{
(child as ISetStyleParent)?.NotifyResourcesChanged(e);
(child as ISetResourceParent)?.ParentResourcesChanged(e);
}
foundSource |= child == sender;

6
src/Avalonia.Visuals/Media/Drawing.cs

@ -1,4 +1,6 @@
namespace Avalonia.Media
using Avalonia.Platform;
namespace Avalonia.Media
{
public abstract class Drawing : AvaloniaObject
{
@ -6,4 +8,4 @@
public abstract Rect GetBounds();
}
}
}

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

@ -74,18 +74,29 @@ namespace Avalonia.Media
public Matrix CurrentContainerTransform => _currentContainerTransform;
/// <summary>
/// Draws a bitmap image.
/// Draws an image.
/// </summary>
/// <param name="source">The bitmap image.</param>
/// <param name="opacity">The opacity to draw with.</param>
/// <param name="source">The image.</param>
/// <param name="rect">The rect in the output to draw to.</param>
public void DrawImage(IImage source, Rect rect)
{
Contract.Requires<ArgumentNullException>(source != null);
DrawImage(source, new Rect(source.Size), rect);
}
/// <summary>
/// Draws an image.
/// </summary>
/// <param name="source">The image.</param>
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public void DrawImage(IBitmap source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default)
public void DrawImage(IImage source, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = default)
{
Contract.Requires<ArgumentNullException>(source != null);
PlatformImpl.DrawImage(source.PlatformImpl, opacity, sourceRect, destRect, bitmapInterpolationMode);
source.Draw(this, sourceRect, destRect, bitmapInterpolationMode);
}
/// <summary>

3
src/Avalonia.Visuals/Media/DrawingGroup.cs

@ -1,5 +1,6 @@
using Avalonia.Collections;
using Avalonia.Metadata;
using Avalonia.Platform;
namespace Avalonia.Media
{
@ -55,4 +56,4 @@ namespace Avalonia.Media
return rect;
}
}
}
}

81
src/Avalonia.Visuals/Media/DrawingImage.cs

@ -0,0 +1,81 @@
using System;
using Avalonia.Metadata;
using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Media
{
/// <summary>
/// An <see cref="IImage"/> that uses a <see cref="Drawing"/> for content.
/// </summary>
public class DrawingImage : AvaloniaObject, IImage, IAffectsRender
{
/// <summary>
/// Defines the <see cref="Drawing"/> property.
/// </summary>
public static readonly StyledProperty<Drawing> DrawingProperty =
AvaloniaProperty.Register<DrawingImage, Drawing>(nameof(Drawing));
/// <inheritdoc/>
public event EventHandler Invalidated;
/// <summary>
/// Gets or sets the drawing content.
/// </summary>
[Content]
public Drawing Drawing
{
get => GetValue(DrawingProperty);
set => SetValue(DrawingProperty, value);
}
/// <inheritdoc/>
public Size Size => Drawing?.GetBounds().Size ?? default;
/// <inheritdoc/>
void IImage.Draw(
DrawingContext context,
Rect sourceRect,
Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode)
{
var drawing = Drawing;
if (drawing == null)
{
return;
}
var bounds = drawing.GetBounds();
var scale = Matrix.CreateScale(
destRect.Width / sourceRect.Width,
destRect.Height / sourceRect.Height);
var translate = Matrix.CreateTranslation(
-sourceRect.X + destRect.X - bounds.X,
-sourceRect.Y + destRect.Y - bounds.Y);
using (context.PushClip(destRect))
using (context.PushPreTransform(translate * scale))
{
Drawing?.Draw(context);
}
}
/// <inheritdoc/>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == DrawingProperty)
{
RaiseInvalidated(EventArgs.Empty);
}
}
/// <summary>
/// Raises the <see cref="Invalidated"/> event.
/// </summary>
/// <param name="e">The event args.</param>
protected void RaiseInvalidated(EventArgs e) => Invalidated?.Invoke(this, e);
}
}

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

@ -1,10 +1,13 @@
namespace Avalonia.Media
using Avalonia.Metadata;
namespace Avalonia.Media
{
public class GeometryDrawing : Drawing
{
public static readonly StyledProperty<Geometry> GeometryProperty =
AvaloniaProperty.Register<GeometryDrawing, Geometry>(nameof(Geometry));
[Content]
public Geometry Geometry
{
get => GetValue(GeometryProperty);

29
src/Avalonia.Visuals/Media/IImage.cs

@ -0,0 +1,29 @@
using Avalonia.Platform;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Media
{
/// <summary>
/// Represents a raster or vector image.
/// </summary>
public interface IImage
{
/// <summary>
/// Gets the size of the image, in device independent pixels.
/// </summary>
Size Size { get; }
/// <summary>
/// Draws the image to a <see cref="DrawingContext"/>.
/// </summary>
/// <param name="context">The drawing context.</param>
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
void Draw(
DrawingContext context,
Rect sourceRect,
Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode);
}
}

20
src/Avalonia.Visuals/Media/Imaging/Bitmap.cs

@ -5,6 +5,7 @@ using System;
using System.IO;
using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.Visuals.Media.Imaging;
namespace Avalonia.Media.Imaging
{
@ -94,9 +95,28 @@ namespace Avalonia.Media.Imaging
PlatformImpl.Item.Save(fileName);
}
/// <summary>
/// Saves the bitmap to a stream.
/// </summary>
/// <param name="stream">The stream.</param>
public void Save(Stream stream)
{
PlatformImpl.Item.Save(stream);
}
/// <inheritdoc/>
void IImage.Draw(
DrawingContext context,
Rect sourceRect,
Rect destRect,
BitmapInterpolationMode bitmapInterpolationMode)
{
context.PlatformImpl.DrawBitmap(
PlatformImpl,
1,
sourceRect,
destRect,
bitmapInterpolationMode);
}
}
}

11
src/Avalonia.Visuals/Media/Imaging/IBitmap.cs

@ -11,7 +11,7 @@ namespace Avalonia.Media.Imaging
/// <summary>
/// Represents a bitmap image.
/// </summary>
public interface IBitmap : IDisposable
public interface IBitmap : IImage, IDisposable
{
/// <summary>
/// Gets the dots per inch (DPI) of the image.
@ -32,15 +32,6 @@ namespace Avalonia.Media.Imaging
/// </summary>
IRef<IBitmapImpl> PlatformImpl { get; }
/// <summary>
/// Gets the size of the image, in device independent pixels.
/// </summary>
/// <remarks>
/// Note that Skia does not currently support reading the DPI of an image so this value
/// will equal <see cref="PixelSize"/> on Skia.
/// </remarks>
Size Size { get; }
/// <summary>
/// Saves the bitmap to a file.
/// </summary>

90
src/Avalonia.Visuals/Media/MediaExtensions.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Utilities;
namespace Avalonia.Media
{
@ -16,24 +17,82 @@ namespace Avalonia.Media
/// <param name="stretch">The stretch mode.</param>
/// <param name="destinationSize">The size of the destination viewport.</param>
/// <param name="sourceSize">The size of the source.</param>
/// <param name="stretchDirection">The stretch direction.</param>
/// <returns>A vector with the X and Y scaling factors.</returns>
public static Vector CalculateScaling(this Stretch stretch, Size destinationSize, Size sourceSize)
public static Vector CalculateScaling(
this Stretch stretch,
Size destinationSize,
Size sourceSize,
StretchDirection stretchDirection = StretchDirection.Both)
{
double scaleX = 1;
double scaleY = 1;
var scaleX = 1.0;
var scaleY = 1.0;
if (stretch != Stretch.None)
bool isConstrainedWidth = !double.IsPositiveInfinity(destinationSize.Width);
bool isConstrainedHeight = !double.IsPositiveInfinity(destinationSize.Height);
if ((stretch == Stretch.Uniform || stretch == Stretch.UniformToFill || stretch == Stretch.Fill)
&& (isConstrainedWidth || isConstrainedHeight))
{
scaleX = destinationSize.Width / sourceSize.Width;
scaleY = destinationSize.Height / sourceSize.Height;
// Compute scaling factors for both axes
scaleX = MathUtilities.IsZero(sourceSize.Width) ? 0.0 : destinationSize.Width / sourceSize.Width;
scaleY = MathUtilities.IsZero(sourceSize.Height) ? 0.0 : destinationSize.Height / sourceSize.Height;
switch (stretch)
if (!isConstrainedWidth)
{
scaleX = scaleY;
}
else if (!isConstrainedHeight)
{
scaleY = scaleX;
}
else
{
case Stretch.Uniform:
scaleX = scaleY = Math.Min(scaleX, scaleY);
// If not preserving aspect ratio, then just apply transform to fit
switch (stretch)
{
case Stretch.Uniform:
// Find minimum scale that we use for both axes
double minscale = scaleX < scaleY ? scaleX : scaleY;
scaleX = scaleY = minscale;
break;
case Stretch.UniformToFill:
// Find maximum scale that we use for both axes
double maxscale = scaleX > scaleY ? scaleX : scaleY;
scaleX = scaleY = maxscale;
break;
case Stretch.Fill:
// We already computed the fill scale factors above, so just use them
break;
}
}
// Apply stretch direction by bounding scales.
// In the uniform case, scaleX=scaleY, so this sort of clamping will maintain aspect ratio
// In the uniform fill case, we have the same result too.
// In the fill case, note that we change aspect ratio, but that is okay
switch (stretchDirection)
{
case StretchDirection.UpOnly:
if (scaleX < 1.0)
scaleX = 1.0;
if (scaleY < 1.0)
scaleY = 1.0;
break;
case StretchDirection.DownOnly:
if (scaleX > 1.0)
scaleX = 1.0;
if (scaleY > 1.0)
scaleY = 1.0;
break;
case Stretch.UniformToFill:
scaleX = scaleY = Math.Max(scaleX, scaleY);
case StretchDirection.Both:
break;
default:
break;
}
}
@ -47,10 +106,15 @@ namespace Avalonia.Media
/// <param name="stretch">The stretch mode.</param>
/// <param name="destinationSize">The size of the destination viewport.</param>
/// <param name="sourceSize">The size of the source.</param>
/// <param name="stretchDirection">The stretch direction.</param>
/// <returns>The size of the stretched source.</returns>
public static Size CalculateSize(this Stretch stretch, Size destinationSize, Size sourceSize)
public static Size CalculateSize(
this Stretch stretch,
Size destinationSize,
Size sourceSize,
StretchDirection stretchDirection = StretchDirection.Both)
{
return sourceSize * stretch.CalculateScaling(destinationSize, sourceSize);
return sourceSize * stretch.CalculateScaling(destinationSize, sourceSize, stretchDirection);
}
}
}

25
src/Avalonia.Visuals/Media/StretchDirection.cs

@ -0,0 +1,25 @@
namespace Avalonia.Media
{
/// <summary>
/// Describes the type of scaling that can be used when scaling content.
/// </summary>
public enum StretchDirection
{
/// <summary>
/// Only scales the content upwards when the content is smaller than the available space.
/// If the content is larger, no scaling downwards is done.
/// </summary>
UpOnly,
/// <summary>
/// Only scales the content downwards when the content is larger than the available space.
/// If the content is smaller, no scaling upwards is done.
/// </summary>
DownOnly,
/// <summary>
/// Always stretches to fit the available space according to the stretch mode.
/// </summary>
Both,
}
}

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

@ -33,7 +33,7 @@ namespace Avalonia.Platform
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default);
void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default);
/// <summary>
/// Draws a bitmap image.
@ -42,7 +42,7 @@ namespace Avalonia.Platform
/// <param name="opacityMask">The opacity mask to draw with.</param>
/// <param name="opacityMaskRect">The destination rect for the opacity mask.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect);
void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect);
/// <summary>
/// Draws a line.

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

@ -469,11 +469,11 @@ namespace Avalonia.Rendering
if (layer.OpacityMask == null)
{
context.DrawImage(bitmap, layer.Opacity, sourceRect, clientRect);
context.DrawBitmap(bitmap, layer.Opacity, sourceRect, clientRect);
}
else
{
context.DrawImage(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
context.DrawBitmap(bitmap, layer.OpacityMask, layer.OpacityMaskRect, sourceRect);
}
if (layer.GeometryClip != null)
@ -485,7 +485,7 @@ namespace Avalonia.Rendering
if (_overlay != null)
{
var sourceRect = new Rect(0, 0, _overlay.Item.PixelSize.Width, _overlay.Item.PixelSize.Height);
context.DrawImage(_overlay, 0.5, sourceRect, clientRect);
context.DrawBitmap(_overlay, 0.5, sourceRect, clientRect);
}
if (DrawFps)

4
src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs

@ -307,7 +307,9 @@ namespace Avalonia.Rendering
if (!child.ClipToBounds || clipRect.Intersects(childBounds))
{
var childClipRect = clipRect.Translate(-childBounds.Position);
var childClipRect = child.RenderTransform == null
? clipRect.Translate(-childBounds.Position)
: clipRect;
Render(context, child, childClipRect);
}
else

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

@ -115,7 +115,7 @@ namespace Avalonia.Rendering.SceneGraph
}
/// <inheritdoc/>
public void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
{
var next = NextDrawAs<ImageNode>();
@ -130,7 +130,7 @@ namespace Avalonia.Rendering.SceneGraph
}
/// <inheritdoc/>
public void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect)
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect sourceRect)
{
// This method is currently only used to composite layers so shouldn't be called here.
throw new NotSupportedException();

2
src/Avalonia.Visuals/Rendering/SceneGraph/ImageNode.cs

@ -100,7 +100,7 @@ namespace Avalonia.Rendering.SceneGraph
public override void Render(IDrawingContextImpl context)
{
context.Transform = Transform;
context.DrawImage(Source, Opacity, SourceRect, DestRect, BitmapInterpolationMode);
context.DrawBitmap(Source, Opacity, SourceRect, DestRect, BitmapInterpolationMode);
}
/// <inheritdoc/>

18
src/Avalonia.X11/Glx/Glx.cs

@ -84,8 +84,24 @@ namespace Avalonia.X11.Glx
[GlEntryPoint("glGetError")]
public GlGetError GetError { get; }
public GlxInterface() : base(GlxGetProcAddress)
public GlxInterface() : base(SafeGetProcAddress)
{
}
// Ignores egl functions.
// On some Linux systems, glXGetProcAddress will return valid pointers for even EGL functions.
// This makes Skia try to load some data from EGL,
// which can then cause segmentation faults because they return garbage.
public static IntPtr SafeGetProcAddress(string proc, bool optional)
{
if (proc.StartsWith("egl", StringComparison.InvariantCulture))
{
return IntPtr.Zero;
}
return GlxConverted(proc, optional);
}
private static readonly Func<string, bool, IntPtr> GlxConverted = ConvertNative(GlxGetProcAddress);
}
}

2
src/Avalonia.X11/Glx/GlxDisplay.cs

@ -87,7 +87,7 @@ namespace Avalonia.X11.Glx
ImmediateContext.MakeCurrent();
var err = Glx.GetError();
GlInterface = new GlInterface(GlxInterface.GlxGetProcAddress);
GlInterface = new GlInterface(GlxInterface.SafeGetProcAddress);
if (GlInterface.Version == null)
throw new OpenGlException("GL version string is null, aborting");
if (GlInterface.Renderer == null)

2
src/Avalonia.X11/X11IconLoader.cs

@ -59,7 +59,7 @@ namespace Avalonia.X11
}
using(var rt = AvaloniaLocator.Current.GetService<IPlatformRenderInterface>().CreateRenderTarget(new[]{this}))
using (var ctx = rt.CreateDrawingContext(null))
ctx.DrawImage(bitmap.PlatformImpl, 1, new Rect(bitmap.Size),
ctx.DrawBitmap(bitmap.PlatformImpl, 1, new Rect(bitmap.Size),
new Rect(0, 0, _width, _height));
Data = new UIntPtr[_width * _height + 2];
Data[0] = new UIntPtr((uint)_width);

2
src/Avalonia.X11/X11Platform.cs

@ -44,7 +44,7 @@ namespace Avalonia.X11
.Bind<IPlatformThreadingInterface>().ToConstant(new X11PlatformThreading(this))
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Control))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Control))
.Bind<IKeyboardDevice>().ToFunc(() => KeyboardDevice)
.Bind<IStandardCursorFactory>().ToConstant(new X11CursorFactory(Display))
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))

3
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/BindingExtension.cs

@ -41,6 +41,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
StringFormat = StringFormat,
RelativeSource = RelativeSource,
DefaultAnchor = new WeakReference(GetDefaultAnchor(descriptorContext)),
TargetNullValue = TargetNullValue,
NameScope = new WeakReference<INameScope>(serviceProvider.GetService<INameScope>())
};
}
@ -86,5 +87,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
public string StringFormat { get; set; }
public RelativeSource RelativeSource { get; set; }
public object TargetNullValue { get; set; } = AvaloniaProperty.UnsetValue;
}
}

26
src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/ResourceInclude.cs

@ -7,8 +7,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
/// <summary>
/// Loads a resource dictionary from a specified URL.
/// </summary>
public class ResourceInclude :IResourceProvider
public class ResourceInclude : IResourceNode, ISetResourceParent
{
private IResourceNode _parent;
private Uri _baseUri;
private IResourceDictionary _loaded;
@ -26,6 +27,9 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
var loader = new AvaloniaXamlLoader();
_loaded = (IResourceDictionary)loader.Load(Source, _baseUri);
(_loaded as ISetResourceParent)?.SetParent(this);
_loaded.ResourcesChanged += ResourcesChanged;
if (_loaded.HasResources)
{
ResourcesChanged?.Invoke(this, new ResourcesChangedEventArgs());
@ -44,12 +48,32 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions
/// <inhertidoc/>
bool IResourceProvider.HasResources => Loaded.HasResources;
/// <inhertidoc/>
IResourceNode IResourceNode.ResourceParent => _parent;
/// <inhertidoc/>
bool IResourceProvider.TryGetResource(object key, out object value)
{
return Loaded.TryGetResource(key, out value);
}
/// <inhertidoc/>
void ISetResourceParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{
throw new InvalidOperationException("The ResourceInclude already has a parent.");
}
_parent = parent;
}
/// <inhertidoc/>
void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
{
(_loaded as ISetResourceParent)?.ParentResourcesChanged(e);
}
public ResourceInclude ProvideValue(IServiceProvider serviceProvider)
{
var tdc = (ITypeDescriptorContext)serviceProvider;

10
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@ -10,7 +10,7 @@ namespace Avalonia.Markup.Xaml.Styling
/// <summary>
/// Includes a style from a URL.
/// </summary>
public class StyleInclude : IStyle, ISetStyleParent
public class StyleInclude : IStyle, ISetResourceParent
{
private Uri _baseUri;
private IStyle _loaded;
@ -53,7 +53,7 @@ namespace Avalonia.Markup.Xaml.Styling
{
var loader = new AvaloniaXamlLoader();
_loaded = (IStyle)loader.Load(Source, _baseUri);
(_loaded as ISetStyleParent)?.SetParent(this);
(_loaded as ISetResourceParent)?.SetParent(this);
}
return _loaded;
@ -89,13 +89,13 @@ namespace Avalonia.Markup.Xaml.Styling
public bool TryGetResource(object key, out object value) => Loaded.TryGetResource(key, out value);
/// <inheritdoc/>
void ISetStyleParent.NotifyResourcesChanged(ResourcesChangedEventArgs e)
void ISetResourceParent.ParentResourcesChanged(ResourcesChangedEventArgs e)
{
(Loaded as ISetStyleParent)?.NotifyResourcesChanged(e);
(Loaded as ISetResourceParent)?.ParentResourcesChanged(e);
}
/// <inheritdoc/>
void ISetStyleParent.SetParent(IResourceNode parent)
void ISetResourceParent.SetParent(IResourceNode parent)
{
if (_parent != null && parent != null)
{

2
src/Markup/Avalonia.Markup.Xaml/XamlIl/CompilerExtensions/AvaloniaXamlIlLanguage.cs

@ -99,7 +99,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
void Add(string type, string conv)
=> AddType(typeSystem.GetType(type), typeSystem.GetType(conv));
Add("Avalonia.Media.Imaging.IBitmap","Avalonia.Markup.Xaml.Converters.BitmapTypeConverter");
Add("Avalonia.Media.IImage","Avalonia.Markup.Xaml.Converters.BitmapTypeConverter");
var ilist = typeSystem.GetType("System.Collections.Generic.IList`1");
AddType(ilist.MakeGenericType(typeSystem.GetType("Avalonia.Point")),
typeSystem.GetType("Avalonia.Markup.Xaml.Converters.PointsListTypeConverter"));

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

@ -110,7 +110,7 @@ namespace Avalonia.Skia
}
/// <inheritdoc />
public void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
{
var drawableImage = (IDrawableBitmapImpl)source.Item;
var s = sourceRect.ToSKRect();
@ -146,10 +146,10 @@ namespace Avalonia.Skia
}
/// <inheritdoc />
public void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
PushOpacityMask(opacityMask, opacityMaskRect);
DrawImage(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default);
DrawBitmap(source, 1, new Rect(0, 0, source.Item.PixelSize.Width, source.Item.PixelSize.Height), destRect, BitmapInterpolationMode.Default);
PopOpacityMask();
}
@ -437,7 +437,7 @@ namespace Avalonia.Skia
context.Clear(Colors.Transparent);
context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform;
context.DrawImage(
context.DrawBitmap(
RefCountable.CreateUnownedNotClonable(tileBrushImage),
1,
sourceRect,

5
src/Skia/Avalonia.Skia/FontManagerImpl.cs

@ -89,12 +89,15 @@ namespace Avalonia.Skia
if (typeface.FontFamily.Key == null)
{
var defaultName = SKTypeface.Default.FamilyName;
foreach (var familyName in typeface.FontFamily.FamilyNames)
{
skTypeface = SKTypeface.FromFamilyName(familyName, (SKFontStyleWeight)typeface.Weight,
SKFontStyleWidth.Normal, (SKFontStyleSlant)typeface.Style);
if (skTypeface == SKTypeface.Default)
if (!skTypeface.FamilyName.Equals(familyName, StringComparison.Ordinal) &&
defaultName.Equals(skTypeface.FamilyName, StringComparison.Ordinal))
{
continue;
}

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

@ -109,7 +109,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="sourceRect">The rect in the image to draw.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
/// <param name="bitmapInterpolationMode">The bitmap interpolation mode.</param>
public void DrawImage(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
public void DrawBitmap(IRef<IBitmapImpl> source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode)
{
using (var d2d = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
{
@ -149,7 +149,7 @@ namespace Avalonia.Direct2D1.Media
/// <param name="opacityMask">The opacity mask to draw with.</param>
/// <param name="opacityMaskRect">The destination rect for the opacity mask.</param>
/// <param name="destRect">The rect in the output to draw to.</param>
public void DrawImage(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
public void DrawBitmap(IRef<IBitmapImpl> source, IBrush opacityMask, Rect opacityMaskRect, Rect destRect)
{
using (var d2dSource = ((BitmapImpl)source.Item).GetDirect2DBitmap(_deviceContext))
using (var sourceBrush = new BitmapBrush(_deviceContext, d2dSource.Value))

2
src/Windows/Avalonia.Direct2D1/Media/ImageBrushImpl.cs

@ -107,7 +107,7 @@ namespace Avalonia.Direct2D1.Media
context.PushClip(calc.IntermediateClip);
context.Transform = calc.IntermediateTransform;
context.DrawImage(RefCountable.CreateUnownedNotClonable(bitmap), 1, rect, rect, _bitmapInterpolationMode);
context.DrawBitmap(RefCountable.CreateUnownedNotClonable(bitmap), 1, rect, rect, _bitmapInterpolationMode);
context.PopClip();
}

3
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs

@ -30,7 +30,7 @@ namespace Avalonia.Direct2D1.Media
_direct2DBitmap = d2DBitmap ?? throw new ArgumentNullException(nameof(d2DBitmap));
}
public override Vector Dpi => _direct2DBitmap.DotsPerInch.ToAvaloniaVector();
public override Vector Dpi => new Vector(96, 96);
public override PixelSize PixelSize => _direct2DBitmap.PixelSize.ToAvalonia();
public override void Dispose()
@ -58,3 +58,4 @@ namespace Avalonia.Direct2D1.Media
}
}
}
;

2
src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs

@ -58,7 +58,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
{
using (var dc = wic.CreateDrawingContext(null))
{
dc.DrawImage(
dc.DrawBitmap(
RefCountable.CreateUnownedNotClonable(this),
1,
new Rect(PixelSize.ToSizeWithDpi(Dpi.X)),

14
src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs

@ -26,6 +26,7 @@ namespace Avalonia.Direct2D1.Media
using (BitmapDecoder decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, fileName, DecodeOptions.CacheOnDemand))
{
WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnDemand);
Dpi = new Vector(96, 96);
}
}
@ -39,6 +40,7 @@ namespace Avalonia.Direct2D1.Media
_decoder = new BitmapDecoder(Direct2D1Platform.ImagingFactory, stream, DecodeOptions.CacheOnLoad);
WicImpl = new Bitmap(Direct2D1Platform.ImagingFactory, _decoder.GetFrame(0), BitmapCreateCacheOption.CacheOnLoad);
Dpi = new Vector(96, 96);
}
/// <summary>
@ -62,6 +64,7 @@ namespace Avalonia.Direct2D1.Media
pixelFormat.Value.ToWic(),
BitmapCreateCacheOption.CacheOnLoad);
WicImpl.SetResolution(dpi.X, dpi.Y);
Dpi = dpi;
}
public WicBitmapImpl(APixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
@ -70,6 +73,8 @@ namespace Avalonia.Direct2D1.Media
WicImpl.SetResolution(dpi.X, dpi.Y);
PixelFormat = format;
Dpi = dpi;
using (var l = WicImpl.Lock(BitmapLockFlags.Write))
{
for (var row = 0; row < size.Height; row++)
@ -82,14 +87,7 @@ namespace Avalonia.Direct2D1.Media
}
}
public override Vector Dpi
{
get
{
WicImpl.GetResolution(out double x, out double y);
return new Vector(x, y);
}
}
public override Vector Dpi { get; }
public override PixelSize PixelSize => WicImpl.Size.ToAvalonia();

70
tests/Avalonia.Controls.UnitTests/ImageTests.cs

@ -13,7 +13,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Measure_Should_Return_Correct_Size_For_No_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.None;
target.Source = bitmap;
@ -26,7 +26,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Measure_Should_Return_Correct_Size_For_Fill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.Fill;
target.Source = bitmap;
@ -39,7 +39,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Measure_Should_Return_Correct_Size_For_Uniform_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.Uniform;
target.Source = bitmap;
@ -52,7 +52,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Measure_Should_Return_Correct_Size_For_UniformToFill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.UniformToFill;
target.Source = bitmap;
@ -62,10 +62,59 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(50, 50), target.DesiredSize);
}
[Fact]
public void Measure_Should_Return_Correct_Size_With_StretchDirection_DownOnly()
{
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.StretchDirection = StretchDirection.DownOnly;
target.Source = bitmap;
target.Measure(new Size(150, 150));
Assert.Equal(new Size(50, 100), target.DesiredSize);
}
[Fact]
public void Measure_Should_Return_Correct_Size_For_Infinite_Height()
{
var bitmap = CreateBitmap(50, 100);
var image = new Image();
image.Source = bitmap;
image.Measure(new Size(200, double.PositiveInfinity));
Assert.Equal(new Size(200, 400), image.DesiredSize);
}
[Fact]
public void Measure_Should_Return_Correct_Size_For_Infinite_Width()
{
var bitmap = CreateBitmap(50, 100);
var image = new Image();
image.Source = bitmap;
image.Measure(new Size(double.PositiveInfinity, 400));
Assert.Equal(new Size(200, 400), image.DesiredSize);
}
[Fact]
public void Measure_Should_Return_Correct_Size_For_Infinite_Width_Height()
{
var bitmap = CreateBitmap(50, 100);
var image = new Image();
image.Source = bitmap;
image.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
Assert.Equal(new Size(50, 100), image.DesiredSize);
}
[Fact]
public void Arrange_Should_Return_Correct_Size_For_No_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.None;
target.Source = bitmap;
@ -79,7 +128,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Arrange_Should_Return_Correct_Size_For_Fill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.Fill;
target.Source = bitmap;
@ -93,7 +142,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Arrange_Should_Return_Correct_Size_For_Uniform_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.Uniform;
target.Source = bitmap;
@ -107,7 +156,7 @@ namespace Avalonia.Controls.UnitTests
[Fact]
public void Arrange_Should_Return_Correct_Size_For_UniformToFill_Stretch()
{
var bitmap = Mock.Of<IBitmap>(x => x.PixelSize == new PixelSize(50, 100));
var bitmap = CreateBitmap(50, 100);
var target = new Image();
target.Stretch = Stretch.UniformToFill;
target.Source = bitmap;
@ -117,5 +166,10 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(25, 100), target.Bounds.Size);
}
private IBitmap CreateBitmap(int width, int height)
{
return Mock.Of<IBitmap>(x => x.Size == new Size(width, height));
}
}
}

24
tests/Avalonia.Direct2D1.UnitTests/Media/FontManagerImplTests.cs

@ -36,6 +36,30 @@ namespace Avalonia.Direct2D1.UnitTests.Media
}
}
[Fact]
public void Should_Create_Typeface_From_Fallback_Bold()
{
using (AvaloniaLocator.EnterScope())
{
Direct2D1Platform.Initialize();
var fontManager = new FontManagerImpl();
var defaultName = fontManager.GetDefaultFontFamilyName();
var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface(
new Typeface(new FontFamily("A, B, Arial"), FontWeight.Bold));
var font = glyphTypeface.DWFont;
Assert.Equal("Arial", font.FontFamily.FamilyNames.GetString(0));
Assert.Equal(SharpDX.DirectWrite.FontWeight.Bold, font.Weight);
Assert.Equal(SharpDX.DirectWrite.FontStyle.Normal, font.Style);
}
}
[Fact]
public void Should_Create_Typeface_For_Unknown_Font()
{

53
tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/BindingExtensionTests.cs

@ -39,6 +39,59 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
[Fact]
public void BindingExtension_Binds_To_TargetNullValue()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<x:String x:Key='text'>foobar</x:String>
</Window.Resources>
<TextBlock Name='textBlock' Text='{Binding Foo, TargetNullValue={StaticResource text}}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
window.DataContext = new FooBar();
window.Show();
Assert.Equal("foobar", textBlock.Text);
}
}
[Fact]
public void BindingExtension_TargetNullValue_UnsetByDefault()
{
using (StyledWindow())
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<TextBlock Name='textBlock' IsVisible='{Binding Foo, Converter={x:Static ObjectConverters.IsNotNull}}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var textBlock = window.FindControl<TextBlock>("textBlock");
window.DataContext = new FooBar();
window.Show();
Assert.Equal(false, textBlock.IsVisible);
}
}
private class FooBar
{
public object Foo { get; } = null;
}
private IDisposable StyledWindow(params (string, string)[] assets)
{
var services = TestServices.StyledWindow.With(

123
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/ResourceDictionaryTests.cs

@ -0,0 +1,123 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.UnitTests;
using Xunit;
namespace Avalonia.Markup.Xaml.UnitTests.Xaml
{
public class ResourceDictionaryTests : XamlTestBase
{
[Fact]
public void StaticResource_Works_In_ResourceDictionary()
{
using (StyledWindow())
{
var xaml = @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Color x:Key='Red'>Red</Color>
<SolidColorBrush x:Key='RedBrush' Color='{StaticResource Red}'/>
</ResourceDictionary>";
var loader = new AvaloniaXamlLoader();
var resources = (ResourceDictionary)loader.Load(xaml);
var brush = (SolidColorBrush)resources["RedBrush"];
Assert.Equal(Colors.Red, brush.Color);
}
}
[Fact]
public void DynamicResource_Works_In_ResourceDictionary()
{
using (StyledWindow())
{
var xaml = @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Color x:Key='Red'>Red</Color>
<SolidColorBrush x:Key='RedBrush' Color='{DynamicResource Red}'/>
</ResourceDictionary>";
var loader = new AvaloniaXamlLoader();
var resources = (ResourceDictionary)loader.Load(xaml);
var brush = (SolidColorBrush)resources["RedBrush"];
Assert.Equal(Colors.Red, brush.Color);
}
}
[Fact]
public void DynamicResource_Finds_Resource_In_Parent_Dictionary()
{
var dictionaryXaml = @"
<ResourceDictionary xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<SolidColorBrush x:Key='RedBrush' Color='{DynamicResource Red}'/>
</ResourceDictionary>";
using (StyledWindow(assets: ("test:dict.xaml", dictionaryXaml)))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'>
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceInclude Source='test:dict.xaml'/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
<Color x:Key='Red'>Red</Color>
</Window.Resources>
<Button Name='button' Background='{DynamicResource RedBrush}'/>
</Window>";
var loader = new AvaloniaXamlLoader();
var window = (Window)loader.Load(xaml);
var button = window.FindControl<Button>("button");
var brush = Assert.IsType<SolidColorBrush>(button.Background);
Assert.Equal(Colors.Red, brush.Color);
window.Resources["Red"] = Colors.Green;
Assert.Equal(Colors.Green, brush.Color);
}
}
private IDisposable StyledWindow(params (string, string)[] assets)
{
var services = TestServices.StyledWindow.With(
assetLoader: new MockAssetLoader(assets),
theme: () => new Styles
{
WindowStyle(),
});
return UnitTestApplication.Start(services);
}
private Style WindowStyle()
{
return new Style(x => x.OfType<Window>())
{
Setters =
{
new Setter(
Window.TemplateProperty,
new FuncControlTemplate<Window>((x, scope) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!Window.ContentProperty],
}.RegisterInNameScope(scope)))
}
};
}
}
}

2
tests/Avalonia.RenderTests/Media/BitmapTests.cs

@ -94,7 +94,7 @@ namespace Avalonia.Direct2D1.RenderTests.Media
ctx.DrawRectangle(Brushes.Pink, null, new Rect(0, 20, 100, 10));
var rc = new Rect(0, 0, 60, 60);
ctx.DrawImage(bmp.PlatformImpl, 1, rc, rc);
ctx.DrawBitmap(bmp.PlatformImpl, 1, rc, rc);
}
rtb.Save(System.IO.Path.Combine(OutputPath, testName + ".out.png"));
}

18
tests/Avalonia.Skia.UnitTests/FontManagerImplTests.cs

@ -1,4 +1,5 @@
using System;
using System.Linq;
using System.Reflection;
using Avalonia.Media;
using Avalonia.Platform;
@ -29,6 +30,23 @@ namespace Avalonia.Skia.UnitTests
Assert.Equal(SKTypeface.Default.FontSlant, skTypeface.FontSlant);
}
[Fact]
public void Should_Create_Typeface_From_Fallback_Bold()
{
var fontManager = new FontManagerImpl();
//we need to have a valid font name different from the default one
string fontName = fontManager.GetInstalledFontFamilyNames().First();
var glyphTypeface = (GlyphTypefaceImpl)fontManager.CreateGlyphTypeface(
new Typeface(new FontFamily($"A, B, {fontName}"), FontWeight.Bold));
var skTypeface = glyphTypeface.Typeface;
Assert.Equal(fontName, skTypeface.FamilyName);
Assert.Equal(SKFontStyle.Bold.Weight, skTypeface.FontWeight);
}
[Fact]
public void Should_Create_Typeface_For_Unknown_Font()
{

45
tests/Avalonia.Styling.UnitTests/ResourceDictionaryTests.cs

@ -3,6 +3,7 @@
using System;
using Avalonia.Controls;
using Moq;
using Xunit;
namespace Avalonia.Styling.UnitTests
@ -136,7 +137,7 @@ namespace Avalonia.Styling.UnitTests
}
[Fact]
public void ResourcesChanged_Should_Not_Be_Raised_On_Empty_MergedDictionary_Remove()
public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Resource_Add()
{
var target = new ResourceDictionary
{
@ -145,31 +146,45 @@ namespace Avalonia.Styling.UnitTests
new ResourceDictionary(),
}
};
var raised = false;
target.ResourcesChanged += (_, __) => raised = true;
target.MergedDictionaries.RemoveAt(0);
((IResourceDictionary)target.MergedDictionaries[0]).Add("foo", "bar");
Assert.False(raised);
Assert.True(raised);
}
[Fact]
public void ResourcesChanged_Should_Be_Raised_On_MergedDictionary_Resource_Add()
public void MergedDictionary_ParentResourcesChanged_Should_Be_Called_On_Resource_Add()
{
var target = new ResourceDictionary
{
MergedDictionaries =
{
new ResourceDictionary(),
}
};
var target = new ResourceDictionary();
var merged = new Mock<ISetResourceParent>();
var raised = false;
target.MergedDictionaries.Add(merged.Object);
merged.ResetCalls();
target.ResourcesChanged += (_, __) => raised = true;
((IResourceDictionary)target.MergedDictionaries[0]).Add("foo", "bar");
target.Add("foo", "bar");
Assert.True(raised);
merged.Verify(
x => x.ParentResourcesChanged(It.IsAny<ResourcesChangedEventArgs>()),
Times.Once);
}
[Fact]
public void MergedDictionary_ParentResourcesChanged_Should_Be_Called_On_NotifyResourceChanged()
{
var target = new ResourceDictionary();
var merged = new Mock<ISetResourceParent>();
target.MergedDictionaries.Add(merged.Object);
merged.ResetCalls();
((ISetResourceParent)target).ParentResourcesChanged(new ResourcesChangedEventArgs());
merged.Verify(
x => x.ParentResourcesChanged(It.IsAny<ResourcesChangedEventArgs>()),
Times.Once);
}
}
}

2
tests/Avalonia.Visuals.UnitTests/Rendering/DeferredRendererTests.cs

@ -670,7 +670,7 @@ namespace Avalonia.Visuals.UnitTests.Rendering
var context = Mock.Get(target.RenderTarget.CreateDrawingContext(null));
var borderLayer = target.Layers[border].Bitmap;
context.Verify(x => x.DrawImage(borderLayer, 0.5, It.IsAny<Rect>(), It.IsAny<Rect>(), BitmapInterpolationMode.Default));
context.Verify(x => x.DrawBitmap(borderLayer, 0.5, It.IsAny<Rect>(), It.IsAny<Rect>(), BitmapInterpolationMode.Default));
}
[Fact]

136
tests/Avalonia.Visuals.UnitTests/Rendering/ImmediateRendererTests.cs

@ -178,5 +178,141 @@ namespace Avalonia.Visuals.UnitTests.Rendering
Assert.Equal(1, rendered);
}
[Fact]
public void Should_Not_Clip_Children_With_RenderTransform_When_In_Bounds()
{
const int RootWidth = 300;
const int RootHeight = 300;
var rootGrid = new Grid
{
Width = RootWidth,
Height = RootHeight,
ClipToBounds = true
};
var stackPanel = new StackPanel
{
Orientation = Orientation.Horizontal,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Right,
Margin = new Thickness(0, 10, 0, 0),
RenderTransformOrigin = new RelativePoint(new Point(0, 0), RelativeUnit.Relative),
RenderTransform = new TransformGroup
{
Children =
{
new RotateTransform { Angle = 90 },
new TranslateTransform { X = 240 }
}
}
};
rootGrid.Children.Add(stackPanel);
TestControl CreateControl()
=> new TestControl
{
Width = 80,
Height = 40,
Margin = new Thickness(0, 0, 5, 0),
ClipToBounds = true
};
var control1 = CreateControl();
var control2 = CreateControl();
var control3 = CreateControl();
stackPanel.Children.Add(control1);
stackPanel.Children.Add(control2);
stackPanel.Children.Add(control3);
var root = new TestRoot(rootGrid);
root.Renderer = new ImmediateRenderer(root);
root.LayoutManager.ExecuteInitialLayoutPass(root);
var rootSize = new Size(RootWidth, RootHeight);
root.Measure(rootSize);
root.Arrange(new Rect(rootSize));
root.Renderer.Paint(root.Bounds);
Assert.True(control1.Rendered);
Assert.True(control2.Rendered);
Assert.True(control3.Rendered);
}
[Fact]
public void Should_Not_Render_Clipped_Child_With_RenderTransform_When_Not_In_Bounds()
{
const int RootWidth = 300;
const int RootHeight = 300;
var rootGrid = new Grid
{
Width = RootWidth,
Height = RootHeight,
ClipToBounds = true
};
var stackPanel = new StackPanel
{
Orientation = Orientation.Horizontal,
VerticalAlignment = VerticalAlignment.Top,
HorizontalAlignment = HorizontalAlignment.Right,
Margin = new Thickness(0, 10, 0, 0),
RenderTransformOrigin = new RelativePoint(new Point(0, 0), RelativeUnit.Relative),
RenderTransform = new TransformGroup
{
Children =
{
new RotateTransform { Angle = 90 },
new TranslateTransform { X = 280 }
}
}
};
rootGrid.Children.Add(stackPanel);
TestControl CreateControl()
=> new TestControl
{
Width = 160,
Height = 40,
Margin = new Thickness(0, 0, 5, 0),
ClipToBounds = true
};
var control1 = CreateControl();
var control2 = CreateControl();
var control3 = CreateControl();
stackPanel.Children.Add(control1);
stackPanel.Children.Add(control2);
stackPanel.Children.Add(control3);
var root = new TestRoot(rootGrid);
root.Renderer = new ImmediateRenderer(root);
root.LayoutManager.ExecuteInitialLayoutPass(root);
var rootSize = new Size(RootWidth, RootHeight);
root.Measure(rootSize);
root.Arrange(new Rect(rootSize));
root.Renderer.Paint(root.Bounds);
Assert.True(control1.Rendered);
Assert.True(control2.Rendered);
Assert.False(control3.Rendered);
}
private class TestControl : Control
{
public bool Rendered { get; private set; }
public override void Render(DrawingContext context)
=> Rendered = true;
}
}
}

Loading…
Cancel
Save