Browse Source

Merge branch 'master' into datagrid-integration

pull/2109/head
Jumar Macato 7 years ago
committed by GitHub
parent
commit
964cc6316b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      .github/PULL_REQUEST_TEMPLATE.md
  2. 31
      Avalonia.sln
  3. 2
      build/SkiaSharp.props
  4. 2
      nukebuild/BuildParameters.cs
  5. 6
      readme.md
  6. 2
      samples/ControlCatalog/MainView.xaml
  7. 9
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  8. 4
      samples/ControlCatalog/Pages/ScreenPage.cs
  9. 2
      samples/ControlCatalog/SideBar.xaml
  10. 3
      scripts/ReplaceNugetCache.ps1
  11. 6
      src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
  12. 8
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  13. 2
      src/Avalonia.Base/AvaloniaProperty.cs
  14. 26
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  15. 24
      src/Avalonia.Base/Data/Converters/ObjectConverters.cs
  16. 4
      src/Avalonia.Base/Data/Converters/StringConverters.cs
  17. 2
      src/Avalonia.Base/Data/Core/BindingExpression.cs
  18. 76
      src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
  19. 22
      src/Avalonia.Base/ISupportInitialize.cs
  20. 20
      src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs
  21. 145
      src/Avalonia.Base/ValueStore.cs
  22. 2
      src/Avalonia.Controls/AutoCompleteBox.cs
  23. 2
      src/Avalonia.Controls/ContentControl.cs
  24. 1
      src/Avalonia.Controls/Control.cs
  25. 1
      src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs
  26. 1
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs
  27. 4
      src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
  28. 17
      src/Avalonia.Controls/Image.cs
  29. 2
      src/Avalonia.Controls/MenuItem.cs
  30. 7
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  31. 27
      src/Avalonia.Controls/PixelPointEventArgs.cs
  32. 4
      src/Avalonia.Controls/Platform/ITopLevelImpl.cs
  33. 6
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  34. 8
      src/Avalonia.Controls/Platform/Screen.cs
  35. 27
      src/Avalonia.Controls/PointEventArgs.cs
  36. 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  37. 30
      src/Avalonia.Controls/Primitives/Popup.cs
  38. 10
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  39. 14
      src/Avalonia.Controls/Screens.cs
  40. 13
      src/Avalonia.Controls/Shapes/Shape.cs
  41. 7
      src/Avalonia.Controls/TabControl.cs
  42. 31
      src/Avalonia.Controls/TopLevel.cs
  43. 60
      src/Avalonia.Controls/Window.cs
  44. 14
      src/Avalonia.Controls/WindowBase.cs
  45. 4
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  46. 10
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  47. 2
      src/Avalonia.Input/IMouseDevice.cs
  48. 2
      src/Avalonia.Input/IPointerDevice.cs
  49. 53
      src/Avalonia.Input/MouseDevice.cs
  50. 15
      src/Avalonia.Native/Helpers.cs
  51. 5
      src/Avalonia.Native/ScreenImpl.cs
  52. 14
      src/Avalonia.Native/WindowImplBase.cs
  53. 12
      src/Avalonia.OpenGL/EglDisplay.cs
  54. 28
      src/Avalonia.OpenGL/EglInterface.cs
  55. 5
      src/Avalonia.OpenGL/GlEntryPointAttribute.cs
  56. 34
      src/Avalonia.OpenGL/GlInterface.cs
  57. 27
      src/Avalonia.OpenGL/GlInterfaceBase.cs
  58. 6
      src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
  59. 4
      src/Avalonia.ReactiveUI/ReactiveUserControl.cs
  60. 7
      src/Avalonia.Remote.Protocol/MetsysBson.cs
  61. 6
      src/Avalonia.Remote.Protocol/TcpTransportBase.cs
  62. 7
      src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs
  63. 72
      src/Avalonia.Styling/Styling/NotSelector.cs
  64. 11
      src/Avalonia.Styling/Styling/Selectors.cs
  65. 9
      src/Avalonia.Themes.Default/CheckBox.xaml
  66. 2
      src/Avalonia.Themes.Default/TabControl.xaml
  67. 4
      src/Avalonia.Themes.Default/TextBox.xaml
  68. 2
      src/Avalonia.Visuals/Media/Imaging/Bitmap.cs
  69. 201
      src/Avalonia.Visuals/Media/PixelPoint.cs
  70. 436
      src/Avalonia.Visuals/Media/PixelRect.cs
  71. 50
      src/Avalonia.Visuals/Media/PixelSize.cs
  72. 44
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  73. 8
      src/Avalonia.Visuals/Rendering/IRenderRoot.cs
  74. 13
      src/Avalonia.Visuals/Rendering/IRenderer.cs
  75. 5
      src/Avalonia.Visuals/Rendering/ImmediateRenderer.cs
  76. 4
      src/Avalonia.Visuals/Rendering/SceneGraph/Scene.cs
  77. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/VisualNode.cs
  78. 36
      src/Avalonia.Visuals/Rendering/SceneInvalidatedEventArgs.cs
  79. 10
      src/Avalonia.Visuals/VisualExtensions.cs
  80. 1
      src/Avalonia.X11/Avalonia.X11.csproj
  81. 91
      src/Avalonia.X11/Glx/Glx.cs
  82. 106
      src/Avalonia.X11/Glx/GlxConsts.cs
  83. 41
      src/Avalonia.X11/Glx/GlxContext.cs
  84. 136
      src/Avalonia.X11/Glx/GlxDisplay.cs
  85. 86
      src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs
  86. 44
      src/Avalonia.X11/Glx/GlxPlatformFeature.cs
  87. 263
      src/Avalonia.X11/NativeDialogs/Gtk.cs
  88. 122
      src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs
  89. 6
      src/Avalonia.X11/X11FramebufferSurface.cs
  90. 8
      src/Avalonia.X11/X11Info.cs
  91. 28
      src/Avalonia.X11/X11Platform.cs
  92. 42
      src/Avalonia.X11/X11PlatformThreading.cs
  93. 19
      src/Avalonia.X11/X11Screens.cs
  94. 102
      src/Avalonia.X11/X11Window.cs
  95. 4
      src/Gtk/Avalonia.Gtk3/GtkScreen.cs
  96. 4
      src/Gtk/Avalonia.Gtk3/ScreenImpl.cs
  97. 14
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  98. 4
      src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs
  99. 17
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  100. 35
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs

30
.github/PULL_REQUEST_TEMPLATE.md

@ -1,17 +1,31 @@
This template is not intended to be prescriptive, but to help us review pull requests it would be useful if you included as much of the following information as possible:
## What does the pull request do?
<!--- Give a bit of background on the PR here, together with links to with related issues etc. -->
- What does the pull request do?
- What is the current behavior?
- What is the updated/expected behavior with this PR?
- How was the solution implemented (if it's not obvious)?
Checklist:
## What is the current behavior?
<!--- If the PR is a fix, describe the current incorrect behavior, otherwise delete this section. -->
## What is the updated/expected behavior with this PR?
<!--- Describe how to test the PR. -->
## How was the solution implemented (if it's not obvious)?
<!--- Include any information that might be of use to a reviewer here. -->
## Checklist
- [ ] Added unit tests (if possible)?
- [ ] Added XML documentation to any related classes?
- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Avaloniaui.net with user documentation
If the pull request fixes issue(s) list them like this:
## Breaking changes
<!--- List any breaking changes here. When the PR is merged please add an entry to https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes -->
## Fixed issues
<!--- If the pull request fixes issue(s) list them like this:
Fixes #123
Fixes #456
Fixes #456
-->

31
Avalonia.sln

@ -196,9 +196,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "_build", "nukebuild\_build.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Animation.UnitTests", "tests\Avalonia.Animation.UnitTests\Avalonia.Animation.UnitTests.csproj", "{AF227847-E65C-4BE9-BCE9-B551357788E0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Avalonia.X11", "src\Avalonia.X11\Avalonia.X11.csproj", "{41B02319-965D-4945-8005-C1A3D1224165}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.X11", "src\Avalonia.X11\Avalonia.X11.csproj", "{41B02319-965D-4945-8005-C1A3D1224165}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlatformSanityChecks", "samples\PlatformSanityChecks\PlatformSanityChecks.csproj", "{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PlatformSanityChecks", "samples\PlatformSanityChecks\PlatformSanityChecks.csproj", "{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI.UnitTests", "tests\Avalonia.ReactiveUI.UnitTests\Avalonia.ReactiveUI.UnitTests.csproj", "{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}"
EndProject
Global
GlobalSection(SharedMSBuildProjectFiles) = preSolution
@ -1819,6 +1821,30 @@ Global
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhone.Build.0 = Release|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhone.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhone.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|Any CPU.Build.0 = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhone.ActiveCfg = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhone.Build.0 = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -1875,6 +1901,7 @@ Global
{AF227847-E65C-4BE9-BCE9-B551357788E0} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
{41B02319-965D-4945-8005-C1A3D1224165} = {86C53C40-57AA-45B8-AD42-FAE0EFDF0F2B}
{D775DECB-4E00-4ED5-A75A-5FCE58ADFF0B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AF915D5C-AB00-4EA0-B5E6-001F4AE84E68} = {C5A00AC3-B34C-4564-9BDD-2DA473EF4D8B}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

2
build/SkiaSharp.props

@ -1,6 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.68.0" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.0" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="Avalonia.Skia.Linux.Natives" Version="1.68.0.1" />
</ItemGroup>
</Project>

2
nukebuild/BuildParameters.cs

@ -109,7 +109,7 @@ public partial class Build
if (!IsNuGetRelease)
{
// Use AssemblyVersion with Build as version
Version += "-build" + Environment.GetEnvironmentVariable("BUILD_BUILDID") + "-beta";
Version += "-cibuild" + int.Parse(Environment.GetEnvironmentVariable("BUILD_BUILDID")).ToString("0000000") + "-beta";
}
PublishTestResults = true;

6
readme.md

@ -2,9 +2,9 @@
# Avalonia
| Gitter Chat | Build Status (Win, Linux, OSX) | Open Collective |
|---|---|---|
| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) |
| Gitter Chat | Build Status (Win, Linux, OSX) | Open Collective | NuGet | MyGet |
|---|---|---|---|---|
| [![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/AvaloniaUI/Avalonia?utm_campaign=pr-badge&utm_content=badge&utm_medium=badge&utm_source=badge) | [![Build Status](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_apis/build/status/AvaloniaUI.Avalonia)](https://dev.azure.com/AvaloniaUI/AvaloniaUI/_build/latest?definitionId=4) | [![Backers on Open Collective](https://opencollective.com/Avalonia/backers/badge.svg)](#backers) [![Sponsors on Open Collective](https://opencollective.com/Avalonia/sponsors/badge.svg)](#sponsors) | [![NuGet](https://img.shields.io/nuget/v/Avalonia.svg)](https://www.nuget.org/packages/Avalonia) | [![MyGet](https://img.shields.io/myget/avalonia-ci/vpre/Avalonia.svg?label=myget)](https://www.myget.org/gallery/avalonia-ci) |
## About

2
samples/ControlCatalog/MainView.xaml

@ -33,10 +33,10 @@
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
<TabItem Header="Slider"><pages:SliderPage/></TabItem>
<TabItem Header="TabControl"><pages:TabControlPage/></TabItem>
<TabItem Header="TextBox"><pages:TextBoxPage/></TabItem>
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
<TabItem Header="TabControl"><pages:TabControlPage/></TabItem>
<TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
</TabControl>
</Grid>

9
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -40,10 +40,13 @@ namespace ControlCatalog.Pages
};
this.FindControl<Button>("Dialog").Click += delegate
{
new MainWindow().ShowDialog(GetWindow());
var window = new Window();
window.Height = 200;
window.Width = 200;
window.Content = new TextBlock { Text = "Hello world!" };
window.WindowStartupLocation = WindowStartupLocation.CenterOwner;
window.ShowDialog(GetWindow());
};
}
Window GetWindow() => (Window)this.VisualRoot;

4
samples/ControlCatalog/Pages/ScreenPage.cs

@ -4,6 +4,7 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
namespace ControlCatalog.Pages
{
@ -23,6 +24,7 @@ namespace ControlCatalog.Pages
base.Render(context);
Window w = (Window)VisualRoot;
Screen[] screens = w.Screens.All;
var scaling = ((IRenderRoot)w).RenderScaling;
Pen p = new Pen(Brushes.Black);
if (screens != null)
@ -56,7 +58,7 @@ namespace ControlCatalog.Pages
text.Text = $"Primary: {screen.Primary}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new Rect(w.Position, w.Bounds.Size)))}";
text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
}

2
samples/ControlCatalog/SideBar.xaml

@ -26,7 +26,7 @@
</ItemsPresenter>
</ScrollViewer>
<ContentPresenter
Name="PART_Content"
Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"

3
scripts/ReplaceNugetCache.ps1

@ -1,6 +1,5 @@
copy ..\samples\ControlCatalog.Desktop\bin\Debug\net461\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\net461\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Gtk3.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.direct2d1\$args\lib\netstandard2.0\

6
src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs

@ -10,10 +10,10 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
class PopupImpl : TopLevelImpl, IPopupImpl
{
private Point _position;
private PixelPoint _position;
private bool _isAdded;
Action IWindowBaseImpl.Activated { get; set; }
public Action<Point> PositionChanged { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public Action Deactivated { get; set; }
public PopupImpl() : base(ActivityTracker.Current, true)
@ -36,7 +36,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IScreenImpl Screen { get; }
public Point Position
public PixelPoint Position
{
get { return _position; }
set

8
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -99,14 +99,14 @@ namespace Avalonia.Android.Platform.SkiaPlatform
if (_view.Holder?.Surface?.IsValid == true) _view.Invalidate();
}
public Point PointToClient(Point point)
public Point PointToClient(PixelPoint point)
{
return point;
return point.ToPoint(1);
}
public Point PointToScreen(Point point)
public PixelPoint PointToScreen(Point point)
{
return point;
return PixelPoint.FromPoint(point, 1);
}
public void SetCursor(IPlatformHandle cursor)

2
src/Avalonia.Base/AvaloniaProperty.cs

@ -21,7 +21,7 @@ namespace Avalonia
/// </summary>
public static readonly object UnsetValue = new Unset();
private static int s_nextId = 1;
private static int s_nextId;
private readonly Subject<AvaloniaPropertyChangedEventArgs> _initialized;
private readonly Subject<AvaloniaPropertyChangedEventArgs> _changed;
private readonly PropertyMetadata _defaultMetadata;

26
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -13,6 +13,8 @@ namespace Avalonia
/// </summary>
public class AvaloniaPropertyRegistry
{
private readonly Dictionary<int, AvaloniaProperty> _properties =
new Dictionary<int, AvaloniaProperty>();
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _registered =
new Dictionary<Type, Dictionary<int, AvaloniaProperty>>();
private readonly Dictionary<Type, Dictionary<int, AvaloniaProperty>> _attached =
@ -28,6 +30,11 @@ namespace Avalonia
public static AvaloniaPropertyRegistry Instance { get; }
= new AvaloniaPropertyRegistry();
/// <summary>
/// Gets a list of all registered properties.
/// </summary>
internal IReadOnlyCollection<AvaloniaProperty> Properties => _properties.Values;
/// <summary>
/// Gets all non-attached <see cref="AvaloniaProperty"/>s registered on a type.
/// </summary>
@ -148,6 +155,16 @@ namespace Avalonia
return FindRegistered(o.GetType(), name);
}
/// <summary>
/// Finds a registered property by Id.
/// </summary>
/// <param name="id">The property Id.</param>
/// <returns>The registered property or null if no matching property found.</returns>
internal AvaloniaProperty FindRegistered(int id)
{
return id < _properties.Count ? _properties[id] : null;
}
/// <summary>
/// Checks whether a <see cref="AvaloniaProperty"/> is registered on a type.
/// </summary>
@ -202,7 +219,12 @@ namespace Avalonia
{
inner.Add(property.Id, property);
}
if (!_properties.ContainsKey(property.Id))
{
_properties.Add(property.Id, property);
}
_registeredCache.Clear();
}
@ -237,7 +259,7 @@ namespace Avalonia
{
inner.Add(property.Id, property);
}
_attachedCache.Clear();
}
}

24
src/Avalonia.Base/Data/Converters/ObjectConverters.cs

@ -0,0 +1,24 @@
// 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.
namespace Avalonia.Data.Converters
{
/// <summary>
/// Provides a set of useful <see cref="IValueConverter"/>s for working with objects.
/// </summary>
public static class ObjectConverters
{
/// <summary>
/// A value converter that returns true if the input object is a null reference.
/// </summary>
public static readonly IValueConverter IsNull =
new FuncValueConverter<object, bool>(x => x is null);
/// <summary>
/// A value converter that returns true if the input object is not null.
/// </summary>
public static readonly IValueConverter IsNotNull =
new FuncValueConverter<object, bool>(x => !(x is null));
}
}

4
src/Avalonia.Base/Data/Converters/StringConverters.cs

@ -12,13 +12,13 @@ namespace Avalonia.Data.Converters
/// <summary>
/// A value converter that returns true if the input string is null or an empty string.
/// </summary>
public static readonly IValueConverter NullOrEmpty =
public static readonly IValueConverter IsNullOrEmpty =
new FuncValueConverter<string, bool>(string.IsNullOrEmpty);
/// <summary>
/// A value converter that returns true if the input string is not null or empty.
/// </summary>
public static readonly IValueConverter NotNullOrEmpty =
public static readonly IValueConverter IsNotNullOrEmpty =
new FuncValueConverter<string, bool>(x => !string.IsNullOrEmpty(x));
}
}

2
src/Avalonia.Base/Data/Core/BindingExpression.cs

@ -177,7 +177,7 @@ namespace Avalonia.Data.Core
protected override void Subscribed(IObserver<object> observer, bool first)
{
if (!first && _value != null && _value.TryGetTarget(out var val) == true)
if (!first && _value != null && _value.TryGetTarget(out var val))
{
observer.OnNext(val);
}

76
src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs

@ -2,6 +2,9 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using System.Reactive.Linq;
using System.Reflection;
namespace Avalonia.Data.Core.Plugins
{
@ -10,12 +13,19 @@ namespace Avalonia.Data.Core.Plugins
/// </summary>
public class ObservableStreamPlugin : IStreamPlugin
{
static MethodInfo observableSelect;
/// <summary>
/// Checks whether this plugin handles the specified value.
/// </summary>
/// <param name="reference">A weak reference to the value.</param>
/// <returns>True if the plugin can handle the value; otherwise false.</returns>
public virtual bool Match(WeakReference reference) => reference.Target is IObservable<object>;
public virtual bool Match(WeakReference reference)
{
return reference.Target.GetType().GetInterfaces().Any(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>));
}
/// <summary>
/// Starts producing output based on the specified value.
@ -26,7 +36,69 @@ namespace Avalonia.Data.Core.Plugins
/// </returns>
public virtual IObservable<object> Start(WeakReference reference)
{
return reference.Target as IObservable<object>;
var target = reference.Target;
// If the observable returns a reference type then we can cast it.
if (target is IObservable<object> result)
{
return result;
};
// If the observable returns a value type then we need to call Observable.Select on it.
// First get the type of T in `IObservable<T>`.
var sourceType = reference.Target.GetType().GetInterfaces().First(x =>
x.IsGenericType &&
x.GetGenericTypeDefinition() == typeof(IObservable<>)).GetGenericArguments()[0];
// Get the Observable.Select method.
var select = GetObservableSelect(sourceType);
// Make a Box<> delegate of the correct type.
var funcType = typeof(Func<,>).MakeGenericType(sourceType, typeof(object));
var box = GetType().GetMethod(nameof(Box), BindingFlags.Static | BindingFlags.NonPublic)
.MakeGenericMethod(sourceType)
.CreateDelegate(funcType);
// Call Observable.Select(target, box);
return (IObservable<object>)select.Invoke(
null,
new object[] { target, box });
}
private static MethodInfo GetObservableSelect(Type source)
{
return GetObservableSelect().MakeGenericMethod(source, typeof(object));
}
private static MethodInfo GetObservableSelect()
{
if (observableSelect == null)
{
observableSelect = typeof(Observable).GetRuntimeMethods().First(x =>
{
if (x.Name == nameof(Observable.Select) &&
x.ContainsGenericParameters &&
x.GetGenericArguments().Length == 2)
{
var parameters = x.GetParameters();
if (parameters.Length == 2 &&
parameters[0].ParameterType.IsConstructedGenericType &&
parameters[0].ParameterType.GetGenericTypeDefinition() == typeof(IObservable<>) &&
parameters[1].ParameterType.IsConstructedGenericType &&
parameters[1].ParameterType.GetGenericTypeDefinition() == typeof(Func<,>))
{
return true;
}
}
return false;
});
}
return observableSelect;
}
private static object Box<T>(T value) => (object)value;
}
}

22
src/Avalonia.Base/ISupportInitialize.cs

@ -1,22 +0,0 @@
// 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.
namespace Avalonia
{
/// <summary>
/// Specifies that this object supports a simple, transacted notification for batch
/// initialization.
/// </summary>
public interface ISupportInitialize
{
/// <summary>
/// Signals the object that initialization is starting.
/// </summary>
void BeginInit();
/// <summary>
/// Signals the object that initialization is complete.
/// </summary>
void EndInit();
}
}

20
src/Avalonia.Base/Utilities/AvaloniaResourcesIndex.cs

@ -4,6 +4,8 @@ using System.IO;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Xml.Linq;
using System.Linq;
// ReSharper disable AssignNullToNotNullAttribute
@ -19,10 +21,20 @@ namespace Avalonia.Utilities
{
var ver = new BinaryReader(stream).ReadInt32();
if (ver > LastKnownVersion)
throw new Exception("Resources index format version is not known");
var index = (AvaloniaResourcesIndex)
new DataContractSerializer(typeof(AvaloniaResourcesIndex)).ReadObject(stream);
return index.Entries;
throw new Exception("Resources index format version is not known");
var assetDoc = XDocument.Load(stream);
XNamespace assetNs = assetDoc.Root.Attribute("xmlns").Value;
List<AvaloniaResourcesIndexEntry> entries=
(from entry in assetDoc.Root.Element(assetNs + "Entries").Elements(assetNs + "AvaloniaResourcesIndexEntry")
select new AvaloniaResourcesIndexEntry
{
Path = entry.Element(assetNs + "Path").Value,
Offset = int.Parse(entry.Element(assetNs + "Offset").Value),
Size = int.Parse(entry.Element(assetNs + "Size").Value)
}).ToList();
return entries;
}
public static void Write(Stream stream, List<AvaloniaResourcesIndexEntry> entries)

145
src/Avalonia.Base/ValueStore.cs

@ -7,13 +7,21 @@ namespace Avalonia
{
internal class ValueStore : IPriorityValueOwner
{
private struct Entry
{
internal int PropertyId;
internal object Value;
}
private readonly AvaloniaObject _owner;
private readonly Dictionary<AvaloniaProperty, object> _values =
new Dictionary<AvaloniaProperty, object>();
private Entry[] _entries;
public ValueStore(AvaloniaObject owner)
{
_owner = owner;
// The last item in the list is always int.MaxValue
_entries = new[] { new Entry { PropertyId = int.MaxValue, Value = null } };
}
public IDisposable AddBinding(
@ -23,7 +31,7 @@ namespace Avalonia
{
PriorityValue priorityValue;
if (_values.TryGetValue(property, out var v))
if (TryGetValue(property, out var v))
{
priorityValue = v as PriorityValue;
@ -31,13 +39,13 @@ namespace Avalonia
{
priorityValue = CreatePriorityValue(property);
priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
_values[property] = priorityValue;
SetValueInternal(property, priorityValue);
}
}
else
{
priorityValue = CreatePriorityValue(property);
_values.Add(property, priorityValue);
AddValueInternal(property, priorityValue);
}
return priorityValue.Add(source, (int)priority);
@ -47,7 +55,7 @@ namespace Avalonia
{
PriorityValue priorityValue;
if (_values.TryGetValue(property, out var v))
if (TryGetValue(property, out var v))
{
priorityValue = v as PriorityValue;
@ -55,7 +63,7 @@ namespace Avalonia
{
if (priority == (int)BindingPriority.LocalValue)
{
_values[property] = Validate(property, value);
SetValueInternal(property, Validate(property, value));
Changed(property, priority, v, value);
return;
}
@ -63,7 +71,7 @@ namespace Avalonia
{
priorityValue = CreatePriorityValue(property);
priorityValue.SetValue(v, (int)BindingPriority.LocalValue);
_values[property] = priorityValue;
SetValueInternal(property, priorityValue);
}
}
}
@ -76,14 +84,14 @@ namespace Avalonia
if (priority == (int)BindingPriority.LocalValue)
{
_values.Add(property, Validate(property, value));
AddValueInternal(property, Validate(property, value));
Changed(property, priority, AvaloniaProperty.UnsetValue, value);
return;
}
else
{
priorityValue = CreatePriorityValue(property);
_values.Add(property, priorityValue);
AddValueInternal(property, priorityValue);
}
}
@ -100,13 +108,22 @@ namespace Avalonia
_owner.PriorityValueChanged(property, priority, oldValue, newValue);
}
public IDictionary<AvaloniaProperty, object> GetSetValues() => _values;
public IDictionary<AvaloniaProperty, object> GetSetValues()
{
var dict = new Dictionary<AvaloniaProperty, object>(_entries.Length - 1);
for (int i = 0; i < _entries.Length - 1; ++i)
{
dict.Add(AvaloniaPropertyRegistry.Instance.FindRegistered(_entries[i].PropertyId), _entries[i].Value);
}
return dict;
}
public object GetValue(AvaloniaProperty property)
{
var result = AvaloniaProperty.UnsetValue;
if (_values.TryGetValue(property, out var value))
if (TryGetValue(property, out var value))
{
result = (value is PriorityValue priorityValue) ? priorityValue.Value : value;
}
@ -116,12 +133,12 @@ namespace Avalonia
public bool IsAnimating(AvaloniaProperty property)
{
return _values.TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating;
return TryGetValue(property, out var value) && value is PriorityValue priority && priority.IsAnimating;
}
public bool IsSet(AvaloniaProperty property)
{
if (_values.TryGetValue(property, out var value))
if (TryGetValue(property, out var value))
{
return ((value as PriorityValue)?.Value ?? value) != AvaloniaProperty.UnsetValue;
}
@ -131,7 +148,7 @@ namespace Avalonia
public void Revalidate(AvaloniaProperty property)
{
if (_values.TryGetValue(property, out var value))
if (TryGetValue(property, out var value))
{
(value as PriorityValue)?.Revalidate();
}
@ -178,5 +195,103 @@ namespace Avalonia
(_deferredSetter = new DeferredSetter<object>());
}
}
private bool TryGetValue(AvaloniaProperty property, out object value)
{
(int index, bool found) = TryFindEntry(property.Id);
if (!found)
{
value = null;
return false;
}
value = _entries[index].Value;
return true;
}
private void AddValueInternal(AvaloniaProperty property, object value)
{
Entry[] entries = new Entry[_entries.Length + 1];
for (int i = 0; i < _entries.Length; ++i)
{
if (_entries[i].PropertyId > property.Id)
{
if (i > 0)
{
Array.Copy(_entries, 0, entries, 0, i);
}
entries[i] = new Entry { PropertyId = property.Id, Value = value };
Array.Copy(_entries, i, entries, i + 1, _entries.Length - i);
break;
}
}
_entries = entries;
}
private void SetValueInternal(AvaloniaProperty property, object value)
{
_entries[TryFindEntry(property.Id).Item1].Value = value;
}
private (int, bool) TryFindEntry(int propertyId)
{
if (_entries.Length <= 12)
{
// For small lists, we use an optimized linear search. Since the last item in the list
// is always int.MaxValue, we can skip a conditional branch in each iteration.
// By unrolling the loop, we can skip another unconditional branch in each iteration.
if (_entries[0].PropertyId >= propertyId) return (0, _entries[0].PropertyId == propertyId);
if (_entries[1].PropertyId >= propertyId) return (1, _entries[1].PropertyId == propertyId);
if (_entries[2].PropertyId >= propertyId) return (2, _entries[2].PropertyId == propertyId);
if (_entries[3].PropertyId >= propertyId) return (3, _entries[3].PropertyId == propertyId);
if (_entries[4].PropertyId >= propertyId) return (4, _entries[4].PropertyId == propertyId);
if (_entries[5].PropertyId >= propertyId) return (5, _entries[5].PropertyId == propertyId);
if (_entries[6].PropertyId >= propertyId) return (6, _entries[6].PropertyId == propertyId);
if (_entries[7].PropertyId >= propertyId) return (7, _entries[7].PropertyId == propertyId);
if (_entries[8].PropertyId >= propertyId) return (8, _entries[8].PropertyId == propertyId);
if (_entries[9].PropertyId >= propertyId) return (9, _entries[9].PropertyId == propertyId);
if (_entries[10].PropertyId >= propertyId) return (10, _entries[10].PropertyId == propertyId);
}
else
{
int low = 0;
int high = _entries.Length;
int id;
while (high - low > 3)
{
int pivot = (high + low) / 2;
id = _entries[pivot].PropertyId;
if (propertyId == id)
return (pivot, true);
if (propertyId <= id)
high = pivot;
else
low = pivot + 1;
}
do
{
id = _entries[low].PropertyId;
if (id == propertyId)
return (low, true);
if (id > propertyId)
break;
++low;
}
while (low < high);
}
return (0, false);
}
}
}

2
src/Avalonia.Controls/AutoCompleteBox.cs

@ -1893,7 +1893,7 @@ namespace Avalonia.Controls
{
bool callTextChanged = false;
// Update the Text dependency property
if ((userInitiated == null || userInitiated == true) && Text != value)
if ((userInitiated ?? true) && Text != value)
{
_ignoreTextPropertyChange++;
Text = value;

2
src/Avalonia.Controls/ContentControl.cs

@ -45,8 +45,6 @@ namespace Avalonia.Controls
static ContentControl()
{
ContentControlMixin.Attach<ContentControl>(ContentProperty, x => x.LogicalChildren);
PseudoClass<ContentControl, object>(ContentProperty, x => x != null, ":valid");
PseudoClass<ContentControl, object>(ContentProperty, x => x == null, ":invalid");
}
/// <summary>

1
src/Avalonia.Controls/Control.cs

@ -1,6 +1,7 @@
// 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.ComponentModel;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;

1
src/Avalonia.Controls/Embedding/EmbeddableControlRoot.cs

@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Platform;

1
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevel.cs

@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using Avalonia.Styling;
namespace Avalonia.Controls.Embedding.Offscreen

4
src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs

@ -49,9 +49,9 @@ namespace Avalonia.Controls.Embedding.Offscreen
public Action<double> ScalingChanged { get; set; }
public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot;
public virtual Point PointToClient(Point point) => point;
public virtual Point PointToClient(PixelPoint point) => point.ToPoint(1);
public virtual Point PointToScreen(Point point) => point;
public virtual PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, 1);
public virtual void SetCursor(IPlatformHandle cursor)
{

17
src/Avalonia.Controls/Image.cs

@ -99,5 +99,22 @@ namespace Avalonia.Controls
return new Size();
}
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
var source = Source;
if (source != null)
{
var sourceSize = new Size(source.PixelSize.Width, source.PixelSize.Height);
var result = Stretch.CalculateSize(finalSize, sourceSize);
return result;
}
else
{
return new Size();
}
}
}
}

2
src/Avalonia.Controls/MenuItem.cs

@ -421,7 +421,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Called when the <see cref="Header"/> property changes.
/// Called when the <see cref="HeaderedSelectingItemsControl.Header"/> property changes.
/// </summary>
/// <param name="e">The property change event.</param>
private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)

7
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -287,6 +287,13 @@ namespace Avalonia.Controls
ValueProperty.Changed.Subscribe(OnValueChanged);
}
/// <inheritdoc />
protected override void OnLostFocus(RoutedEventArgs e)
{
CommitInput();
base.OnLostFocus(e);
}
/// <inheritdoc />
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{

27
src/Avalonia.Controls/PixelPointEventArgs.cs

@ -0,0 +1,27 @@
// 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;
namespace Avalonia.Controls
{
/// <summary>
/// Provides <see cref="PixelPoint"/> data for events.
/// </summary>
public class PixelPointEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="PixelPointEventArgs"/> class.
/// </summary>
/// <param name="point">The <see cref="PixelPoint"/> data.</param>
public PixelPointEventArgs(PixelPoint point)
{
Point = point;
}
/// <summary>
/// Gets the <see cref="PixelPoint"/> data.
/// </summary>
public PixelPoint Point { get; }
}
}

4
src/Avalonia.Controls/Platform/ITopLevelImpl.cs

@ -82,14 +82,14 @@ namespace Avalonia.Platform
/// </summary>
/// <param name="point">The point in screen coordinates.</param>
/// <returns>The point in client coordinates.</returns>
Point PointToClient(Point point);
Point PointToClient(PixelPoint point);
/// <summary>
/// Converts a point from client to screen coordinates.
/// </summary>
/// <param name="point">The point in client coordinates.</param>
/// <returns>The point in screen coordinates.</returns>
Point PointToScreen(Point point);
PixelPoint PointToScreen(Point point);
/// <summary>
/// Sets the cursor associated with the toplevel.

6
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@ -27,14 +27,14 @@ namespace Avalonia.Platform
void BeginResizeDrag(WindowEdge edge);
/// <summary>
/// Gets position of the window relatively to the screen
/// Gets the position of the window in device pixels.
/// </summary>
Point Position { get; set; }
PixelPoint Position { get; set; }
/// <summary>
/// Gets or sets a method called when the window's position changes.
/// </summary>
Action<Point> PositionChanged { get; set; }
Action<PixelPoint> PositionChanged { get; set; }
/// <summary>
/// Activates the window.

8
src/Avalonia.Controls/Platform/Screen.cs

@ -2,17 +2,17 @@
{
public class Screen
{
public Rect Bounds { get; }
public PixelRect Bounds { get; }
public Rect WorkingArea { get; }
public PixelRect WorkingArea { get; }
public bool Primary { get; }
public Screen(Rect bounds, Rect workingArea, bool primary)
public Screen(PixelRect bounds, PixelRect workingArea, bool primary)
{
this.Bounds = bounds;
this.WorkingArea = workingArea;
this.Primary = primary;
}
}
}
}

27
src/Avalonia.Controls/PointEventArgs.cs

@ -1,27 +0,0 @@
// 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;
namespace Avalonia.Controls
{
/// <summary>
/// Provides <see cref="Point"/> data for events.
/// </summary>
public class PointEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="PointEventArgs"/> class.
/// </summary>
/// <param name="point">The <see cref="Point"/> data.</param>
public PointEventArgs(Point point)
{
Point = point;
}
/// <summary>
/// Gets the <see cref="Point"/> data.
/// </summary>
public Point Point { get; }
}
}

2
src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs

@ -285,7 +285,7 @@ namespace Avalonia.Controls.Presenters
{
scrollable.InvalidateScroll = () => UpdateFromScrollable(scrollable);
if (scrollable.IsLogicalScrollEnabled == true)
if (scrollable.IsLogicalScrollEnabled)
{
_logicalScrollSubscription = new CompositeDisposable(
this.GetObservable(CanHorizontallyScrollProperty)

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

@ -380,7 +380,7 @@ namespace Avalonia.Controls.Primitives
/// Gets the position for the popup based on the placement properties.
/// </summary>
/// <returns>The popup's position in screen coordinates.</returns>
protected virtual Point GetPosition()
protected virtual PixelPoint GetPosition()
{
var result = GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
HorizontalOffset, VerticalOffset);
@ -388,35 +388,31 @@ namespace Avalonia.Controls.Primitives
return result;
}
internal static Point GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
internal static PixelPoint GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
{
var zero = default(Point);
var mode = placement;
if (target?.GetVisualRoot() == null)
{
mode = PlacementMode.Pointer;
}
var root = target?.GetVisualRoot();
var mode = root != null ? placement : PlacementMode.Pointer;
var scaling = root?.RenderScaling ?? 1;
switch (mode)
{
case PlacementMode.Pointer:
if (popupRoot != null)
{
// Scales the Horizontal and Vertical offset to screen co-ordinates.
var screenOffset = new Point(horizontalOffset * (popupRoot as ILayoutRoot).LayoutScaling,
verticalOffset * (popupRoot as ILayoutRoot).LayoutScaling);
return (((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default(Point)) + screenOffset;
var screenOffset = PixelPoint.FromPoint(new Point(horizontalOffset, verticalOffset), scaling);
var mouseOffset = ((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default;
return new PixelPoint(
screenOffset.X + mouseOffset.X,
screenOffset.Y + mouseOffset.Y);
}
return default(Point);
return default;
case PlacementMode.Bottom:
return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ??
zero;
return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ?? default;
case PlacementMode.Right:
return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? zero;
return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? default;
default:
throw new InvalidOperationException("Invalid value for Popup.PlacementMode");

10
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -85,18 +85,18 @@ namespace Avalonia.Controls.Primitives
if (screen != null)
{
var scaling = VisualRoot.RenderScaling;
var screenX = Position.X + (Bounds.Width * scaling) - screen.Bounds.X;
var screenY = Position.Y + (Bounds.Height * scaling) - screen.Bounds.Y;
var bounds = PixelRect.FromRect(Bounds, scaling);
var screenX = Position.X + bounds.Width - screen.Bounds.X;
var screenY = Position.Y + bounds.Height - screen.Bounds.Y;
if (screenX > screen.Bounds.Width)
{
Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width));
Position = Position.WithX(Position.X - screenX - bounds.Width);
}
if (screenY > screen.Bounds.Height)
{
Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height));
Position = Position.WithY(Position.Y - screenY - bounds.Height);
}
}
}

14
src/Avalonia.Controls/Screens.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls
_iScreenImpl = iScreenImpl;
}
public Screen ScreenFromBounds(Rect bounds){
public Screen ScreenFromBounds(PixelRect bounds){
Screen currMaxScreen = null;
double maxAreaSize = 0;
@ -39,16 +39,16 @@ namespace Avalonia.Controls
return currMaxScreen;
}
public Screen ScreenFromPoint(Point point)
public Screen ScreenFromPoint(PixelPoint point)
{
return All.FirstOrDefault(x=>x.Bounds.Contains(point));
return All.FirstOrDefault(x => x.Bounds.Contains(point));
}
public Screen ScreenFromVisual(IVisual visual)
{
Point tl = visual.PointToScreen(visual.Bounds.TopLeft);
Point br = visual.PointToScreen(visual.Bounds.BottomRight);
return ScreenFromBounds(new Rect(tl,br));
var tl = visual.PointToScreen(visual.Bounds.TopLeft);
var br = visual.PointToScreen(visual.Bounds.BottomRight);
return ScreenFromBounds(new PixelRect(tl, br));
}
}
}
}

13
src/Avalonia.Controls/Shapes/Shape.cs

@ -20,7 +20,10 @@ namespace Avalonia.Controls.Shapes
AvaloniaProperty.Register<Shape, IBrush>(nameof(Stroke));
public static readonly StyledProperty<AvaloniaList<double>> StrokeDashArrayProperty =
AvaloniaProperty.Register<Shape, AvaloniaList<double>>("StrokeDashArray");
AvaloniaProperty.Register<Shape, AvaloniaList<double>>(nameof(StrokeDashArray));
public static readonly StyledProperty<double> StrokeDashOffsetProperty =
AvaloniaProperty.Register<Shape, double>(nameof(StrokeDashOffset));
public static readonly StyledProperty<double> StrokeThicknessProperty =
AvaloniaProperty.Register<Shape, double>(nameof(StrokeThickness));
@ -103,6 +106,12 @@ namespace Avalonia.Controls.Shapes
get { return GetValue(StrokeDashArrayProperty); }
set { SetValue(StrokeDashArrayProperty, value); }
}
public double StrokeDashOffset
{
get { return GetValue(StrokeDashOffsetProperty); }
set { SetValue(StrokeDashOffsetProperty, value); }
}
public double StrokeThickness
{
@ -124,7 +133,7 @@ namespace Avalonia.Controls.Shapes
if (geometry != null)
{
var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray),
var pen = new Pen(Stroke, StrokeThickness, new DashStyle(StrokeDashArray, StrokeDashOffset),
StrokeDashCap, StrokeStartLineCap, StrokeEndLineCap, StrokeJoin);
context.DrawGeometry(Fill, pen, geometry);
}

7
src/Avalonia.Controls/TabControl.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls.Generators;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
@ -65,6 +66,10 @@ namespace Avalonia.Controls
SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
ItemsPanelProperty.OverrideDefaultValue<TabControl>(DefaultPanel);
AffectsMeasure<TabControl>(TabStripPlacementProperty);
ContentControlMixin.Attach<TabControl>(
SelectedContentProperty,
x => x.LogicalChildren,
"PART_SelectedContentHost");
}
/// <summary>
@ -142,7 +147,7 @@ namespace Avalonia.Controls
ItemsPresenterPart = e.NameScope.Get<ItemsPresenter>("PART_ItemsPresenter");
ContentPart = e.NameScope.Get<ContentPresenter>("PART_Content");
ContentPart = e.NameScope.Get<ContentPresenter>("PART_SelectedContentHost");
}
/// <inheritdoc/>

31
src/Avalonia.Controls/TopLevel.cs

@ -98,6 +98,11 @@ namespace Avalonia.Controls
Renderer = impl.CreateRenderer(this);
if (Renderer != null)
{
Renderer.SceneInvalidated += SceneInvalidated;
}
impl.SetInputRoot(this);
impl.Closed = HandleClosed;
@ -131,6 +136,11 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Fired when the window is opened.
/// </summary>
public event EventHandler Opened;
/// <summary>
/// Fired when the window is closed.
/// </summary>
@ -231,17 +241,17 @@ namespace Avalonia.Controls
{
PlatformImpl?.Invalidate(rect);
}
/// <inheritdoc/>
Point IRenderRoot.PointToClient(Point p)
Point IRenderRoot.PointToClient(PixelPoint p)
{
return PlatformImpl?.PointToClient(p) ?? default(Point);
return PlatformImpl?.PointToClient(p) ?? default;
}
/// <inheritdoc/>
Point IRenderRoot.PointToScreen(Point p)
PixelPoint IRenderRoot.PointToScreen(Point p)
{
return PlatformImpl?.PointToScreen(p) ?? default(Point);
return PlatformImpl?.PointToScreen(p) ?? default;
}
/// <summary>
@ -306,6 +316,12 @@ namespace Avalonia.Controls
$"Control '{GetType().Name}' is a top level control and cannot be added as a child.");
}
/// <summary>
/// Raises the <see cref="Opened"/> event.
/// </summary>
/// <param name="e">The event args.</param>
protected virtual void OnOpened(EventArgs e) => Opened?.Invoke(this, e);
/// <summary>
/// Tries to get a service from an <see cref="IAvaloniaDependencyResolver"/>, logging a
/// warning if not found.
@ -349,5 +365,10 @@ namespace Avalonia.Controls
{
_inputManager.ProcessInput(e);
}
private void SceneInvalidated(object sender, SceneInvalidatedEventArgs e)
{
(this as IInputRoot).MouseDevice.SceneInvalidated(this, e.DirtyRect);
}
}
}

60
src/Avalonia.Controls/Window.cs

@ -388,45 +388,49 @@ namespace Avalonia.Controls
PlatformImpl?.Show();
Renderer?.Start();
}
SetWindowStartupLocation();
SetWindowStartupLocation(Owner?.PlatformImpl);
OnOpened(EventArgs.Empty);
}
/// <summary>
/// Shows the window as a dialog.
/// </summary>
/// <param name="owner">The dialog's owner window.</param>
/// <returns>
/// A task that can be used to track the lifetime of the dialog.
/// </returns>
public Task ShowDialog(Window parent)
public Task ShowDialog(Window owner)
{
return ShowDialog<object>(parent);
return ShowDialog<object>(owner);
}
/// <summary>
/// Shows the window as a dialog.
/// </summary>
/// <typeparam name="TResult">
/// The type of the result produced by the dialog.
/// </typeparam>
/// <param name="owner">The dialog's owner window.</param>
/// <returns>.
/// A task that can be used to retrieve the result of the dialog when it closes.
/// </returns>
public Task<TResult> ShowDialog<TResult>(Window parent) => ShowDialog<TResult>(parent.PlatformImpl);
public Task<TResult> ShowDialog<TResult>(Window owner) => ShowDialog<TResult>(owner.PlatformImpl);
/// <summary>
/// Shows the window as a dialog.
/// </summary>
/// <typeparam name="TResult">
/// The type of the result produced by the dialog.
/// </typeparam>
/// <param name="owner">The dialog's owner window.</param>
/// <returns>.
/// A task that can be used to retrieve the result of the dialog when it closes.
/// </returns>
public Task<TResult> ShowDialog<TResult>(IWindowImpl parent)
public Task<TResult> ShowDialog<TResult>(IWindowImpl owner)
{
if(parent == null)
throw new ArgumentNullException(nameof(parent));
if(owner == null)
throw new ArgumentNullException(nameof(owner));
if (IsVisible)
{
throw new InvalidOperationException("The window is already being shown.");
@ -435,15 +439,15 @@ namespace Avalonia.Controls
AddWindow(this);
EnsureInitialized();
SetWindowStartupLocation();
IsVisible = true;
LayoutManager.ExecuteInitialLayoutPass(this);
var result = new TaskCompletionSource<TResult>();
using (BeginAutoSizing())
{
PlatformImpl?.ShowDialog(parent);
var result = new TaskCompletionSource<TResult>();
PlatformImpl?.ShowDialog(owner);
Renderer?.Start();
Observable.FromEventPattern<EventHandler, EventArgs>(
@ -452,29 +456,43 @@ namespace Avalonia.Controls
.Take(1)
.Subscribe(_ =>
{
parent.Activate();
owner.Activate();
result.SetResult((TResult)(_dialogResult ?? default(TResult)));
});
return result.Task;
OnOpened(EventArgs.Empty);
}
SetWindowStartupLocation(owner);
return result.Task;
}
void SetWindowStartupLocation()
private void SetWindowStartupLocation(IWindowBaseImpl owner = null)
{
var scaling = owner?.Scaling ?? PlatformImpl?.Scaling ?? 1;
// TODO: We really need non-client size here.
var rect = new PixelRect(
PixelPoint.Origin,
PixelSize.FromSize(ClientSize, scaling));
if (WindowStartupLocation == WindowStartupLocation.CenterScreen)
{
var screen = Screens.ScreenFromPoint(Bounds.Position);
var screen = Screens.ScreenFromPoint(owner?.Position ?? Position);
if (screen != null)
Position = screen.WorkingArea.CenterRect(new Rect(ClientSize)).Position;
{
Position = screen.WorkingArea.CenterRect(rect).Position;
}
}
else if (WindowStartupLocation == WindowStartupLocation.CenterOwner)
{
if (Owner != null)
if (owner != null)
{
var positionAsSize = Owner.ClientSize / 2 - ClientSize / 2;
Position = Owner.Position + new Point(positionAsSize.Width, positionAsSize.Height);
// TODO: We really need non-client size here.
var ownerRect = new PixelRect(
owner.Position,
PixelSize.FromSize(owner.ClientSize, scaling));
Position = ownerRect.CenterRect(rect).Position;
}
}
}

14
src/Avalonia.Controls/WindowBase.cs

@ -1,4 +1,5 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
@ -81,7 +82,7 @@ namespace Avalonia.Controls
/// <summary>
/// Fired when the window position is changed.
/// </summary>
public event EventHandler<PointEventArgs> PositionChanged;
public event EventHandler<PixelPointEventArgs> PositionChanged;
[CanBeNull]
public new IWindowBaseImpl PlatformImpl => (IWindowBaseImpl) base.PlatformImpl;
@ -98,9 +99,9 @@ namespace Avalonia.Controls
/// <summary>
/// Gets or sets the window position in screen coordinates.
/// </summary>
public Point Position
public PixelPoint Position
{
get { return PlatformImpl?.Position ?? default(Point); }
get { return PlatformImpl?.Position ?? PixelPoint.Origin; }
set
{
if (PlatformImpl is IWindowBaseImpl impl)
@ -163,7 +164,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Shows the popup.
/// Shows the window.
/// </summary>
public virtual void Show()
{
@ -181,6 +182,7 @@ namespace Avalonia.Controls
}
PlatformImpl?.Show();
Renderer?.Start();
OnOpened(EventArgs.Empty);
}
finally
{
@ -267,9 +269,9 @@ namespace Avalonia.Controls
/// <see cref="IWindowBaseImpl.PositionChanged"/>.
/// </summary>
/// <param name="pos">The window position.</param>
private void HandlePositionChanged(Point pos)
private void HandlePositionChanged(PixelPoint pos)
{
PositionChanged?.Invoke(this, new PointEventArgs(pos));
PositionChanged?.Invoke(this, new PixelPointEventArgs(pos));
}
/// <summary>

4
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -35,8 +35,8 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public Point Position { get; set; }
public Action<Point> PositionChanged { get; set; }
public PixelPoint Position { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public Action Deactivated { get; set; }
public Action Activated { get; set; }
public Func<bool> Closing { get; set; }

10
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -29,8 +29,8 @@ namespace Avalonia.DesignerSupport.Remote
public Func<bool> Closing { get; set; }
public Action Closed { get; set; }
public IMouseDevice MouseDevice { get; } = new MouseDevice();
public Point Position { get; set; }
public Action<Point> PositionChanged { get; set; }
public PixelPoint Position { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public WindowState WindowState { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root);
@ -45,9 +45,9 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public Point PointToClient(Point point) => point;
public Point PointToClient(PixelPoint p) => p.ToPoint(1);
public Point PointToScreen(Point point) => point;
public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);
public void SetCursor(IPlatformHandle cursor)
{
@ -157,6 +157,6 @@ namespace Avalonia.DesignerSupport.Remote
public int ScreenCount => 1;
public Screen[] AllScreens { get; } =
{new Screen(new Rect(0, 0, 4000, 4000), new Rect(0, 0, 4000, 4000), true)};
{new Screen(new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true)};
}
}

2
src/Avalonia.Input/IMouseDevice.cs

@ -11,6 +11,6 @@ namespace Avalonia.Input
/// <summary>
/// Gets the mouse position, in screen coordinates.
/// </summary>
Point Position { get; }
PixelPoint Position { get; }
}
}

2
src/Avalonia.Input/IPointerDevice.cs

@ -12,5 +12,7 @@ namespace Avalonia.Input
void Capture(IInputElement control);
Point GetPosition(IVisual relativeTo);
void SceneInvalidated(IInputRoot root, Rect rect);
}
}

53
src/Avalonia.Input/MouseDevice.cs

@ -54,7 +54,7 @@ namespace Avalonia.Input
/// <summary>
/// Gets the mouse position, in screen coordinates.
/// </summary>
public Point Position
public PixelPoint Position
{
get;
protected set;
@ -104,6 +104,23 @@ namespace Avalonia.Input
ProcessRawEvent(margs);
}
public void SceneInvalidated(IInputRoot root, Rect rect)
{
var clientPoint = root.PointToClient(Position);
if (rect.Contains(clientPoint))
{
if (Captured == null)
{
SetPointerOver(this, root, clientPoint);
}
else
{
SetPointerOver(this, root, Captured);
}
}
}
private void ProcessRawEvent(RawMouseEventArgs e)
{
Contract.Requires<ArgumentNullException>(e != null);
@ -305,16 +322,41 @@ namespace Avalonia.Input
Device = device,
};
if (element!=null && !element.IsAttachedToVisualTree)
{
// element has been removed from visual tree so do top down cleanup
if (root.IsPointerOver)
ClearChildrenPointerOver(e, root,true);
}
while (element != null)
{
e.Source = element;
e.Handled = false;
element.RaiseEvent(e);
element = (IInputElement)element.VisualParent;
}
root.PointerOverElement = null;
}
private void ClearChildrenPointerOver(PointerEventArgs e, IInputElement element,bool clearRoot)
{
foreach (IInputElement el in element.VisualChildren)
{
if (el.IsPointerOver)
{
ClearChildrenPointerOver(e, el, true);
break;
}
}
if(clearRoot)
{
e.Source = element;
e.Handled = false;
element.RaiseEvent(e);
}
}
private IInputElement SetPointerOver(IPointerDevice device, IInputRoot root, Point p)
{
Contract.Requires<ArgumentNullException>(device != null);
@ -361,13 +403,18 @@ namespace Avalonia.Input
el = root.PointerOverElement;
e.RoutedEvent = InputElement.PointerLeaveEvent;
if (el!=null && branch!=null && !el.IsAttachedToVisualTree)
{
ClearChildrenPointerOver(e,branch,false);
}
while (el != null && el != branch)
{
e.Source = el;
e.Handled = false;
el.RaiseEvent(e);
el = (IInputElement)el.VisualParent;
}
}
el = root.PointerOverElement = element;
e.RoutedEvent = InputElement.PointerEnterEvent;

15
src/Avalonia.Native/Helpers.cs

@ -12,11 +12,21 @@ namespace Avalonia.Native
return new Point(pt.X, pt.Y);
}
public static PixelPoint ToAvaloniaPixelPoint(this AvnPoint pt)
{
return new PixelPoint((int)pt.X, (int)pt.Y);
}
public static AvnPoint ToAvnPoint (this Point pt)
{
return new AvnPoint { X = pt.X, Y = pt.Y };
}
public static AvnPoint ToAvnPoint(this PixelPoint pt)
{
return new AvnPoint { X = pt.X, Y = pt.Y };
}
public static AvnSize ToAvnSize (this Size size)
{
return new AvnSize { Height = size.Height, Width = size.Width };
@ -31,5 +41,10 @@ namespace Avalonia.Native
{
return new Rect(rect.X, rect.Y, rect.Width, rect.Height);
}
public static PixelRect ToAvaloniaPixelRect(this AvnRect rect)
{
return new PixelRect((int)rect.X, (int)rect.Y, (int)rect.Width, (int)rect.Height);
}
}
}

5
src/Avalonia.Native/ScreenImpl.cs

@ -29,7 +29,10 @@ namespace Avalonia.Native
{
var screen = _native.GetScreen(i);
result[i] = new Screen(screen.Bounds.ToAvaloniaRect(), screen.WorkingArea.ToAvaloniaRect(), screen.Primary);
result[i] = new Screen(
screen.Bounds.ToAvaloniaPixelRect(),
screen.WorkingArea.ToAvaloniaPixelRect(),
screen.Primary);
}
return result;

14
src/Avalonia.Native/WindowImplBase.cs

@ -159,7 +159,7 @@ namespace Avalonia.Native
void IAvnWindowBaseEvents.PositionChanged(AvnPoint position)
{
_parent.PositionChanged?.Invoke(position.ToAvaloniaPoint());
_parent.PositionChanged?.Invoke(position.ToAvaloniaPixelPoint());
}
void IAvnWindowBaseEvents.RawMouseEvent(AvnRawMouseEventType type, uint timeStamp, AvnInputModifiers modifiers, AvnPoint point, AvnVector delta)
@ -275,20 +275,20 @@ namespace Avalonia.Native
}
public Point Position
public PixelPoint Position
{
get => _native.GetPosition().ToAvaloniaPoint();
get => _native.GetPosition().ToAvaloniaPixelPoint();
set => _native.SetPosition(value.ToAvnPoint());
}
public Point PointToClient(Point point)
public Point PointToClient(PixelPoint point)
{
return _native.PointToClient(point.ToAvnPoint()).ToAvaloniaPoint();
}
public Point PointToScreen(Point point)
public PixelPoint PointToScreen(Point point)
{
return _native.PointToScreen(point.ToAvnPoint()).ToAvaloniaPoint();
return _native.PointToScreen(point.ToAvnPoint()).ToAvaloniaPixelPoint();
}
public void Hide()
@ -320,7 +320,7 @@ namespace Avalonia.Native
_native.Cursor = newCursor.Cursor;
}
public Action<Point> PositionChanged { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public Action<RawInputEventArgs> Input { get; set; }

12
src/Avalonia.OpenGL/EglDisplay.cs

@ -86,18 +86,8 @@ namespace Avalonia.OpenGL
if (_contextAttributes == null)
throw new OpenGlException("No suitable EGL config was found");
GlInterface = new GlInterface((proc, optional) =>
{
using (var u = new Utf8Buffer(proc))
{
var rv = _egl.GetProcAddress(u);
if (rv == IntPtr.Zero && !optional)
throw new OpenGlException("Missing function " + proc);
return rv;
}
});
GlInterface = GlInterface.FromNativeUtf8GetProcAddress(b => _egl.GetProcAddress(b));
}
public EglDisplay() : this(new EglInterface())

28
src/Avalonia.OpenGL/EglInterface.cs

@ -34,61 +34,61 @@ namespace Avalonia.OpenGL
// ReSharper disable UnassignedGetOnlyAutoProperty
public delegate int EglGetError();
[EntryPoint("eglGetError")]
[GlEntryPoint("eglGetError")]
public EglGetError GetError { get; }
public delegate IntPtr EglGetDisplay(IntPtr nativeDisplay);
[EntryPoint("eglGetDisplay")]
[GlEntryPoint("eglGetDisplay")]
public EglGetDisplay GetDisplay { get; }
public delegate IntPtr EglGetPlatformDisplayEXT(int platform, IntPtr nativeDisplay, int[] attrs);
[EntryPoint("eglGetPlatformDisplayEXT", true)]
[GlEntryPoint("eglGetPlatformDisplayEXT", true)]
public EglGetPlatformDisplayEXT GetPlatformDisplayEXT { get; }
public delegate bool EglInitialize(IntPtr display, out int major, out int minor);
[EntryPoint("eglInitialize")]
[GlEntryPoint("eglInitialize")]
public EglInitialize Initialize { get; }
public delegate IntPtr EglGetProcAddress(Utf8Buffer proc);
[EntryPoint("eglGetProcAddress")]
[GlEntryPoint("eglGetProcAddress")]
public EglGetProcAddress GetProcAddress { get; }
public delegate bool EglBindApi(int api);
[EntryPoint("eglBindAPI")]
[GlEntryPoint("eglBindAPI")]
public EglBindApi BindApi { get; }
public delegate bool EglChooseConfig(IntPtr display, int[] attribs,
out IntPtr surfaceConfig, int numConfigs, out int choosenConfig);
[EntryPoint("eglChooseConfig")]
[GlEntryPoint("eglChooseConfig")]
public EglChooseConfig ChooseConfig { get; }
public delegate IntPtr EglCreateContext(IntPtr display, IntPtr config,
IntPtr share, int[] attrs);
[EntryPoint("eglCreateContext")]
[GlEntryPoint("eglCreateContext")]
public EglCreateContext CreateContext { get; }
public delegate IntPtr EglCreatePBufferSurface(IntPtr display, IntPtr config, int[] attrs);
[EntryPoint("eglCreatePbufferSurface")]
[GlEntryPoint("eglCreatePbufferSurface")]
public EglCreatePBufferSurface CreatePBufferSurface { get; }
public delegate bool EglMakeCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
[EntryPoint("eglMakeCurrent")]
[GlEntryPoint("eglMakeCurrent")]
public EglMakeCurrent MakeCurrent { get; }
public delegate void EglDisplaySurfaceVoidDelegate(IntPtr display, IntPtr surface);
[EntryPoint("eglDestroySurface")]
[GlEntryPoint("eglDestroySurface")]
public EglDisplaySurfaceVoidDelegate DestroySurface { get; }
[EntryPoint("eglSwapBuffers")]
[GlEntryPoint("eglSwapBuffers")]
public EglDisplaySurfaceVoidDelegate SwapBuffers { get; }
public delegate IntPtr
EglCreateWindowSurface(IntPtr display, IntPtr config, IntPtr window, int[] attrs);
[EntryPoint("eglCreateWindowSurface")]
[GlEntryPoint("eglCreateWindowSurface")]
public EglCreateWindowSurface CreateWindowSurface { get; }
public delegate bool EglGetConfigAttrib(IntPtr display, IntPtr config, int attr, out int rv);
[EntryPoint("eglGetConfigAttrib")]
[GlEntryPoint("eglGetConfigAttrib")]
public EglGetConfigAttrib GetConfigAttrib { get; }
// ReSharper restore UnassignedGetOnlyAutoProperty

5
src/Avalonia.OpenGL/EntryPointAttribute.cs → src/Avalonia.OpenGL/GlEntryPointAttribute.cs

@ -2,12 +2,13 @@ using System;
namespace Avalonia.OpenGL
{
class EntryPointAttribute : Attribute
[AttributeUsage(AttributeTargets.Property)]
public class GlEntryPointAttribute : Attribute
{
public string EntryPoint { get; }
public bool Optional { get; }
public EntryPointAttribute(string entryPoint, bool optional = false)
public GlEntryPointAttribute(string entryPoint, bool optional = false)
{
EntryPoint = entryPoint;
Optional = optional;

34
src/Avalonia.OpenGL/GlInterface.cs

@ -1,5 +1,6 @@
using System;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
namespace Avalonia.OpenGL
{
@ -7,43 +8,56 @@ namespace Avalonia.OpenGL
public class GlInterface : GlInterfaceBase
{
private readonly Func<string, bool, IntPtr> _getProcAddress;
public string Version { get; }
public GlInterface(Func<string, bool, IntPtr> getProcAddress) : base(getProcAddress)
{
_getProcAddress = getProcAddress;
var versionPtr = GetString(GlConsts.GL_VERSION);
if (versionPtr != IntPtr.Zero)
Version = Marshal.PtrToStringAnsi(versionPtr);
}
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true);
public GlInterface(Func<Utf8Buffer, IntPtr> n) : this(ConvertNative(n))
{
}
public static GlInterface FromNativeUtf8GetProcAddress(Func<Utf8Buffer, IntPtr> getProcAddress) =>
new GlInterface(getProcAddress);
public T GetProcAddress<T>(string proc) => Marshal.GetDelegateForFunctionPointer<T>(GetProcAddress(proc));
// ReSharper disable UnassignedGetOnlyAutoProperty
public delegate int GlGetError();
[EntryPoint("glGetError")]
[GlEntryPoint("glGetError")]
public GlGetError GetError { get; }
public delegate void GlClearStencil(int s);
[EntryPoint("glClearStencil")]
[GlEntryPoint("glClearStencil")]
public GlClearStencil ClearStencil { get; }
public delegate void GlClearColor(int r, int g, int b, int a);
[EntryPoint("glClearColor")]
[GlEntryPoint("glClearColor")]
public GlClearColor ClearColor { get; }
public delegate void GlClear(int bits);
[EntryPoint("glClear")]
[GlEntryPoint("glClear")]
public GlClear Clear { get; }
public delegate void GlViewport(int x, int y, int width, int height);
[EntryPoint("glViewport")]
[GlEntryPoint("glViewport")]
public GlViewport Viewport { get; }
[EntryPoint("glFlush")]
[GlEntryPoint("glFlush")]
public Action Flush { get; }
public delegate IntPtr GlGetString(int v);
[GlEntryPoint("glGetString")]
public GlGetString GetString { get; }
public delegate void GlGetIntegerv(int name, out int rv);
[EntryPoint("glGetIntegerv")]
[GlEntryPoint("glGetIntegerv")]
public GlGetIntegerv GetIntegerv { get; }
// ReSharper restore UnassignedGetOnlyAutoProperty

27
src/Avalonia.OpenGL/GlInterfaceBase.cs

@ -1,16 +1,20 @@
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using Avalonia.Platform.Interop;
namespace Avalonia.OpenGL
{
public class GlInterfaceBase
{
private readonly Func<string, bool, IntPtr> _getProcAddress;
public GlInterfaceBase(Func<string, bool, IntPtr> getProcAddress)
{
_getProcAddress = getProcAddress;
foreach (var prop in this.GetType().GetProperties())
{
var a = prop.GetCustomAttribute<EntryPointAttribute>();
var a = prop.GetCustomAttribute<GlEntryPointAttribute>();
if (a != null)
{
var fieldName = $"<{prop.Name}>k__BackingField";
@ -24,5 +28,26 @@ namespace Avalonia.OpenGL
}
}
}
protected static Func<string, bool, IntPtr> ConvertNative(Func<Utf8Buffer, IntPtr> func) =>
(proc, optional) =>
{
using (var u = new Utf8Buffer(proc))
{
var rv = func(u);
if (rv == IntPtr.Zero && !optional)
throw new OpenGlException("Missing function " + proc);
return rv;
}
};
public GlInterfaceBase(Func<Utf8Buffer, IntPtr> nativeGetProcAddress) : this(ConvertNative(nativeGetProcAddress))
{
}
public IntPtr GetProcAddress(string proc) => _getProcAddress(proc, true);
public IntPtr GetProcAddress(string proc, bool optional) => _getProcAddress(proc, optional);
}
}

6
src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs

@ -29,8 +29,8 @@ namespace Avalonia
{
var windowLoaded = Observable
.FromEventPattern(
x => window.Initialized += x,
x => window.Initialized -= x)
x => window.Opened += x,
x => window.Opened -= x)
.Select(args => true);
var windowUnloaded = Observable
.FromEventPattern(
@ -59,4 +59,4 @@ namespace Avalonia
.DistinctUntilChanged();
}
}
}
}

4
src/Avalonia.ReactiveUI/ReactiveUserControl.cs

@ -16,7 +16,7 @@ namespace Avalonia
public class ReactiveUserControl<TViewModel> : UserControl, IViewFor<TViewModel> where TViewModel : class
{
public static readonly AvaloniaProperty<TViewModel> ViewModelProperty = AvaloniaProperty
.Register<ReactiveWindow<TViewModel>, TViewModel>(nameof(ViewModel));
.Register<ReactiveUserControl<TViewModel>, TViewModel>(nameof(ViewModel));
/// <summary>
/// Initializes a new instance of the <see cref="ReactiveUserControl{TViewModel}"/> class.
@ -41,4 +41,4 @@ namespace Avalonia
set => ViewModel = (TViewModel)value;
}
}
}
}

7
src/Avalonia.Remote.Protocol/MetsysBson.cs

@ -1190,10 +1190,6 @@ namespace Metsys.Bson
object container = null;
var property = typeHelper.FindProperty(name);
var propertyType = property != null ? property.Type : _typeMap.ContainsKey(storageType) ? _typeMap[storageType] : typeof(object);
if (property == null && typeHelper.Expando == null)
{
throw new BsonException(string.Format("Deserialization failed: type {0} does not have a property named {1}", type.FullName, name));
}
if (property != null && property.Setter == null)
{
container = property.Getter(instance);
@ -1201,7 +1197,8 @@ namespace Metsys.Bson
var value = isNull ? null : DeserializeValue(propertyType, storageType, container, options);
if (property == null)
{
((IDictionary<string, object>)typeHelper.Expando.Getter(instance))[name] = value;
if (typeHelper.Expando != null)
((IDictionary<string, object>)typeHelper.Expando.Getter(instance))[name] = value;
}
else if (container == null && value != null && !property.Ignored)
{

6
src/Avalonia.Remote.Protocol/TcpTransportBase.cs

@ -46,7 +46,7 @@ namespace Avalonia.Remote.Protocol
{
try
{
var cl = await server.AcceptTcpClientAsync();
var cl = await server.AcceptTcpClientAsync().ConfigureAwait(false);
AcceptNew();
await Task.Run(async () =>
{
@ -54,7 +54,7 @@ namespace Avalonia.Remote.Protocol
var t = CreateTransport(_resolver, cl.GetStream(), () => tcs.TrySetResult(0));
cb(t);
await tcs.Task;
});
}).ConfigureAwait(false);
}
catch
{
@ -69,7 +69,7 @@ namespace Avalonia.Remote.Protocol
public async Task<IAvaloniaRemoteTransportConnection> Connect(IPAddress address, int port)
{
var c = new TcpClient();
await c.ConnectAsync(address, port);
await c.ConnectAsync(address, port).ConfigureAwait(false);
return CreateTransport(_resolver, c.GetStream(), ((IDisposable)c).Dispose);
}
}

7
src/Avalonia.Remote.Protocol/TransportConnectionWrapper.cs

@ -64,7 +64,7 @@ namespace Avalonia.Remote.Protocol
public Task Send(object data)
{
var tcs = new TaskCompletionSource<int>();
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
lock (_lock)
{
if (!_workerIsAlive)
@ -79,8 +79,9 @@ namespace Avalonia.Remote.Protocol
});
if (_signal != null)
{
_signal.SetResult(0);
var signal = _signal;
_signal = null;
signal.SetResult(0);
}
}
return tcs.Task;
@ -98,4 +99,4 @@ namespace Avalonia.Remote.Protocol
remove => _onException.Remove(value);
}
}
}
}

72
src/Avalonia.Styling/Styling/NotSelector.cs

@ -0,0 +1,72 @@
// 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 System.Reactive.Linq;
namespace Avalonia.Styling
{
/// <summary>
/// The `:not()` style selector.
/// </summary>
internal class NotSelector : Selector
{
private readonly Selector _previous;
private readonly Selector _argument;
private string _selectorString;
/// <summary>
/// Initializes a new instance of the <see cref="NotSelector"/> class.
/// </summary>
/// <param name="previous">The previous selector.</param>
/// <param name="argument">The selector to be not-ed.</param>
public NotSelector(Selector previous, Selector argument)
{
_previous = previous;
_argument = argument ?? throw new InvalidOperationException("Not selector must have a selector argument.");
}
/// <inheritdoc/>
public override bool InTemplate => _argument.InTemplate;
/// <inheritdoc/>
public override bool IsCombinator => false;
/// <inheritdoc/>
public override Type TargetType => _previous?.TargetType;
/// <inheritdoc/>
public override string ToString()
{
if (_selectorString == null)
{
_selectorString = ":not(" + _argument.ToString() + ")";
}
return _selectorString;
}
protected override SelectorMatch Evaluate(IStyleable control, bool subscribe)
{
var innerResult = _argument.Match(control, subscribe);
switch (innerResult.Result)
{
case SelectorMatchResult.AlwaysThisInstance:
return SelectorMatch.NeverThisInstance;
case SelectorMatchResult.AlwaysThisType:
return SelectorMatch.NeverThisType;
case SelectorMatchResult.NeverThisInstance:
return SelectorMatch.AlwaysThisInstance;
case SelectorMatchResult.NeverThisType:
return SelectorMatch.AlwaysThisType;
case SelectorMatchResult.Sometimes:
return new SelectorMatch(innerResult.Activator.Select(x => !x));
default:
throw new InvalidOperationException("Invalid SelectorMatchResult.");
}
}
protected override Selector MovePrevious() => _previous;
}
}

11
src/Avalonia.Styling/Styling/Selectors.cs

@ -94,6 +94,17 @@ namespace Avalonia.Styling
}
}
/// <summary>
/// Returns a selector which inverts the results of selector argument.
/// </summary>
/// <param name="previous">The previous selector.</param>
/// <param name="argument">The selector to be not-ed.</param>
/// <returns>The selector.</returns>
public static Selector Not(this Selector previous, Func<Selector, Selector> argument)
{
return new NotSelector(previous, argument(null));
}
/// <summary>
/// Returns a selector which matches a type.
/// </summary>

9
src/Avalonia.Themes.Default/CheckBox.xaml

@ -1,9 +1,10 @@
<Styles xmlns="https://github.com/avaloniaui">
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Style Selector="CheckBox">
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}"/>
<Setter Property="Background" Value="Transparent"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="Padding" Value="4,0,0,0"/>
<Setter Property="VerticalContentAlignment" Value="Center"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="Template">
@ -38,17 +39,15 @@
TextBlock.Foreground="{TemplateBinding Foreground}"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="4,0,0,0"
Margin="{TemplateBinding Padding}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
IsVisible="{TemplateBinding Content, Converter={x:Static ObjectConverters.IsNotNull}}"
Grid.Column="1"/>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="CheckBox:invalid /template/ ContentPresenter#PART_ContentPresenter">
<Setter Property="IsVisible" Value="False"/>
</Style>
<Style Selector="CheckBox:pointerover /template/ Border#border">
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderHighBrush}"/>
</Style>

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

@ -18,7 +18,7 @@
MemberSelector="{TemplateBinding MemberSelector}" >
</ItemsPresenter>
<ContentPresenter
Name="PART_Content"
Name="PART_SelectedContentHost"
Margin="{TemplateBinding Padding}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"

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

@ -23,7 +23,7 @@
Path="UseFloatingWatermark"/>
<Binding RelativeSource="{RelativeSource TemplatedParent}"
Path="Text"
Converter="{x:Static StringConverters.NotNullOrEmpty}"/>
Converter="{x:Static StringConverters.IsNotNullOrEmpty}"/>
</MultiBinding>
</TextBlock.IsVisible>
</TextBlock>
@ -36,7 +36,7 @@
<TextBlock Name="watermark"
Opacity="0.5"
Text="{TemplateBinding Watermark}"
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.NullOrEmpty}}"/>
IsVisible="{TemplateBinding Text, Converter={x:Static StringConverters.IsNullOrEmpty}}"/>
<TextPresenter Name="PART_TextPresenter"
Text="{TemplateBinding Text, Mode=TwoWay}"
CaretIndex="{TemplateBinding CaretIndex}"

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

@ -75,7 +75,7 @@ namespace Avalonia.Media.Imaging
public Vector Dpi => PlatformImpl.Item.Dpi;
/// <inheritdoc/>
public Size Size => PlatformImpl.Item.PixelSize.ToSize(Dpi);
public Size Size => PlatformImpl.Item.PixelSize.ToSizeWithDpi(Dpi);
/// <inheritdoc/>
public PixelSize PixelSize => PlatformImpl.Item.PixelSize;

201
src/Avalonia.Visuals/Media/PixelPoint.cs

@ -0,0 +1,201 @@
// 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 System.Globalization;
using Avalonia.Utilities;
namespace Avalonia
{
/// <summary>
/// Represents a point in device pixels.
/// </summary>
public readonly struct PixelPoint
{
/// <summary>
/// A point representing 0,0.
/// </summary>
public static readonly PixelPoint Origin = new PixelPoint(0, 0);
/// <summary>
/// Initializes a new instance of the <see cref="PixelPoint"/> structure.
/// </summary>
/// <param name="x">The X co-ordinate.</param>
/// <param name="y">The Y co-ordinate.</param>
public PixelPoint(int x, int y)
{
X = x;
Y = y;
}
/// <summary>
/// Gets the X co-ordinate.
/// </summary>
public int X { get; }
/// <summary>
/// Gets the Y co-ordinate.
/// </summary>
public int Y { get; }
/// <summary>
/// Checks for equality between two <see cref="PixelPoint"/>s.
/// </summary>
/// <param name="left">The first point.</param>
/// <param name="right">The second point.</param>
/// <returns>True if the points are equal; otherwise false.</returns>
public static bool operator ==(PixelPoint left, PixelPoint right)
{
return left.X == right.X && left.Y == right.Y;
}
/// <summary>
/// Checks for inequality between two <see cref="PixelPoint"/>s.
/// </summary>
/// <param name="left">The first point.</param>
/// <param name="right">The second point.</param>
/// <returns>True if the points are unequal; otherwise false.</returns>
public static bool operator !=(PixelPoint left, PixelPoint right)
{
return !(left == right);
}
/// <summary>
/// Parses a <see cref="PixelPoint"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The <see cref="PixelPoint"/>.</returns>
public static PixelPoint Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelPoint"))
{
return new PixelPoint(
tokenizer.ReadInt32(),
tokenizer.ReadInt32());
}
}
/// <summary>
/// Checks for equality between a point and an object.
/// </summary>
/// <param name="obj">The object.</param>
/// <returns>
/// True if <paramref name="obj"/> is a point that equals the current point.
/// </returns>
public override bool Equals(object obj)
{
if (obj is PixelPoint other)
{
return this == other;
}
return false;
}
/// <summary>
/// Returns a hash code for a <see cref="PixelPoint"/>.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = (hash * 23) + X.GetHashCode();
hash = (hash * 23) + Y.GetHashCode();
return hash;
}
}
/// <summary>
/// Returns a new <see cref="PixelPoint"/> with the same Y co-ordinate and the specified X co-ordinate.
/// </summary>
/// <param name="x">The X co-ordinate.</param>
/// <returns>The new <see cref="PixelPoint"/>.</returns>
public PixelPoint WithX(int x) => new PixelPoint(x, Y);
/// <summary>
/// Returns a new <see cref="PixelPoint"/> with the same X co-ordinate and the specified Y co-ordinate.
/// </summary>
/// <param name="y">The Y co-ordinate.</param>
/// <returns>The new <see cref="PixelPoint"/>.</returns>
public PixelPoint WithY(int y) => new PixelPoint(X, y);
/// <summary>
/// Converts the <see cref="PixelPoint"/> to a device-independent <see cref="Point"/> using the
/// specified scaling factor.
/// </summary>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent point.</returns>
public Point ToPoint(double scale) => new Point(X / scale, Y / scale);
/// <summary>
/// Converts the <see cref="PixelPoint"/> to a device-independent <see cref="Point"/> using the
/// specified scaling factor.
/// </summary>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent point.</returns>
public Point ToPoint(Vector scale) => new Point(X / scale.X, Y / scale.Y);
/// <summary>
/// Converts the <see cref="PixelPoint"/> to a device-independent <see cref="Point"/> using the
/// specified dots per inch (DPI).
/// </summary>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent point.</returns>
public Point ToPointWithDpi(double dpi) => ToPoint(dpi / 96);
/// <summary>
/// Converts the <see cref="PixelPoint"/> to a device-independent <see cref="Point"/> using the
/// specified dots per inch (DPI).
/// </summary>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent point.</returns>
public Point ToPointWithDpi(Vector dpi) => ToPoint(new Vector(dpi.X / 96, dpi.Y / 96));
/// <summary>
/// Converts a <see cref="Point"/> to device pixels using the specified scaling factor.
/// </summary>
/// <param name="point">The point.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent point.</returns>
public static PixelPoint FromPoint(Point point, double scale) => new PixelPoint(
(int)(point.X * scale),
(int)(point.Y * scale));
/// <summary>
/// Converts a <see cref="Point"/> to device pixels using the specified scaling factor.
/// </summary>
/// <param name="point">The point.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent point.</returns>
public static PixelPoint FromPoint(Point point, Vector scale) => new PixelPoint(
(int)(point.X * scale.X),
(int)(point.Y * scale.Y));
/// <summary>
/// Converts a <see cref="Point"/> to device pixels using the specified dots per inch (DPI).
/// </summary>
/// <param name="point">The point.</param>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent point.</returns>
public static PixelPoint FromPointWithDpi(Point point, double dpi) => FromPoint(point, dpi / 96);
/// <summary>
/// Converts a <see cref="Point"/> to device pixels using the specified dots per inch (DPI).
/// </summary>
/// <param name="point">The point.</param>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent point.</returns>
public static PixelPoint FromPointWithDpi(Point point, Vector dpi) => FromPoint(point, new Vector(dpi.X / 96, dpi.Y / 96));
/// <summary>
/// Returns the string representation of the point.
/// </summary>
/// <returns>The string representation of the point.</returns>
public override string ToString()
{
return string.Format(CultureInfo.InvariantCulture, "{0}, {1}", X, Y);
}
}
}

436
src/Avalonia.Visuals/Media/PixelRect.cs

@ -0,0 +1,436 @@
// 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 System.Globalization;
using Avalonia.Utilities;
namespace Avalonia
{
/// <summary>
/// Represents a rectangle in device pixels.
/// </summary>
public readonly struct PixelRect
{
/// <summary>
/// An empty rectangle.
/// </summary>
public static readonly PixelRect Empty = default;
/// <summary>
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
/// </summary>
/// <param name="x">The X position.</param>
/// <param name="y">The Y position.</param>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public PixelRect(int x, int y, int width, int height)
{
X = x;
Y = y;
Width = width;
Height = height;
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
/// </summary>
/// <param name="size">The size of the rectangle.</param>
public PixelRect(PixelSize size)
{
X = 0;
Y = 0;
Width = size.Width;
Height = size.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
/// </summary>
/// <param name="position">The position of the rectangle.</param>
/// <param name="size">The size of the rectangle.</param>
public PixelRect(PixelPoint position, PixelSize size)
{
X = position.X;
Y = position.Y;
Width = size.Width;
Height = size.Height;
}
/// <summary>
/// Initializes a new instance of the <see cref="PixelRect"/> structure.
/// </summary>
/// <param name="topLeft">The top left position of the rectangle.</param>
/// <param name="bottomRight">The bottom right position of the rectangle.</param>
public PixelRect(PixelPoint topLeft, PixelPoint bottomRight)
{
X = topLeft.X;
Y = topLeft.Y;
Width = bottomRight.X - topLeft.X;
Height = bottomRight.Y - topLeft.Y;
}
/// <summary>
/// Gets the X position.
/// </summary>
public int X { get; }
/// <summary>
/// Gets the Y position.
/// </summary>
public int Y { get; }
/// <summary>
/// Gets the width.
/// </summary>
public int Width { get; }
/// <summary>
/// Gets the height.
/// </summary>
public int Height { get; }
/// <summary>
/// Gets the position of the rectangle.
/// </summary>
public PixelPoint Position => new PixelPoint(X, Y);
/// <summary>
/// Gets the size of the rectangle.
/// </summary>
public PixelSize Size => new PixelSize(Width, Height);
/// <summary>
/// Gets the right position of the rectangle.
/// </summary>
public int Right => X + Width;
/// <summary>
/// Gets the bottom position of the rectangle.
/// </summary>
public int Bottom => Y + Height;
/// <summary>
/// Gets the top left point of the rectangle.
/// </summary>
public PixelPoint TopLeft => new PixelPoint(X, Y);
/// <summary>
/// Gets the top right point of the rectangle.
/// </summary>
public PixelPoint TopRight => new PixelPoint(Right, Y);
/// <summary>
/// Gets the bottom left point of the rectangle.
/// </summary>
public PixelPoint BottomLeft => new PixelPoint(X, Bottom);
/// <summary>
/// Gets the bottom right point of the rectangle.
/// </summary>
public PixelPoint BottomRight => new PixelPoint(Right, Bottom);
/// <summary>
/// Gets the center point of the rectangle.
/// </summary>
public PixelPoint Center => new PixelPoint(X + (Width / 2), Y + (Height / 2));
/// <summary>
/// Gets a value that indicates whether the rectangle is empty.
/// </summary>
public bool IsEmpty => Width == 0 && Height == 0;
/// <summary>
/// Checks for equality between two <see cref="PixelRect"/>s.
/// </summary>
/// <param name="left">The first rect.</param>
/// <param name="right">The second rect.</param>
/// <returns>True if the rects are equal; otherwise false.</returns>
public static bool operator ==(PixelRect left, PixelRect right)
{
return left.Position == right.Position && left.Size == right.Size;
}
/// <summary>
/// Checks for inequality between two <see cref="PixelRect"/>s.
/// </summary>
/// <param name="left">The first rect.</param>
/// <param name="right">The second rect.</param>
/// <returns>True if the rects are unequal; otherwise false.</returns>
public static bool operator !=(PixelRect left, PixelRect right)
{
return !(left == right);
}
/// <summary>
/// Determines whether a point in in the bounds of the rectangle.
/// </summary>
/// <param name="p">The point.</param>
/// <returns>true if the point is in the bounds of the rectangle; otherwise false.</returns>
public bool Contains(PixelPoint p)
{
return p.X >= X && p.X <= Right && p.Y >= Y && p.Y <= Bottom;
}
/// <summary>
/// Determines whether the rectangle fully contains another rectangle.
/// </summary>
/// <param name="r">The rectangle.</param>
/// <returns>true if the rectangle is fully contained; otherwise false.</returns>
public bool Contains(PixelRect r)
{
return Contains(r.TopLeft) && Contains(r.BottomRight);
}
/// <summary>
/// Centers another rectangle in this rectangle.
/// </summary>
/// <param name="rect">The rectangle to center.</param>
/// <returns>The centered rectangle.</returns>
public PixelRect CenterRect(PixelRect rect)
{
return new PixelRect(
X + ((Width - rect.Width) / 2),
Y + ((Height - rect.Height) / 2),
rect.Width,
rect.Height);
}
/// <summary>
/// Returns a boolean indicating whether the given object is equal to this rectangle.
/// </summary>
/// <param name="obj">The object to compare against.</param>
/// <returns>True if the object is equal to this rectangle; false otherwise.</returns>
public override bool Equals(object obj)
{
if (obj is PixelRect other)
{
return this == other;
}
return false;
}
/// <summary>
/// Returns the hash code for this instance.
/// </summary>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
unchecked
{
int hash = 17;
hash = (hash * 23) + X.GetHashCode();
hash = (hash * 23) + Y.GetHashCode();
hash = (hash * 23) + Width.GetHashCode();
hash = (hash * 23) + Height.GetHashCode();
return hash;
}
}
/// <summary>
/// Gets the intersection of two rectangles.
/// </summary>
/// <param name="rect">The other rectangle.</param>
/// <returns>The intersection.</returns>
public PixelRect Intersect(PixelRect rect)
{
var newLeft = (rect.X > X) ? rect.X : X;
var newTop = (rect.Y > Y) ? rect.Y : Y;
var newRight = (rect.Right < Right) ? rect.Right : Right;
var newBottom = (rect.Bottom < Bottom) ? rect.Bottom : Bottom;
if ((newRight > newLeft) && (newBottom > newTop))
{
return new PixelRect(newLeft, newTop, newRight - newLeft, newBottom - newTop);
}
else
{
return Empty;
}
}
/// <summary>
/// Determines whether a rectangle intersects with this rectangle.
/// </summary>
/// <param name="rect">The other rectangle.</param>
/// <returns>
/// True if the specified rectangle intersects with this one; otherwise false.
/// </returns>
public bool Intersects(PixelRect rect)
{
return (rect.X < Right) && (X < rect.Right) && (rect.Y < Bottom) && (Y < rect.Bottom);
}
/// <summary>
/// Gets the union of two rectangles.
/// </summary>
/// <param name="rect">The other rectangle.</param>
/// <returns>The union.</returns>
public PixelRect Union(PixelRect rect)
{
if (IsEmpty)
{
return rect;
}
else if (rect.IsEmpty)
{
return this;
}
else
{
var x1 = Math.Min(X, rect.X);
var x2 = Math.Max(Right, rect.Right);
var y1 = Math.Min(Y, rect.Y);
var y2 = Math.Max(Bottom, rect.Bottom);
return new PixelRect(new PixelPoint(x1, y1), new PixelPoint(x2, y2));
}
}
/// <summary>
/// Returns a new <see cref="PixelRect"/> with the specified X position.
/// </summary>
/// <param name="x">The x position.</param>
/// <returns>The new <see cref="PixelRect"/>.</returns>
public PixelRect WithX(int x)
{
return new PixelRect(x, Y, Width, Height);
}
/// <summary>
/// Returns a new <see cref="PixelRect"/> with the specified Y position.
/// </summary>
/// <param name="y">The y position.</param>
/// <returns>The new <see cref="PixelRect"/>.</returns>
public PixelRect WithY(int y)
{
return new PixelRect(X, y, Width, Height);
}
/// <summary>
/// Returns a new <see cref="PixelRect"/> with the specified width.
/// </summary>
/// <param name="width">The width.</param>
/// <returns>The new <see cref="PixelRect"/>.</returns>
public PixelRect WithWidth(int width)
{
return new PixelRect(X, Y, width, Height);
}
/// <summary>
/// Returns a new <see cref="PixelRect"/> with the specified height.
/// </summary>
/// <param name="height">The height.</param>
/// <returns>The new <see cref="PixelRect"/>.</returns>
public PixelRect WithHeight(int height)
{
return new PixelRect(X, Y, Width, Height);
}
/// <summary>
/// Converts the <see cref="PixelRect"/> to a device-independent <see cref="Rect"/> using the
/// specified scaling factor.
/// </summary>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent rect.</returns>
public Rect ToRect(double scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale));
/// <summary>
/// Converts the <see cref="PixelRect"/> to a device-independent <see cref="Rect"/> using the
/// specified scaling factor.
/// </summary>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent rect.</returns>
public Rect ToRect(Vector scale) => new Rect(Position.ToPoint(scale), Size.ToSize(scale));
/// <summary>
/// Converts the <see cref="PixelRect"/> to a device-independent <see cref="Rect"/> using the
/// specified dots per inch (DPI).
/// </summary>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent rect.</returns>
public Rect ToRectWithDpi(double dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi));
/// <summary>
/// Converts the <see cref="PixelRect"/> to a device-independent <see cref="Rect"/> using the
/// specified dots per inch (DPI).
/// </summary>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent rect.</returns>
public Rect ToRectWithDpi(Vector dpi) => new Rect(Position.ToPointWithDpi(dpi), Size.ToSizeWithDpi(dpi));
/// <summary>
/// Converts a <see cref="Rect"/> to device pixels using the specified scaling factor.
/// </summary>
/// <param name="rect">The rect.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent rect.</returns>
public static PixelRect FromRect(Rect rect, double scale) => new PixelRect(
PixelPoint.FromPoint(rect.Position, scale),
PixelSize.FromSize(rect.Size, scale));
/// <summary>
/// Converts a <see cref="Rect"/> to device pixels using the specified scaling factor.
/// </summary>
/// <param name="rect">The rect.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent point.</returns>
public static PixelRect FromRect(Rect rect, Vector scale) => new PixelRect(
PixelPoint.FromPoint(rect.Position, scale),
PixelSize.FromSize(rect.Size, scale));
/// <summary>
/// Converts a <see cref="Rect"/> to device pixels using the specified dots per inch (DPI).
/// </summary>
/// <param name="rect">The rect.</param>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent point.</returns>
public static PixelRect FromRectWithDpi(Rect rect, double dpi) => new PixelRect(
PixelPoint.FromPointWithDpi(rect.Position, dpi),
PixelSize.FromSizeWithDpi(rect.Size, dpi));
/// <summary>
/// Converts a <see cref="Rect"/> to device pixels using the specified dots per inch (DPI).
/// </summary>
/// <param name="rect">The rect.</param>
/// <param name="dpi">The dots per inch of the device.</param>
/// <returns>The device-independent point.</returns>
public static PixelRect FromRectWithDpi(Rect rect, Vector dpi) => new PixelRect(
PixelPoint.FromPointWithDpi(rect.Position, dpi),
PixelSize.FromSizeWithDpi(rect.Size, dpi));
/// <summary>
/// Returns the string representation of the rectangle.
/// </summary>
/// <returns>The string representation of the rectangle.</returns>
public override string ToString()
{
return string.Format(
CultureInfo.InvariantCulture,
"{0}, {1}, {2}, {3}",
X,
Y,
Width,
Height);
}
/// <summary>
/// Parses a <see cref="PixelRect"/> string.
/// </summary>
/// <param name="s">The string.</param>
/// <returns>The parsed <see cref="PixelRect"/>.</returns>
public static PixelRect Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelRect"))
{
return new PixelRect(
tokenizer.ReadInt32(),
tokenizer.ReadInt32(),
tokenizer.ReadInt32(),
tokenizer.ReadInt32()
);
}
}
}
}

50
src/Avalonia.Visuals/Media/PixelSize.cs

@ -72,7 +72,7 @@ namespace Avalonia
/// <returns>The <see cref="PixelSize"/>.</returns>
public static PixelSize Parse(string s)
{
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid Size"))
using (var tokenizer = new StringTokenizer(s, CultureInfo.InvariantCulture, exceptionMessage: "Invalid PixelSize"))
{
return new PixelSize(
tokenizer.ReadInt32(),
@ -126,13 +126,29 @@ namespace Avalonia
/// <returns>The new <see cref="PixelSize"/>.</returns>
public PixelSize WithHeight(int height) => new PixelSize(Width, height);
/// <summary>
/// Converts the <see cref="PixelSize"/> to a device-independent <see cref="Size"/> using the
/// specified scaling factor.
/// </summary>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent size.</returns>
public Size ToSize(double scale) => new Size(Width / scale, Height / scale);
/// <summary>
/// Converts the <see cref="PixelSize"/> to a device-independent <see cref="Size"/> using the
/// specified scaling factor.
/// </summary>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent size.</returns>
public Size ToSize(Vector scale) => new Size(Width / scale.X, Height / scale.Y);
/// <summary>
/// Converts the <see cref="PixelSize"/> to a device-independent <see cref="Size"/> using the
/// specified dots per inch (DPI).
/// </summary>
/// <param name="dpi">The dots per inch.</param>
/// <returns>The device-independent size.</returns>
public Size ToSize(double dpi) => new Size(Width / (dpi / 96), Height / (dpi / 96));
public Size ToSizeWithDpi(double dpi) => ToSize(dpi / 96);
/// <summary>
/// Converts the <see cref="PixelSize"/> to a device-independent <see cref="Size"/> using the
@ -140,7 +156,27 @@ namespace Avalonia
/// </summary>
/// <param name="dpi">The dots per inch.</param>
/// <returns>The device-independent size.</returns>
public Size ToSize(Vector dpi) => new Size(Width / (dpi.X / 96), Height / (dpi.Y / 96));
public Size ToSizeWithDpi(Vector dpi) => ToSize(new Vector(dpi.X / 96, dpi.Y / 96));
/// <summary>
/// Converts a <see cref="Size"/> to device pixels using the specified scaling factor.
/// </summary>
/// <param name="size">The size.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent size.</returns>
public static PixelSize FromSize(Size size, double scale) => new PixelSize(
(int)Math.Ceiling(size.Width * scale),
(int)Math.Ceiling(size.Height * scale));
/// <summary>
/// Converts a <see cref="Size"/> to device pixels using the specified scaling factor.
/// </summary>
/// <param name="size">The size.</param>
/// <param name="scale">The scaling factor.</param>
/// <returns>The device-independent size.</returns>
public static PixelSize FromSize(Size size, Vector scale) => new PixelSize(
(int)Math.Ceiling(size.Width * scale.X),
(int)Math.Ceiling(size.Height * scale.Y));
/// <summary>
/// Converts a <see cref="Size"/> to device pixels using the specified dots per inch (DPI).
@ -148,9 +184,7 @@ namespace Avalonia
/// <param name="size">The size.</param>
/// <param name="dpi">The dots per inch.</param>
/// <returns>The device-independent size.</returns>
public static PixelSize FromSize(Size size, double dpi) => new PixelSize(
(int)(size.Width * (dpi / 96)),
(int)(size.Height * (dpi / 96)));
public static PixelSize FromSizeWithDpi(Size size, double dpi) => FromSize(size, dpi / 96);
/// <summary>
/// Converts a <see cref="Size"/> to device pixels using the specified dots per inch (DPI).
@ -158,9 +192,7 @@ namespace Avalonia
/// <param name="size">The size.</param>
/// <param name="dpi">The dots per inch.</param>
/// <returns>The device-independent size.</returns>
public static PixelSize FromSize(Size size, Vector dpi) => new PixelSize(
(int)Math.Ceiling(size.Width * (dpi.X / 96)),
(int)Math.Ceiling(size.Height * (dpi.Y / 96)));
public static PixelSize FromSizeWithDpi(Size size, Vector dpi) => FromSize(size, new Vector(dpi.X / 96, dpi.Y / 96));
/// <summary>
/// Returns the string representation of the size.

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

@ -29,6 +29,7 @@ namespace Avalonia.Rendering
private readonly ISceneBuilder _sceneBuilder;
private bool _running;
private bool _disposed;
private volatile IRef<Scene> _scene;
private DirtyVisuals _dirty;
private IRef<IRenderTargetBitmapImpl> _overlay;
@ -99,6 +100,9 @@ namespace Avalonia.Rendering
/// </summary>
public string DebugFramesPath { get; set; }
/// <inheritdoc/>
public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
/// <summary>
/// Gets the render layers.
/// </summary>
@ -122,6 +126,9 @@ namespace Avalonia.Rendering
{
lock (_sceneLock)
{
if (_disposed)
return;
_disposed = true;
var scene = _scene;
_scene = null;
scene?.Dispose();
@ -168,7 +175,7 @@ namespace Avalonia.Rendering
var t = (IRenderLoopTask)this;
if(t.NeedsUpdate)
UpdateScene();
if(_scene.Item != null)
if(_scene?.Item != null)
Render(true);
}
@ -248,15 +255,19 @@ namespace Avalonia.Rendering
}
var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(GetContext);
using (scene)
{
var overlay = DrawDirtyRects || DrawFps;
if (DrawDirtyRects)
_dirtyRectsDisplay.Tick();
if (overlay)
RenderOverlay(scene.Item, GetContext());
if (updated || forceComposite || overlay)
RenderComposite(scene.Item, GetContext());
if (scene?.Item != null)
{
var overlay = DrawDirtyRects || DrawFps;
if (DrawDirtyRects)
_dirtyRectsDisplay.Tick();
if (overlay)
RenderOverlay(scene.Item, GetContext());
if (updated || forceComposite || overlay)
RenderComposite(scene.Item, GetContext());
}
}
}
finally
@ -478,6 +489,8 @@ namespace Avalonia.Rendering
Dispatcher.UIThread.VerifyAccess();
lock (_sceneLock)
{
if (_disposed)
return;
if (_scene?.Item.Generation > _lastSceneId)
return;
}
@ -506,6 +519,21 @@ namespace Avalonia.Rendering
oldScene?.Dispose();
}
if (SceneInvalidated != null)
{
var rect = new Rect();
foreach (var layer in scene.Layers)
{
foreach (var dirty in layer.Dirty)
{
rect = rect.Union(dirty);
}
}
SceneInvalidated(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect));
}
_dirty.Clear();
}
else

8
src/Avalonia.Visuals/Rendering/IRenderRoot.cs

@ -41,15 +41,15 @@ namespace Avalonia.Rendering
/// <summary>
/// Converts a point from screen to client coordinates.
/// </summary>
/// <param name="point">The point in screen coordinates.</param>
/// <param name="point">The point in screen device coordinates.</param>
/// <returns>The point in client coordinates.</returns>
Point PointToClient(Point point);
Point PointToClient(PixelPoint point);
/// <summary>
/// Converts a point from client to screen coordinates.
/// </summary>
/// <param name="point">The point in client coordinates.</param>
/// <returns>The point in screen coordinates.</returns>
Point PointToScreen(Point point);
/// <returns>The point in screen device coordinates.</returns>
PixelPoint PointToScreen(Point point);
}
}

13
src/Avalonia.Visuals/Rendering/IRenderer.cs

@ -18,11 +18,20 @@ namespace Avalonia.Rendering
bool DrawFps { get; set; }
/// <summary>
/// Gets or sets a value indicating whether the renderer should a visual representation
/// Gets or sets a value indicating whether the renderer should draw a visual representation
/// of its dirty rectangles.
/// </summary>
bool DrawDirtyRects { get; set; }
/// <summary>
/// Raised when a portion of the scene has been invalidated.
/// </summary>
/// <remarks>
/// Indicates that the underlying low-level scene information has been updated. Used to
/// signal that an update to the current pointer-over state may be required.
/// </remarks>
event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
/// <summary>
/// Mark a visual as dirty and needing re-rendering.
/// </summary>
@ -63,4 +72,4 @@ namespace Avalonia.Rendering
/// </summary>
void Stop();
}
}
}

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

@ -42,6 +42,9 @@ namespace Avalonia.Rendering
/// <inheritdoc/>
public bool DrawDirtyRects { get; set; }
/// <inheritdoc/>
public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated;
/// <inheritdoc/>
public void Paint(Rect rect)
{
@ -81,6 +84,8 @@ namespace Avalonia.Rendering
_renderTarget.Dispose();
_renderTarget = null;
}
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs((IRenderRoot)_root, rect));
}
/// <inheritdoc/>

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

@ -160,7 +160,7 @@ namespace Avalonia.Rendering.SceneGraph
private IEnumerable<IVisual> HitTest(IVisualNode node, Point p, Rect? clip, Func<IVisual, bool> filter)
{
if (filter?.Invoke(node.Visual) != false)
if (filter?.Invoke(node.Visual) != false && node.Visual.IsAttachedToVisualTree)
{
var clipped = false;
@ -186,7 +186,7 @@ namespace Avalonia.Rendering.SceneGraph
}
}
if (node.HitTest(p) && node.Visual.IsAttachedToVisualTree)
if (node.HitTest(p))
{
yield return node.Visual;
}

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

@ -236,7 +236,7 @@ namespace Avalonia.Rendering.SceneGraph
{
foreach (var operation in DrawOperations)
{
if (operation.Item.HitTest(p) == true)
if (operation.Item.HitTest(p))
{
return true;
}

36
src/Avalonia.Visuals/Rendering/SceneInvalidatedEventArgs.cs

@ -0,0 +1,36 @@
// 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;
namespace Avalonia.Rendering
{
/// <summary>
/// Provides data for the <see cref="IRenderer.SceneInvalidated"/> event.
/// </summary>
public class SceneInvalidatedEventArgs : EventArgs
{
/// <summary>
/// Initializes a new instance of the <see cref="SceneInvalidatedEventArgs"/> class.
/// </summary>
/// <param name="root">The render root that has been updated.</param>
/// <param name="dirtyRect">The updated area.</param>
public SceneInvalidatedEventArgs(
IRenderRoot root,
Rect dirtyRect)
{
RenderRoot = root;
DirtyRect = dirtyRect;
}
/// <summary>
/// Gets the invalidated area.
/// </summary>
public Rect DirtyRect { get; }
/// <summary>
/// Gets the render root that has been invalidated.
/// </summary>
public IRenderRoot RenderRoot { get; }
}
}

10
src/Avalonia.Visuals/VisualExtensions.cs

@ -18,10 +18,12 @@ namespace Avalonia
/// <param name="visual">The visual.</param>
/// <param name="point">The point in screen coordinates.</param>
/// <returns>The point in client coordinates.</returns>
public static Point PointToClient(this IVisual visual, Point point)
public static Point PointToClient(this IVisual visual, PixelPoint point)
{
var p = GetRootAndPosition(visual);
return p.Item1.PointToClient(point - p.Item2);
var (root, offset) = GetRootAndPosition(visual);
var screenOffset = PixelPoint.FromPoint((Point)offset, root.RenderScaling);
var screenPoint = new PixelPoint(point.X - screenOffset.X, point.Y - screenOffset.Y);
return root.PointToClient(screenPoint);
}
/// <summary>
@ -30,7 +32,7 @@ namespace Avalonia
/// <param name="visual">The visual.</param>
/// <param name="point">The point in client coordinates.</param>
/// <returns>The point in screen coordinates.</returns>
public static Point PointToScreen(this IVisual visual, Point point)
public static PixelPoint PointToScreen(this IVisual visual, Point point)
{
var p = GetRootAndPosition(visual);
return p.Item1.PointToScreen(point + p.Item2);

1
src/Avalonia.X11/Avalonia.X11.csproj

@ -7,7 +7,6 @@
<ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Gtk\Avalonia.Gtk3\Avalonia.Gtk3.csproj" />
<ProjectReference Include="..\Skia\Avalonia.Skia\Avalonia.Skia.csproj" />
</ItemGroup>

91
src/Avalonia.X11/Glx/Glx.cs

@ -0,0 +1,91 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using Avalonia.OpenGL;
using Avalonia.Platform.Interop;
// ReSharper disable UnassignedGetOnlyAutoProperty
namespace Avalonia.X11.Glx
{
unsafe class GlxInterface : GlInterfaceBase
{
private const string libGL = "libGL.so.1";
[GlEntryPointAttribute("glXMakeContextCurrent")]
public GlxMakeContextCurrent MakeContextCurrent { get; }
public delegate bool GlxMakeContextCurrent(IntPtr display, IntPtr draw, IntPtr read, IntPtr context);
[GlEntryPoint("glXCreatePbuffer")]
public GlxCreatePbuffer CreatePbuffer { get; }
public delegate IntPtr GlxCreatePbuffer(IntPtr dpy, IntPtr fbc, int[] attrib_list);
[GlEntryPointAttribute("glXChooseVisual")]
public GlxChooseVisual ChooseVisual { get; }
public delegate XVisualInfo* GlxChooseVisual(IntPtr dpy, int screen, int[] attribList);
[GlEntryPointAttribute("glXCreateContext")]
public GlxCreateContext CreateContext { get; }
public delegate IntPtr GlxCreateContext(IntPtr dpy, XVisualInfo* vis, IntPtr shareList, bool direct);
[GlEntryPointAttribute("glXCreateContextAttribsARB")]
public GlxCreateContextAttribsARB CreateContextAttribsARB { get; }
public delegate IntPtr GlxCreateContextAttribsARB(IntPtr dpy, IntPtr fbconfig, IntPtr shareList,
bool direct, int[] attribs);
[DllImport(libGL, EntryPoint = "glXGetProcAddress")]
public static extern IntPtr GlxGetProcAddress(Utf8Buffer buffer);
[GlEntryPointAttribute("glXDestroyContext")]
public GlxDestroyContext DestroyContext { get; }
public delegate void GlxDestroyContext(IntPtr dpy, IntPtr ctx);
[GlEntryPointAttribute("glXChooseFBConfig")]
public GlxChooseFBConfig ChooseFBConfig { get; }
public delegate IntPtr* GlxChooseFBConfig(IntPtr dpy, int screen, int[] attrib_list, out int nelements);
public IntPtr* GlxChooseFbConfig(IntPtr dpy, int screen, IEnumerable<int> attribs, out int nelements)
{
var arr = attribs.Concat(new[]{0}).ToArray();
return ChooseFBConfig(dpy, screen, arr, out nelements);
}
[GlEntryPointAttribute("glXGetVisualFromFBConfig")]
public GlxGetVisualFromFBConfig GetVisualFromFBConfig { get; }
public delegate XVisualInfo * GlxGetVisualFromFBConfig(IntPtr dpy, IntPtr config);
[GlEntryPointAttribute("glXGetFBConfigAttrib")]
public GlxGetFBConfigAttrib GetFBConfigAttrib { get; }
public delegate int GlxGetFBConfigAttrib(IntPtr dpy, IntPtr config, int attribute, out int value);
[GlEntryPointAttribute("glXSwapBuffers")]
public GlxSwapBuffers SwapBuffers { get; }
public delegate void GlxSwapBuffers(IntPtr dpy, IntPtr drawable);
[GlEntryPointAttribute("glXWaitX")]
public GlxWaitX WaitX { get; }
public delegate void GlxWaitX();
[GlEntryPointAttribute("glXWaitGL")]
public GlxWaitGL WaitGL { get; }
public delegate void GlxWaitGL();
public delegate int GlGetError();
[GlEntryPoint("glGetError")]
public GlGetError GetError { get; }
public GlxInterface() : base(GlxGetProcAddress)
{
}
}
}

106
src/Avalonia.X11/Glx/GlxConsts.cs

@ -0,0 +1,106 @@
// ReSharper disable InconsistentNaming
// ReSharper disable IdentifierTypo
// ReSharper disable UnusedMember.Global
#pragma warning disable 414
namespace Avalonia.X11.Glx
{
class GlxConsts
{
public const int GLX_USE_GL = 1;
public const int GLX_BUFFER_SIZE = 2;
public const int GLX_LEVEL = 3;
public const int GLX_RGBA = 4;
public const int GLX_DOUBLEBUFFER = 5;
public const int GLX_STEREO = 6;
public const int GLX_AUX_BUFFERS = 7;
public const int GLX_RED_SIZE = 8;
public const int GLX_GREEN_SIZE = 9;
public const int GLX_BLUE_SIZE = 10;
public const int GLX_ALPHA_SIZE = 11;
public const int GLX_DEPTH_SIZE = 12;
public const int GLX_STENCIL_SIZE = 13;
public const int GLX_ACCUM_RED_SIZE = 14;
public const int GLX_ACCUM_GREEN_SIZE = 15;
public const int GLX_ACCUM_BLUE_SIZE = 16;
public const int GLX_ACCUM_ALPHA_SIZE = 17;
public const int GLX_BAD_SCREEN = 1;
public const int GLX_BAD_ATTRIBUTE = 2;
public const int GLX_NO_EXTENSION = 3;
public const int GLX_BAD_VISUAL = 4;
public const int GLX_BAD_CONTEXT = 5;
public const int GLX_BAD_VALUE = 6;
public const int GLX_BAD_ENUM = 7;
public const int GLX_VENDOR = 1;
public const int GLX_VERSION = 2;
public const int GLX_EXTENSIONS= 3;
public const int GLX_CONFIG_CAVEAT = 0x20;
public const int GLX_DONT_CARE = unchecked((int)0xFFFFFFFF);
public const int GLX_X_VISUAL_TYPE = 0x22;
public const int GLX_TRANSPARENT_TYPE = 0x23;
public const int GLX_TRANSPARENT_INDEX_VALUE = 0x24;
public const int GLX_TRANSPARENT_RED_VALUE = 0x25;
public const int GLX_TRANSPARENT_GREEN_VALUE = 0x26;
public const int GLX_TRANSPARENT_BLUE_VALUE = 0x27;
public const int GLX_TRANSPARENT_ALPHA_VALUE = 0x28;
public const int GLX_WINDOW_BIT = 0x00000001;
public const int GLX_PIXMAP_BIT = 0x00000002;
public const int GLX_PBUFFER_BIT = 0x00000004;
public const int GLX_AUX_BUFFERS_BIT = 0x00000010;
public const int GLX_FRONT_LEFT_BUFFER_BIT = 0x00000001;
public const int GLX_FRONT_RIGHT_BUFFER_BIT = 0x00000002;
public const int GLX_BACK_LEFT_BUFFER_BIT = 0x00000004;
public const int GLX_BACK_RIGHT_BUFFER_BIT = 0x00000008;
public const int GLX_DEPTH_BUFFER_BIT = 0x00000020;
public const int GLX_STENCIL_BUFFER_BIT = 0x00000040;
public const int GLX_ACCUM_BUFFER_BIT = 0x00000080;
public const int GLX_NONE = 0x8000;
public const int GLX_SLOW_CONFIG = 0x8001;
public const int GLX_TRUE_COLOR = 0x8002;
public const int GLX_DIRECT_COLOR = 0x8003;
public const int GLX_PSEUDO_COLOR = 0x8004;
public const int GLX_STATIC_COLOR = 0x8005;
public const int GLX_GRAY_SCALE = 0x8006;
public const int GLX_STATIC_GRAY = 0x8007;
public const int GLX_TRANSPARENT_RGB = 0x8008;
public const int GLX_TRANSPARENT_INDEX = 0x8009;
public const int GLX_VISUAL_ID = 0x800B;
public const int GLX_SCREEN = 0x800C;
public const int GLX_NON_CONFORMANT_CONFIG = 0x800D;
public const int GLX_DRAWABLE_TYPE = 0x8010;
public const int GLX_RENDER_TYPE = 0x8011;
public const int GLX_X_RENDERABLE = 0x8012;
public const int GLX_FBCONFIG_ID = 0x8013;
public const int GLX_RGBA_TYPE = 0x8014;
public const int GLX_COLOR_INDEX_TYPE = 0x8015;
public const int GLX_MAX_PBUFFER_WIDTH = 0x8016;
public const int GLX_MAX_PBUFFER_HEIGHT = 0x8017;
public const int GLX_MAX_PBUFFER_PIXELS = 0x8018;
public const int GLX_PRESERVED_CONTENTS = 0x801B;
public const int GLX_LARGEST_PBUFFER = 0x801C;
public const int GLX_WIDTH = 0x801D;
public const int GLX_HEIGHT = 0x801E;
public const int GLX_EVENT_MASK = 0x801F;
public const int GLX_DAMAGED = 0x8020;
public const int GLX_SAVED = 0x8021;
public const int GLX_WINDOW = 0x8022;
public const int GLX_PBUFFER = 0x8023;
public const int GLX_PBUFFER_HEIGHT = 0x8040;
public const int GLX_PBUFFER_WIDTH = 0x8041;
public const int GLX_RGBA_BIT = 0x00000001;
public const int GLX_COLOR_INDEX_BIT = 0x00000002;
public const int GLX_PBUFFER_CLOBBER_MASK = 0x08000000;
public const int GLX_SAMPLE_BUFFERS = 0x186a0 /*100000*/;
public const int GLX_SAMPLES = 0x186a1 /*100001*/;
public const int GLX_PbufferClobber = 0;
public const int GLX_BufferSwapComplete = 1;
public const int GLX_CONTEXT_DEBUG_BIT_ARB = 0x00000001;
public const int GLX_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB = 0x00000002;
public const int GLX_CONTEXT_MAJOR_VERSION_ARB = 0x2091;
public const int GLX_CONTEXT_MINOR_VERSION_ARB = 0x2092;
public const int GLX_CONTEXT_FLAGS_ARB = 0x2094;
public const int GLX_CONTEXT_CORE_PROFILE_BIT_ARB = 0x00000001;
public const int GLX_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB = 0x00000002;
public const int GLX_CONTEXT_PROFILE_MASK_ARB = 0x9126;
}
}

41
src/Avalonia.X11/Glx/GlxContext.cs

@ -0,0 +1,41 @@
using System;
using System.Reactive.Disposables;
using System.Threading;
using Avalonia.OpenGL;
namespace Avalonia.X11.Glx
{
class GlxContext : IGlContext
{
public IntPtr Handle { get; }
public GlxInterface Glx { get; }
private readonly X11Info _x11;
private readonly IntPtr _defaultXid;
private readonly object _lock = new object();
public GlxContext(GlxInterface glx, IntPtr handle, GlxDisplay display, X11Info x11, IntPtr defaultXid)
{
Handle = handle;
Glx = glx;
_x11 = x11;
_defaultXid = defaultXid;
Display = display;
}
public GlxDisplay Display { get; }
IGlDisplay IGlContext.Display => Display;
public IDisposable Lock()
{
Monitor.Enter(_lock);
return Disposable.Create(() => Monitor.Exit(_lock));
}
public void MakeCurrent() => MakeCurrent(_defaultXid);
public void MakeCurrent(IntPtr xid)
{
if (!Glx.MakeContextCurrent(_x11.Display, xid, xid, Handle))
throw new OpenGlException("glXMakeContextCurrent failed ");
}
}
}

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

@ -0,0 +1,136 @@
using System;
using System.Linq;
using Avalonia.OpenGL;
using static Avalonia.X11.Glx.GlxConsts;
namespace Avalonia.X11.Glx
{
unsafe class GlxDisplay : IGlDisplay
{
private readonly X11Info _x11;
private readonly IntPtr _fbconfig;
private readonly XVisualInfo* _visual;
public GlDisplayType Type => GlDisplayType.OpenGL2;
public GlInterface GlInterface { get; }
public XVisualInfo* VisualInfo => _visual;
public int SampleCount { get; }
public int StencilSize { get; }
public GlxContext ImmediateContext { get; }
public GlxContext DeferredContext { get; }
public GlxInterface Glx { get; } = new GlxInterface();
public GlxDisplay(X11Info x11)
{
_x11 = x11;
var baseAttribs = new[]
{
GLX_X_RENDERABLE, 1,
GLX_RENDER_TYPE, GLX_RGBA_BIT,
GLX_DRAWABLE_TYPE, GLX_WINDOW_BIT | GLX_PBUFFER_BIT,
GLX_DOUBLEBUFFER, 1,
GLX_RED_SIZE, 8,
GLX_GREEN_SIZE, 8,
GLX_BLUE_SIZE, 8,
GLX_ALPHA_SIZE, 8,
GLX_DEPTH_SIZE, 1,
GLX_STENCIL_SIZE, 8,
};
foreach (var attribs in new[]
{
//baseAttribs.Concat(multiattribs),
baseAttribs,
})
{
var ptr = Glx.ChooseFBConfig(_x11.Display, x11.DefaultScreen,
attribs, out var count);
for (var c = 0 ; c < count; c++)
{
var visual = Glx.GetVisualFromFBConfig(_x11.Display, ptr[c]);
// We prefer 32 bit visuals
if (_fbconfig == IntPtr.Zero || visual->depth == 32)
{
_fbconfig = ptr[c];
_visual = visual;
if(visual->depth == 32)
break;
}
}
if (_fbconfig != IntPtr.Zero)
break;
}
if (_fbconfig == IntPtr.Zero)
throw new OpenGlException("Unable to choose FBConfig");
if (_visual == null)
throw new OpenGlException("Unable to get visual info from FBConfig");
if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_SAMPLES, out var samples) == 0)
SampleCount = samples;
if (Glx.GetFBConfigAttrib(_x11.Display, _fbconfig, GLX_STENCIL_SIZE, out var stencil) == 0)
StencilSize = stencil;
var pbuffers = Enumerable.Range(0, 2).Select(_ => Glx.CreatePbuffer(_x11.Display, _fbconfig, new[]
{
GLX_PBUFFER_WIDTH, 1, GLX_PBUFFER_HEIGHT, 1, 0
})).ToList();
XLib.XFlush(_x11.Display);
ImmediateContext = CreateContext(pbuffers[0],null);
DeferredContext = CreateContext(pbuffers[1], ImmediateContext);
ImmediateContext.MakeCurrent();
var err = Glx.GetError();
GlInterface = new GlInterface(GlxInterface.GlxGetProcAddress);
if (GlInterface.Version == null)
throw new OpenGlException("GL version string is null, aborting");
}
public void ClearContext() => Glx.MakeContextCurrent(_x11.Display,
IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
public GlxContext CreateContext(IGlContext share) => CreateContext(IntPtr.Zero, share);
public GlxContext CreateContext(IntPtr defaultXid, IGlContext share)
{
var sharelist = ((GlxContext)share)?.Handle ?? IntPtr.Zero;
IntPtr handle = default;
foreach (var ver in new[]
{
new Version(4, 0), new Version(3, 2),
new Version(3, 0), new Version(2, 0)
})
{
var attrs = new[]
{
GLX_CONTEXT_MAJOR_VERSION_ARB, ver.Major,
GLX_CONTEXT_MINOR_VERSION_ARB, ver.Minor,
GLX_CONTEXT_PROFILE_MASK_ARB, GLX_CONTEXT_CORE_PROFILE_BIT_ARB,
0
};
try
{
handle = Glx.CreateContextAttribsARB(_x11.Display, _fbconfig, sharelist, true, attrs);
if (handle != IntPtr.Zero)
break;
}
catch
{
break;
}
}
if (handle == IntPtr.Zero)
throw new OpenGlException("Unable to create direct GLX context");
return new GlxContext(new GlxInterface(), handle, this, _x11, defaultXid);
}
public void SwapBuffers(IntPtr xid) => Glx.SwapBuffers(_x11.Display, xid);
}
}

86
src/Avalonia.X11/Glx/GlxGlPlatformSurface.cs

@ -0,0 +1,86 @@
using System;
using Avalonia.OpenGL;
namespace Avalonia.X11.Glx
{
class GlxGlPlatformSurface: IGlPlatformSurface
{
private readonly GlxDisplay _display;
private readonly GlxContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
public GlxGlPlatformSurface(GlxDisplay display, GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
{
_display = display;
_context = context;
_info = info;
}
public IGlPlatformSurfaceRenderTarget CreateGlRenderTarget()
{
return new RenderTarget(_context, _info);
}
class RenderTarget : IGlPlatformSurfaceRenderTarget
{
private readonly GlxContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
public RenderTarget(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info)
{
_context = context;
_info = info;
}
public void Dispose()
{
// No-op
}
public IGlPlatformSurfaceRenderingSession BeginDraw()
{
var l = _context.Lock();
try
{
_context.MakeCurrent(_info.Handle);
return new Session(_context, _info, l);
}
catch
{
l.Dispose();
throw;
}
}
class Session : IGlPlatformSurfaceRenderingSession
{
private readonly GlxContext _context;
private readonly EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo _info;
private IDisposable _lock;
public Session(GlxContext context, EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo info,
IDisposable @lock)
{
_context = context;
_info = info;
_lock = @lock;
}
public void Dispose()
{
_context.Display.GlInterface.Flush();
_context.Glx.WaitGL();
_context.Display.SwapBuffers(_info.Handle);
_context.Glx.WaitX();
_context.Display.ClearContext();
_lock.Dispose();
}
public IGlDisplay Display => _context.Display;
public PixelSize Size => _info.Size;
public double Scaling => _info.Scaling;
}
}
}
}

44
src/Avalonia.X11/Glx/GlxPlatformFeature.cs

@ -0,0 +1,44 @@
using System;
using Avalonia.Logging;
using Avalonia.OpenGL;
namespace Avalonia.X11.Glx
{
class GlxGlPlatformFeature : IWindowingPlatformGlFeature
{
public GlxDisplay Display { get; private set; }
public IGlContext ImmediateContext { get; private set; }
public GlxContext DeferredContext { get; private set; }
public static bool TryInitialize(X11Info x11)
{
var feature = TryCreate(x11);
if (feature != null)
{
AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>().ToConstant(feature);
return true;
}
return false;
}
public static GlxGlPlatformFeature TryCreate(X11Info x11)
{
try
{
var disp = new GlxDisplay(x11);
return new GlxGlPlatformFeature
{
Display = disp,
ImmediateContext = disp.ImmediateContext,
DeferredContext = disp.DeferredContext
};
}
catch(Exception e)
{
Logger.Error("OpenGL", null, "Unable to initialize GLX-based rendering: {0}", e);
return null;
}
}
}
}

263
src/Avalonia.X11/NativeDialogs/Gtk.cs

@ -0,0 +1,263 @@
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Platform.Interop;
// ReSharper disable IdentifierTypo
namespace Avalonia.X11.NativeDialogs
{
static unsafe class Glib
{
private const string GlibName = "libglib-2.0.so.0";
private const string GObjectName = "libgobject-2.0.so.0";
[DllImport(GlibName)]
public static extern void g_slist_free(GSList* data);
[DllImport(GObjectName)]
private static extern void g_object_ref(IntPtr instance);
[DllImport(GObjectName)]
private static extern ulong g_signal_connect_object(IntPtr instance, Utf8Buffer signal,
IntPtr handler, IntPtr userData, int flags);
[DllImport(GObjectName)]
private static extern void g_object_unref(IntPtr instance);
[DllImport(GObjectName)]
private static extern ulong g_signal_handler_disconnect(IntPtr instance, ulong connectionId);
private delegate bool timeout_callback(IntPtr data);
[DllImport(GlibName)]
private static extern ulong g_timeout_add_full(int prio, uint interval, timeout_callback callback, IntPtr data,
IntPtr destroy);
class ConnectedSignal : IDisposable
{
private readonly IntPtr _instance;
private GCHandle _handle;
private readonly ulong _id;
public ConnectedSignal(IntPtr instance, GCHandle handle, ulong id)
{
_instance = instance;
g_object_ref(instance);
_handle = handle;
_id = id;
}
public void Dispose()
{
if (_handle.IsAllocated)
{
g_signal_handler_disconnect(_instance, _id);
g_object_unref(_instance);
_handle.Free();
}
}
}
public static IDisposable ConnectSignal<T>(IntPtr obj, string name, T handler)
{
var handle = GCHandle.Alloc(handler);
var ptr = Marshal.GetFunctionPointerForDelegate((Delegate)(object)handler);
using (var utf = new Utf8Buffer(name))
{
var id = g_signal_connect_object(obj, utf, ptr, IntPtr.Zero, 0);
if (id == 0)
throw new ArgumentException("Unable to connect to signal " + name);
return new ConnectedSignal(obj, handle, id);
}
}
static bool TimeoutHandler(IntPtr data)
{
var handle = GCHandle.FromIntPtr(data);
var cb = (Func<bool>)handle.Target;
if (!cb())
{
handle.Free();
return false;
}
return true;
}
private static readonly timeout_callback s_pinnedHandler;
static Glib()
{
s_pinnedHandler = TimeoutHandler;
}
static void AddTimeout(int priority, uint interval, Func<bool> callback)
{
var handle = GCHandle.Alloc(callback);
g_timeout_add_full(priority, interval, s_pinnedHandler, GCHandle.ToIntPtr(handle), IntPtr.Zero);
}
public static Task<T> RunOnGlibThread<T>(Func<T> action)
{
var tcs = new TaskCompletionSource<T>();
AddTimeout(0, 0, () =>
{
try
{
tcs.SetResult(action());
}
catch (Exception e)
{
tcs.TrySetException(e);
}
return false;
});
return tcs.Task;
}
}
[StructLayout(LayoutKind.Sequential)]
unsafe struct GSList
{
public readonly IntPtr Data;
public readonly GSList* Next;
}
enum GtkFileChooserAction
{
Open,
Save,
SelectFolder,
}
// ReSharper disable UnusedMember.Global
enum GtkResponseType
{
Help = -11,
Apply = -10,
No = -9,
Yes = -8,
Close = -7,
Cancel = -6,
Ok = -5,
DeleteEvent = -4,
Accept = -3,
Reject = -2,
None = -1,
}
// ReSharper restore UnusedMember.Global
static unsafe class Gtk
{
private static IntPtr s_display;
private const string GdkName = "libgdk-3.so.0";
private const string GtkName = "libgtk-3.so.0";
[DllImport(GtkName)]
static extern void gtk_main_iteration();
[DllImport(GtkName)]
public static extern void gtk_window_set_modal(IntPtr window, bool modal);
[DllImport(GtkName)]
public static extern void gtk_window_present(IntPtr gtkWindow);
public delegate bool signal_generic(IntPtr gtkWidget, IntPtr userData);
public delegate bool signal_dialog_response(IntPtr gtkWidget, GtkResponseType response, IntPtr userData);
[DllImport(GtkName)]
public static extern IntPtr gtk_file_chooser_dialog_new(Utf8Buffer title, IntPtr parent,
GtkFileChooserAction action, IntPtr ignore);
[DllImport(GtkName)]
public static extern void gtk_file_chooser_set_select_multiple(IntPtr chooser, bool allow);
[DllImport(GtkName)]
public static extern void
gtk_dialog_add_button(IntPtr raw, Utf8Buffer button_text, GtkResponseType response_id);
[DllImport(GtkName)]
public static extern GSList* gtk_file_chooser_get_filenames(IntPtr chooser);
[DllImport(GtkName)]
public static extern void gtk_file_chooser_set_filename(IntPtr chooser, Utf8Buffer file);
[DllImport(GtkName)]
public static extern void gtk_widget_realize(IntPtr gtkWidget);
[DllImport(GtkName)]
public static extern IntPtr gtk_widget_get_window(IntPtr gtkWidget);
[DllImport(GtkName)]
public static extern void gtk_widget_hide(IntPtr gtkWidget);
[DllImport(GtkName)]
static extern bool gtk_init_check(int argc, IntPtr argv);
[DllImport(GdkName)]
static extern IntPtr gdk_x11_window_foreign_new_for_display(IntPtr display, IntPtr xid);
[DllImport(GdkName)]
static extern IntPtr gdk_set_allowed_backends(Utf8Buffer backends);
[DllImport(GdkName)]
static extern IntPtr gdk_display_get_default();
[DllImport(GtkName)]
static extern IntPtr gtk_application_new(Utf8Buffer appId, int flags);
[DllImport(GdkName)]
public static extern void gdk_window_set_transient_for(IntPtr window, IntPtr parent);
public static IntPtr GetForeignWindow(IntPtr xid) => gdk_x11_window_foreign_new_for_display(s_display, xid);
public static Task<bool> StartGtk()
{
var tcs = new TaskCompletionSource<bool>();
new Thread(() =>
{
try
{
using (var backends = new Utf8Buffer("x11"))
gdk_set_allowed_backends(backends);
}
catch
{
//Ignore
}
Environment.SetEnvironmentVariable("WAYLAND_DISPLAY",
"/proc/fake-display-to-prevent-wayland-initialization-by-gtk3");
if (!gtk_init_check(0, IntPtr.Zero))
{
tcs.SetResult(false);
return;
}
IntPtr app;
using (var utf = new Utf8Buffer($"avalonia.app.a{Guid.NewGuid():N}"))
app = gtk_application_new(utf, 0);
if (app == IntPtr.Zero)
{
tcs.SetResult(false);
return;
}
s_display = gdk_display_get_default();
tcs.SetResult(true);
while (true)
gtk_main_iteration();
}) {Name = "GTK3THREAD", IsBackground = true}.Start();
return tcs.Task;
}
}
}

122
src/Avalonia.X11/NativeDialogs/GtkNativeFileDialogs.cs

@ -0,0 +1,122 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
using Avalonia.Platform.Interop;
using static Avalonia.X11.NativeDialogs.Glib;
using static Avalonia.X11.NativeDialogs.Gtk;
// ReSharper disable AccessToModifiedClosure
namespace Avalonia.X11.NativeDialogs
{
class GtkSystemDialog : ISystemDialogImpl
{
private Task<bool> _initialized;
private unsafe Task<string[]> ShowDialog(string title, IWindowImpl parent, GtkFileChooserAction action,
bool multiSelect, string initialFileName)
{
IntPtr dlg;
using (var name = new Utf8Buffer(title))
dlg = gtk_file_chooser_dialog_new(name, IntPtr.Zero, action, IntPtr.Zero);
UpdateParent(dlg, parent);
if (multiSelect)
gtk_file_chooser_set_select_multiple(dlg, true);
gtk_window_set_modal(dlg, true);
var tcs = new TaskCompletionSource<string[]>();
List<IDisposable> disposables = null;
void Dispose()
{
// ReSharper disable once PossibleNullReferenceException
foreach (var d in disposables) d.Dispose();
disposables.Clear();
}
disposables = new List<IDisposable>
{
ConnectSignal<signal_generic>(dlg, "close", delegate
{
tcs.TrySetResult(null);
Dispose();
return false;
}),
ConnectSignal<signal_dialog_response>(dlg, "response", (_, resp, __) =>
{
string[] result = null;
if (resp == GtkResponseType.Accept)
{
var resultList = new List<string>();
var gs = gtk_file_chooser_get_filenames(dlg);
var cgs = gs;
while (cgs != null)
{
if (cgs->Data != IntPtr.Zero)
resultList.Add(Utf8Buffer.StringFromPtr(cgs->Data));
cgs = cgs->Next;
}
g_slist_free(gs);
result = resultList.ToArray();
}
gtk_widget_hide(dlg);
Dispose();
tcs.TrySetResult(result);
return false;
})
};
using (var open = new Utf8Buffer("Open"))
gtk_dialog_add_button(dlg, open, GtkResponseType.Accept);
using (var open = new Utf8Buffer("Cancel"))
gtk_dialog_add_button(dlg, open, GtkResponseType.Cancel);
if (initialFileName != null)
using (var fn = new Utf8Buffer(initialFileName))
gtk_file_chooser_set_filename(dlg, fn);
gtk_window_present(dlg);
return tcs.Task;
}
public async Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
{
await EnsureInitialized();
return await await RunOnGlibThread(
() => ShowDialog(dialog.Title, parent,
dialog is OpenFileDialog ? GtkFileChooserAction.Open : GtkFileChooserAction.Save,
(dialog as OpenFileDialog)?.AllowMultiple ?? false,
Path.Combine(string.IsNullOrEmpty(dialog.InitialDirectory) ? "" : dialog.InitialDirectory,
string.IsNullOrEmpty(dialog.InitialFileName) ? "" : dialog.InitialFileName)));
}
public async Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
{
await EnsureInitialized();
return await await RunOnGlibThread(async () =>
{
var res = await ShowDialog(dialog.Title, parent,
GtkFileChooserAction.SelectFolder, false, dialog.InitialDirectory);
return res?.FirstOrDefault();
});
}
async Task EnsureInitialized()
{
if (_initialized == null) _initialized = StartGtk();
if (!(await _initialized))
throw new Exception("Unable to initialize GTK on separate thread");
}
void UpdateParent(IntPtr chooser, IWindowImpl parentWindow)
{
var xid = parentWindow.Handle.Handle;
gtk_widget_realize(chooser);
var window = gtk_widget_get_window(chooser);
var parent = GetForeignWindow(xid);
if (window != IntPtr.Zero && parent != IntPtr.Zero)
gdk_window_set_transient_for(window, parent);
}
}
}

6
src/Avalonia.X11/X11FramebufferSurface.cs

@ -8,12 +8,14 @@ namespace Avalonia.X11
{
private readonly IntPtr _display;
private readonly IntPtr _xid;
private readonly int _depth;
private readonly Func<double> _scaling;
public X11FramebufferSurface(IntPtr display, IntPtr xid, Func<double> scaling)
public X11FramebufferSurface(IntPtr display, IntPtr xid, int depth, Func<double> scaling)
{
_display = display;
_xid = xid;
_depth = depth;
_scaling = scaling;
}
@ -23,7 +25,7 @@ namespace Avalonia.X11
XGetGeometry(_display, _xid, out var root, out var x, out var y, out var width, out var height,
out var bw, out var d);
XUnlockDisplay(_display);
return new X11Framebuffer(_display, _xid, 24,width, height, _scaling());
return new X11Framebuffer(_display, _xid, _depth, width, height, _scaling());
}
}
}

8
src/Avalonia.X11/X11Info.cs

@ -7,7 +7,7 @@ using static Avalonia.X11.XLib;
// ReSharper disable UnusedAutoPropertyAccessor.Local
namespace Avalonia.X11
{
class X11Info
unsafe class X11Info
{
public IntPtr Display { get; }
public IntPtr DeferredDisplay { get; }
@ -31,6 +31,7 @@ namespace Avalonia.X11
public Version XInputVersion { get; }
public IntPtr LastActivityTimestamp { get; set; }
public XVisualInfo? TransparentVisualInfo { get; set; }
public unsafe X11Info(IntPtr display, IntPtr deferredDisplay)
{
@ -45,7 +46,10 @@ namespace Avalonia.X11
//TODO: Open an actual XIM once we get support for preedit in our textbox
XSetLocaleModifiers("@im=none");
Xim = XOpenIM(display, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
XMatchVisualInfo(Display, DefaultScreen, 32, 4, out var visual);
if (visual.depth == 32)
TransparentVisualInfo = visual;
try
{
if (XRRQueryExtension(display, out int randrEventBase, out var randrErrorBase) != 0)

28
src/Avalonia.X11/X11Platform.cs

@ -2,13 +2,14 @@
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Gtk3;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.X11;
using Avalonia.X11.Glx;
using Avalonia.X11.NativeDialogs;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
{
@ -23,7 +24,7 @@ namespace Avalonia.X11
public X11Info Info { get; private set; }
public IX11Screens X11Screens { get; private set; }
public IScreenImpl Screens { get; private set; }
public void Initialize()
public void Initialize(X11PlatformOptions options)
{
XInitThreads();
Display = XOpenDisplay(IntPtr.Zero);
@ -44,7 +45,7 @@ namespace Avalonia.X11
.Bind<IClipboard>().ToConstant(new X11Clipboard(this))
.Bind<IPlatformSettings>().ToConstant(new PlatformSettingsStub())
.Bind<IPlatformIconLoader>().ToConstant(new X11IconLoader(Info))
.Bind<ISystemDialogImpl>().ToConstant(new Gtk3ForeignX11SystemDialog());
.Bind<ISystemDialogImpl>().ToConstant(new GtkSystemDialog());
X11Screens = Avalonia.X11.X11Screens.Init(this);
Screens = new X11Screens(X11Screens);
@ -54,8 +55,14 @@ namespace Avalonia.X11
if (xi2.Init(this))
XI2 = xi2;
}
EglGlPlatformFeature.TryInitialize();
if (options.UseGpu)
{
if (options.UseEGL)
EglGlPlatformFeature.TryInitialize();
else
GlxGlPlatformFeature.TryInitialize(Info);
}
}
public IntPtr DeferredDisplay { get; set; }
@ -79,15 +86,22 @@ namespace Avalonia.X11
namespace Avalonia
{
public class X11PlatformOptions
{
public bool UseEGL { get; set; }
public bool UseGpu { get; set; } = true;
}
public static class AvaloniaX11PlatformExtensions
{
public static T UseX11<T>(this T builder) where T : AppBuilderBase<T>, new()
public static T UseX11<T>(this T builder, X11PlatformOptions options = null) where T : AppBuilderBase<T>, new()
{
builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize());
builder.UseWindowingSubsystem(() => new AvaloniaX11Platform().Initialize(options ?? new X11PlatformOptions()));
return builder;
}
public static void InitializeX11Platform() => new AvaloniaX11Platform().Initialize();
public static void InitializeX11Platform(X11PlatformOptions options = null) =>
new AvaloniaX11Platform().Initialize(options ?? new X11PlatformOptions());
}
}

42
src/Avalonia.X11/X11PlatformThreading.cs

@ -164,39 +164,33 @@ namespace Avalonia.X11
void HandleX11(CancellationToken cancellationToken)
{
while (true)
while (XPending(_display) != 0)
{
var pending = XPending(_display);
if (pending == 0)
break;
while (pending > 0)
if (cancellationToken.IsCancellationRequested)
return;
XNextEvent(_display, out var xev);
if (xev.type == XEventName.GenericEvent)
XGetEventData(_display, &xev.GenericEventCookie);
try
{
if (cancellationToken.IsCancellationRequested)
return;
XNextEvent(_display, out var xev);
if (xev.type == XEventName.GenericEvent)
XGetEventData(_display, &xev.GenericEventCookie);
pending--;
try
{
if (xev.type == XEventName.GenericEvent)
if (_platform.XI2 != null && _platform.Info.XInputOpcode ==
xev.GenericEventCookie.extension)
{
if (_platform.XI2 != null && _platform.Info.XInputOpcode ==
xev.GenericEventCookie.extension)
{
_platform.XI2.OnEvent((XIEvent*)xev.GenericEventCookie.data);
}
_platform.XI2.OnEvent((XIEvent*)xev.GenericEventCookie.data);
}
else if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler))
handler(xev);
}
finally
{
if (xev.type == XEventName.GenericEvent && xev.GenericEventCookie.data != null)
XFreeEventData(_display, &xev.GenericEventCookie);
}
else if (_eventHandlers.TryGetValue(xev.AnyEvent.window, out var handler))
handler(xev);
}
finally
{
if (xev.type == XEventName.GenericEvent && xev.GenericEventCookie.data != null)
XFreeEventData(_display, &xev.GenericEventCookie);
}
}
Dispatcher.UIThread.RunJobs();
}

19
src/Avalonia.X11/X11Screens.cs

@ -20,7 +20,7 @@ namespace Avalonia.X11
static unsafe X11Screen[] UpdateWorkArea(X11Info info, X11Screen[] screens)
{
var rect = default(Rect);
var rect = default(PixelRect);
foreach (var s in screens)
{
rect = rect.Union(s.Bounds);
@ -46,7 +46,7 @@ namespace Avalonia.X11
return screens;
var pwa = (IntPtr*)prop;
var wa = new Rect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32());
var wa = new PixelRect(pwa[0].ToInt32(), pwa[1].ToInt32(), pwa[2].ToInt32(), pwa[3].ToInt32());
foreach (var s in screens)
@ -105,7 +105,7 @@ namespace Avalonia.X11
density *= _settings.GlobalScaleFactor;
var bounds = new Rect(mon.X, mon.Y, mon.Width, mon.Height);
var bounds = new PixelRect(mon.X, mon.Y, mon.Width, mon.Height);
screens[c] = new X11Screen(bounds,
mon.Primary != 0,
name,
@ -130,12 +130,12 @@ namespace Avalonia.X11
Screens = UpdateWorkArea(info,
new[]
{
new X11Screen(new Rect(0, 0, geo.width, geo.height), true, "Default", null,
new X11Screen(new PixelRect(0, 0, geo.width, geo.height), true, "Default", null,
settings.GlobalScaleFactor)
});
}
Screens = new[] {new X11Screen(new Rect(0, 0, 1920, 1280), true, "Default", null, settings.GlobalScaleFactor)};
Screens = new[] {new X11Screen(new PixelRect(0, 0, 1920, 1280), true, "Default", null, settings.GlobalScaleFactor)};
}
public X11Screen[] Screens { get; }
@ -218,14 +218,15 @@ namespace Avalonia.X11
class X11Screen
{
private const int FullHDWidth = 1920;
public bool Primary { get; }
public string Name { get; set; }
public Rect Bounds { get; set; }
public PixelRect Bounds { get; set; }
public Size? PhysicalSize { get; set; }
public double PixelDensity { get; set; }
public Rect WorkingArea { get; set; }
public PixelRect WorkingArea { get; set; }
public X11Screen(Rect bounds, bool primary,
public X11Screen(PixelRect bounds, bool primary,
string name, Size? physicalSize, double? pixelDensity)
{
Primary = primary;
@ -247,6 +248,6 @@ namespace Avalonia.X11
}
public static double GuessPixelDensity(double pixelWidth, double mmWidth)
=> Math.Max(1, Math.Round(pixelWidth / mmWidth * 25.4 / 96));
=> pixelWidth <= FullHDWidth ? 1 : Math.Max(1, Math.Round(pixelWidth / mmWidth * 25.4 / 96));
}
}

102
src/Avalonia.X11/X11Window.cs

@ -12,6 +12,7 @@ using Avalonia.OpenGL;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.X11.Glx;
using static Avalonia.X11.XLib;
// ReSharper disable IdentifierTypo
// ReSharper disable StringLiteralTypo
@ -24,12 +25,12 @@ namespace Avalonia.X11
private readonly X11Info _x11;
private bool _invalidated;
private XConfigureEvent? _configure;
private Point? _configurePoint;
private PixelPoint? _configurePoint;
private bool _triggeredExpose;
private IInputRoot _inputRoot;
private readonly IMouseDevice _mouse;
private readonly IKeyboardDevice _keyboard;
private Point? _position;
private PixelPoint? _position;
private PixelSize _realSize;
private IntPtr _handle;
private IntPtr _xic;
@ -45,7 +46,7 @@ namespace Avalonia.X11
}
private readonly Queue<InputEventContainer> _inputQueue = new Queue<InputEventContainer>();
private InputEventContainer _lastEvent;
private bool _useRenderWindow = false;
public X11Window(AvaloniaX11Platform platform, bool popup)
{
_platform = platform;
@ -53,9 +54,8 @@ namespace Avalonia.X11
_x11 = platform.Info;
_mouse = platform.MouseDevice;
_keyboard = platform.KeyboardDevice;
_xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
XNames.XNClientWindow, _handle, IntPtr.Zero);
var glfeature = AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
XSetWindowAttributes attr = new XSetWindowAttributes();
var valueMask = default(SetWindowValuemask);
@ -71,16 +71,44 @@ namespace Avalonia.X11
attr.override_redirect = true;
valueMask |= SetWindowValuemask.OverrideRedirect;
}
XVisualInfo? visualInfo = null;
// OpenGL seems to be do weird things to it's current window which breaks resize sometimes
_useRenderWindow = glfeature != null;
var glx = glfeature as GlxGlPlatformFeature;
if (glx != null)
visualInfo = *glx.Display.VisualInfo;
else if (glfeature == null)
visualInfo = _x11.TransparentVisualInfo;
var egl = glfeature as EglGlPlatformFeature;
var visual = IntPtr.Zero;
var depth = 24;
if (visualInfo != null)
{
visual = visualInfo.Value.visual;
depth = (int)visualInfo.Value.depth;
attr.colormap = XCreateColormap(_x11.Display, _x11.RootWindow, visualInfo.Value.visual, 0);
valueMask |= SetWindowValuemask.ColorMap;
}
_handle = XCreateWindow(_x11.Display, _x11.RootWindow, 10, 10, 300, 200, 0,
24,
(int)CreateWindowArgs.InputOutput, IntPtr.Zero,
depth,
(int)CreateWindowArgs.InputOutput,
visual,
new UIntPtr((uint)valueMask), ref attr);
_renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, 300, 200, 0, 24,
(int)CreateWindowArgs.InputOutput,
IntPtr.Zero,
new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity |
SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr);
if (_useRenderWindow)
_renderHandle = XCreateWindow(_x11.Display, _handle, 0, 0, 300, 200, 0, depth,
(int)CreateWindowArgs.InputOutput,
visual,
new UIntPtr((uint)(SetWindowValuemask.BorderPixel | SetWindowValuemask.BitGravity |
SetWindowValuemask.WinGravity | SetWindowValuemask.BackingStore)), ref attr);
else
_renderHandle = _handle;
Handle = new PlatformHandle(_handle, "XID");
_realSize = new PixelSize(300, 200);
@ -99,18 +127,26 @@ namespace Avalonia.X11
XSetWMProtocols(_x11.Display, _handle, protocols, protocols.Length);
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM,
32, PropertyMode.Replace, new[] {_x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL}, 1);
var feature = (EglGlPlatformFeature)AvaloniaLocator.Current.GetService<IWindowingPlatformGlFeature>();
var surfaces = new List<object>
{
new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle, () => Scaling)
new X11FramebufferSurface(_x11.DeferredDisplay, _renderHandle,
depth, () => Scaling)
};
if (feature != null)
if (egl != null)
surfaces.Insert(0,
new EglGlPlatformSurface((EglDisplay)feature.Display, feature.DeferredContext,
new EglGlPlatformSurface((EglDisplay)egl.Display, egl.DeferredContext,
new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle)));
if (glx != null)
surfaces.Insert(0, new GlxGlPlatformSurface(glx.Display, glx.DeferredContext,
new SurfaceInfo(this, _x11.Display, _handle, _renderHandle)));
Surfaces = surfaces.ToArray();
UpdateMotifHints();
_xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
XNames.XNClientWindow, _handle, IntPtr.Zero);
XFlush(_x11.Display);
}
@ -136,8 +172,6 @@ namespace Avalonia.X11
XLockDisplay(_display);
XGetGeometry(_display, _parent, out var geo);
XResizeWindow(_display, Handle, geo.width, geo.height);
XFlush(_display);
XSync(_display, true);
XUnlockDisplay(_display);
return new PixelSize(geo.width, geo.height);
}
@ -235,7 +269,7 @@ namespace Avalonia.X11
public Func<bool> Closing { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public Action Closed { get; set; }
public Action<Point> PositionChanged { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public IRenderer CreateRenderer(IRenderRoot root) =>
new DeferredRenderer(root, AvaloniaLocator.Current.GetService<IRenderLoop>());
@ -252,7 +286,8 @@ namespace Avalonia.X11
if (ev.type == XEventName.MapNotify)
{
_mapped = true;
XMapWindow(_x11.Display, _renderHandle);
if (_useRenderWindow)
XMapWindow(_x11.Display, _renderHandle);
}
else if (ev.type == XEventName.UnmapNotify)
_mapped = false;
@ -321,13 +356,13 @@ namespace Avalonia.X11
var needEnqueue = (_configure == null);
_configure = ev.ConfigureEvent;
if (ev.ConfigureEvent.override_redirect || ev.ConfigureEvent.send_event)
_configurePoint = new Point(ev.ConfigureEvent.x, ev.ConfigureEvent.y);
_configurePoint = new PixelPoint(ev.ConfigureEvent.x, ev.ConfigureEvent.y);
else
{
XTranslateCoordinates(_x11.Display, _handle, _x11.RootWindow,
0, 0,
out var tx, out var ty, out _);
_configurePoint = new Point(tx, ty);
_configurePoint = new PixelPoint(tx, ty);
}
if (needEnqueue)
Dispatcher.UIThread.Post(() =>
@ -356,7 +391,9 @@ namespace Avalonia.X11
Dispatcher.UIThread.RunJobs(DispatcherPriority.Layout);
}, DispatcherPriority.Layout);
XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width, ev.ConfigureEvent.height);
if (_useRenderWindow)
XConfigureResizeWindow(_x11.Display, _renderHandle, ev.ConfigureEvent.width,
ev.ConfigureEvent.height);
}
else if (ev.type == XEventName.DestroyNotify && ev.AnyEvent.window == _handle)
{
@ -619,7 +656,7 @@ namespace Avalonia.X11
Closed?.Invoke();
}
if (_renderHandle != IntPtr.Zero)
if (_useRenderWindow && _renderHandle != IntPtr.Zero)
{
XDestroyWindow(_x11.Display, _renderHandle);
_renderHandle = IntPtr.Zero;
@ -660,9 +697,11 @@ namespace Avalonia.X11
public void Hide() => XUnmapWindow(_x11.Display, _handle);
public Point PointToClient(Point point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling);
public Point PointToClient(PixelPoint point) => new Point((point.X - Position.X) / Scaling, (point.Y - Position.Y) / Scaling);
public Point PointToScreen(Point point) => new Point(point.X * Scaling + Position.X, point.Y * Scaling + Position.Y);
public PixelPoint PointToScreen(Point point) => new PixelPoint(
(int)(point.X * Scaling + Position.X),
(int)(point.Y * Scaling + Position.Y));
public void SetSystemDecorations(bool enabled)
{
@ -685,7 +724,8 @@ namespace Avalonia.X11
var pixelSize = ToPixelSize(clientSize);
UpdateSizeHints(pixelSize);
XConfigureResizeWindow(_x11.Display, _handle, pixelSize);
XConfigureResizeWindow(_x11.Display, _renderHandle, pixelSize);
if (_useRenderWindow)
XConfigureResizeWindow(_x11.Display, _renderHandle, pixelSize);
XFlush(_x11.Display);
if (force || (_popup && needImmediatePopupResize))
@ -716,7 +756,7 @@ namespace Avalonia.X11
public IPlatformHandle Handle { get; }
public Point Position
public PixelPoint Position
{
get => _position ?? default;
set
@ -752,7 +792,7 @@ namespace Avalonia.X11
public IScreenImpl Screen => _platform.Screens;
public Size MaxClientSize => _platform.X11Screens.Screens.Select(s => s.Bounds.Size / s.PixelDensity)
public Size MaxClientSize => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity))
.OrderByDescending(x => x.Width + x.Height).FirstOrDefault();

4
src/Gtk/Avalonia.Gtk3/GtkScreen.cs

@ -6,7 +6,7 @@ namespace Avalonia.Gtk3
{
private readonly int _screenId;
public GtkScreen(Rect bounds, Rect workingArea, bool primary, int screenId) : base(bounds, workingArea, primary)
public GtkScreen(PixelRect bounds, PixelRect workingArea, bool primary, int screenId) : base(bounds, workingArea, primary)
{
this._screenId = screenId;
}
@ -21,4 +21,4 @@ namespace Avalonia.Gtk3
return (obj is GtkScreen screen) ? this._screenId == screen._screenId : base.Equals(obj);
}
}
}
}

4
src/Gtk/Avalonia.Gtk3/ScreenImpl.cs

@ -27,8 +27,8 @@ namespace Avalonia.Gtk3
GdkRectangle workArea = new GdkRectangle(), geometry = new GdkRectangle();
Native.GdkScreenGetMonitorGeometry(screen, i, ref geometry);
Native.GdkScreenGetMonitorWorkarea(screen, i, ref workArea);
Rect workAreaRect = new Rect(workArea.X, workArea.Y, workArea.Width, workArea.Height);
Rect geometryRect = new Rect(geometry.X, geometry.Y, geometry.Width, geometry.Height);
PixelRect workAreaRect = new PixelRect(workArea.X, workArea.Y, workArea.Width, workArea.Height);
PixelRect geometryRect = new PixelRect(geometry.X, geometry.Y, geometry.Width, geometry.Height);
GtkScreen s = new GtkScreen(geometryRect, workAreaRect, i == primary, i);
screens[i] = s;
}

14
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -24,7 +24,7 @@ namespace Avalonia.Gtk3
private readonly EglGlPlatformSurface _egl;
protected readonly List<IDisposable> Disposables = new List<IDisposable>();
private Size _lastSize;
private Point _lastPosition;
private PixelPoint _lastPosition;
private double _lastScaling;
private uint _lastKbdEvent;
private uint _lastSmoothScrollEvent;
@ -383,7 +383,7 @@ namespace Avalonia.Gtk3
public Action<Rect> Paint { get; set; }
public Action<Size> Resized { get; set; }
public Action<double> ScalingChanged { get; set; } //TODO
public Action<Point> PositionChanged { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public void Activate() => Native.GtkWidgetActivate(GtkWidget);
@ -402,7 +402,7 @@ namespace Avalonia.Gtk3
Dispatcher.UIThread.Post(() => Input?.Invoke(args), DispatcherPriority.Input);
}
public Point PointToClient(Point point)
public Point PointToClient(PixelPoint point)
{
int x, y;
Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y);
@ -410,11 +410,11 @@ namespace Avalonia.Gtk3
return new Point(point.X - x, point.Y - y);
}
public Point PointToScreen(Point point)
public PixelPoint PointToScreen(Point point)
{
int x, y;
Native.GdkWindowGetOrigin(Native.GtkWidgetGetWindow(GtkWidget), out x, out y);
return new Point(point.X + x, point.Y + y);
return new PixelPoint((int)(point.X + x), (int)(point.Y + y));
}
public void SetCursor(IPlatformHandle cursor)
@ -490,13 +490,13 @@ namespace Avalonia.Gtk3
get;
} = new ScreenImpl();
public Point Position
public PixelPoint Position
{
get
{
int x, y;
Native.GtkWindowGetPosition(GtkWidget, out x, out y);
return new Point(x, y);
return new PixelPoint(x, y);
}
set { Native.GtkWindowMove(GtkWidget, (int)value.X, (int)value.Y); }
}

4
src/Linux/Avalonia.LinuxFramebuffer/FramebufferToplevelImpl.cs

@ -51,9 +51,9 @@ namespace Avalonia.LinuxFramebuffer
InputRoot = inputRoot;
}
public Point PointToClient(Point point) => point;
public Point PointToClient(PixelPoint p) => p.ToPoint(1);
public Point PointToScreen(Point point) => point;
public PixelPoint PointToScreen(Point p) => PixelPoint.FromPoint(p, 1);
public void SetCursor(IPlatformHandle cursor)
{

17
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@ -8,11 +8,14 @@ using Avalonia.Platform;
using Portable.Xaml;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Xml.Linq;
using System.Linq;
namespace Avalonia.Markup.Xaml
{
@ -240,15 +243,21 @@ namespace Avalonia.Markup.Xaml
{
using (var xamlInfoStream = assetLocator.Open(xamlInfoUri))
{
var xamlInfo = (AvaloniaResourceXamlInfo)s_xamlInfoSerializer.ReadObject(xamlInfoStream);
if (xamlInfo.ClassToResourcePathIndex.TryGetValue(typeName, out var rv) == true)
var assetDoc = XDocument.Load(xamlInfoStream);
XNamespace assetNs = assetDoc.Root.Attribute("xmlns").Value;
XNamespace arrayNs = "http://schemas.microsoft.com/2003/10/Serialization/Arrays";
Dictionary<string,string> xamlInfo =
assetDoc.Root.Element(assetNs + "ClassToResourcePathIndex").Elements(arrayNs + "KeyValueOfstringstring")
.ToDictionary(entry =>entry.Element(arrayNs + "Key").Value,
entry => entry.Element(arrayNs + "Value").Value);
if (xamlInfo.TryGetValue(typeName, out var rv))
{
yield return new Uri($"avares://{asm}{rv}");
yield break;
}
}
}
}
yield return new Uri("resm:" + typeName + ".xaml?assembly=" + asm);
yield return new Uri("resm:" + typeName + ".paml?assembly=" + asm);

35
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs

@ -77,40 +77,15 @@ namespace Avalonia.Markup.Xaml.PortableXaml
_delayedValuesHelper.ApplyAll();
}
protected internal override void OnAfterBeginInit(object value)
{
//not called for avalonia objects
//as it's called inly for
//Portable.Xaml.ComponentModel.ISupportInitialize
base.OnAfterBeginInit(value);
}
protected internal override void OnAfterEndInit(object value)
{
//not called for avalonia objects
//as it's called inly for
//Portable.Xaml.ComponentModel.ISupportInitialize
base.OnAfterEndInit(value);
}
protected internal override void OnAfterProperties(object value)
{
_delayedValuesHelper.EndInit(value);
base.OnAfterProperties(value);
//AfterEndInit is not called as it supports only
//Portable.Xaml.ComponentModel.ISupportInitialize
//and we have Avalonia.ISupportInitialize so we need some hacks
HandleEndEdit(value);
}
protected internal override void OnBeforeProperties(object value)
{
//OnAfterBeginInit is not called as it supports only
//Portable.Xaml.ComponentModel.ISupportInitialize
//and we have Avalonia.ISupportInitialize so we need some hacks
HandleBeginInit(value);
if (value != null)
_delayedValuesHelper.BeginInit(value);
@ -127,16 +102,6 @@ namespace Avalonia.Markup.Xaml.PortableXaml
return base.OnSetValue(target, member, value);
}
private void HandleBeginInit(object value)
{
(value as Avalonia.ISupportInitialize)?.BeginInit();
}
private void HandleEndEdit(object value)
{
(value as Avalonia.ISupportInitialize)?.EndInit();
}
public override void WriteStartMember(XamlMember property)
{
foreach(var d in DesignDirectives)

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save