Browse Source

Merge branch 'master' into fixes/tapped-gestures

pull/9360/head
Max Katz 4 years ago
committed by GitHub
parent
commit
58a4d06536
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 60
      Avalonia.Desktop.slnf
  2. 38
      Documentation/build.md
  3. 2
      readme.md
  4. 16
      src/Avalonia.Base/Controls/NameScopeEventArgs.cs
  5. 2
      src/Avalonia.Base/Input/DragEventArgs.cs
  6. 5
      src/Avalonia.Base/Input/GotFocusEventArgs.cs
  7. 5
      src/Avalonia.Base/Input/KeyEventArgs.cs
  8. 2
      src/Avalonia.Base/Input/PointerDeltaEventArgs.cs
  9. 12
      src/Avalonia.Base/Input/PointerEventArgs.cs
  10. 2
      src/Avalonia.Base/Input/PointerWheelEventArgs.cs
  11. 6
      src/Avalonia.Base/Input/ScrollGestureEventArgs.cs
  12. 2
      src/Avalonia.Base/Input/TappedEventArgs.cs
  13. 4
      src/Avalonia.Base/Input/TextInputEventArgs.cs
  14. 5
      src/Avalonia.Base/Input/VectorEventArgs.cs
  15. 2
      src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs
  16. 18
      src/Avalonia.Base/Media/Imaging/Bitmap.cs
  17. 14
      src/Avalonia.Base/Media/Imaging/IBitmap.cs
  18. 14
      src/Avalonia.Base/Platform/IBitmapImpl.cs
  19. 2
      src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs
  20. 55
      src/Avalonia.Controls/Documents/InlineCollection.cs
  21. 19
      src/Avalonia.Controls/ProgressBar.cs
  22. 19
      src/Avalonia.Controls/TextBlock.cs
  23. 4
      src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
  24. 4
      src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs
  25. 33
      src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs
  26. 4
      src/Skia/Avalonia.Skia/ImmutableBitmap.cs
  27. 8
      src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
  28. 8
      src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
  29. 6
      src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
  30. 2
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
  31. 2
      src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
  32. 2
      src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
  33. 20
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  34. 3
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
  35. 11
      src/Windows/Avalonia.Win32/WinRT/winrt.idl
  36. 84
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  37. 2
      src/Windows/Avalonia.Win32/WindowImpl.cs
  38. 22
      tests/Avalonia.Controls.UnitTests/TextBlockTests.cs

60
Avalonia.Desktop.slnf

@ -0,0 +1,60 @@
{
"solution": {
"path": "Avalonia.sln",
"projects": [
"packages\\Avalonia\\Avalonia.csproj",
"samples\\ControlCatalog.NetCore\\ControlCatalog.NetCore.csproj",
"samples\\ControlCatalog\\ControlCatalog.csproj",
"samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
"samples\\MiniMvvm\\MiniMvvm.csproj",
"samples\\SampleControls\\ControlSamples.csproj",
"samples\\Sandbox\\Sandbox.csproj",
"src\\Avalonia.Base\\Avalonia.Base.csproj",
"src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
"src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",
"src\\Avalonia.Controls.DataGrid\\Avalonia.Controls.DataGrid.csproj",
"src\\Avalonia.Controls\\Avalonia.Controls.csproj",
"src\\Avalonia.DesignerSupport\\Avalonia.DesignerSupport.csproj",
"src\\Avalonia.Desktop\\Avalonia.Desktop.csproj",
"src\\Avalonia.Diagnostics\\Avalonia.Diagnostics.csproj",
"src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj",
"src\\Avalonia.FreeDesktop\\Avalonia.FreeDesktop.csproj",
"src\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj",
"src\\Avalonia.Headless\\Avalonia.Headless.csproj",
"src\\Avalonia.MicroCom\\Avalonia.MicroCom.csproj",
"src\\Avalonia.Native\\Avalonia.Native.csproj",
"src\\Avalonia.OpenGL\\Avalonia.OpenGL.csproj",
"src\\Avalonia.ReactiveUI\\Avalonia.ReactiveUI.csproj",
"src\\Avalonia.Remote.Protocol\\Avalonia.Remote.Protocol.csproj",
"src\\Avalonia.Themes.Fluent\\Avalonia.Themes.Fluent.csproj",
"src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj",
"src\\Avalonia.X11\\Avalonia.X11.csproj",
"src\\Linux\\Avalonia.LinuxFramebuffer\\Avalonia.LinuxFramebuffer.csproj",
"src\\Markup\\Avalonia.Markup.Xaml.Loader\\Avalonia.Markup.Xaml.Loader.csproj",
"src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj",
"src\\Markup\\Avalonia.Markup\\Avalonia.Markup.csproj",
"src\\Skia\\Avalonia.Skia\\Avalonia.Skia.csproj",
"src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj",
"src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj",
"src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj",
"src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
"src\\tools\\DevGenerators\\DevGenerators.csproj",
"tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj",
"tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj",
"tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj",
"tests\\Avalonia.Controls.UnitTests\\Avalonia.Controls.UnitTests.csproj",
"tests\\Avalonia.DesignerSupport.TestApp\\Avalonia.DesignerSupport.TestApp.csproj",
"tests\\Avalonia.DesignerSupport.Tests\\Avalonia.DesignerSupport.Tests.csproj",
"tests\\Avalonia.Direct2D1.RenderTests\\Avalonia.Direct2D1.RenderTests.csproj",
"tests\\Avalonia.Direct2D1.UnitTests\\Avalonia.Direct2D1.UnitTests.csproj",
"tests\\Avalonia.IntegrationTests.Appium\\Avalonia.IntegrationTests.Appium.csproj",
"tests\\Avalonia.LeakTests\\Avalonia.LeakTests.csproj",
"tests\\Avalonia.Markup.UnitTests\\Avalonia.Markup.UnitTests.csproj",
"tests\\Avalonia.Markup.Xaml.UnitTests\\Avalonia.Markup.Xaml.UnitTests.csproj",
"tests\\Avalonia.ReactiveUI.UnitTests\\Avalonia.ReactiveUI.UnitTests.csproj",
"tests\\Avalonia.Skia.RenderTests\\Avalonia.Skia.RenderTests.csproj",
"tests\\Avalonia.Skia.UnitTests\\Avalonia.Skia.UnitTests.csproj",
"tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
]
}
}

38
Documentation/build.md

@ -1,8 +1,8 @@
# Windows
Avalonia requires at least Visual Studio 2022 and dotnet 6 SDK 6.0.100 to build on all platforms.
Avalonia requires at least Visual Studio 2022 and dotnet 7-rc2 SDK 7.0.100-rc.2 to build on all platforms.
### Clone the Avalonia repository
## Clone the Avalonia repository
```
git clone https://github.com/AvaloniaUI/Avalonia.git
@ -10,15 +10,30 @@ cd Avalonia
git submodule update --init
```
### Install the required version of the .NET Core SDK
## Install the required version of the .NET Core SDK
Go to https://dotnet.microsoft.com/download/visual-studio-sdks and install the latest version of the .NET Core SDK compatible with Avalonia UI. Make sure to download the SDK (not just the "runtime") package. The version compatible is indicated within the [global.json](https://github.com/AvaloniaUI/Avalonia/blob/master/global.json) file. Note that Avalonia UI does not always use the latest version and is hardcoded to use the last version known to be compatible (SDK releases may break the builds from time-to-time).
### Open in Visual Studio
## Build and Run Avalonia
Open the `Avalonia.sln` solution in Visual Studio 2022 or newer. The free Visual Studio Community edition works fine. Build and run the `Samples\ControlCatalog.Desktop` or `ControlCatalog.NetCore` project to see the sample application.
```
cd samples\ControlCatalog.NetCore
dotnet restore
dotnet run
```
## Opening in Visual Studio
### Troubleshooting
If you want to open Avalonia in Visual Studio you have two options:
- Avalonia.sln: This contains the whole of Avalonia in including desktop, mobile and web. You must have a number of dotnet workloads installed in order to build everything in this solution
- Avalonia.Desktop.slnf: This solution filter opens only the parts of Avalonia required to run on desktop. This requires no extra workloads to be installed.
Avalonia requires Visual Studio 2022 or newer. The free Visual Studio Community edition works fine.
Build and run `ControlCatalog.NetCore` project to see the sample application.
### Visual Studio Troubleshooting
* **Error CS0006: Avalonia.DesktopRuntime.dll could not be found**
@ -35,16 +50,15 @@ It's *not* possible to build the *whole* project on Linux/macOS. You can only bu
MonoDevelop, Xamarin Studio and Visual Studio for Mac aren't capable of properly opening our solution. You can use Rider (at least 2017.2 EAP) or VSCode instead. They will fail to load most of platform specific projects, but you don't need them to run on .NET Core.
### Install the latest version of the .NET Core SDK
## Install the latest version of the .NET Core SDK
Go to https://www.microsoft.com/net/core and follow the instructions for your OS. Make sure to download the SDK (not just the "runtime") package.
### Additional requirements for macOS
## Additional requirements for macOS
The build process needs [Xcode](https://developer.apple.com/xcode/) to build the native library. Following the install instructions at the [Xcode](https://developer.apple.com/xcode/) website to properly install.
### Clone the Avalonia repository
## Clone the Avalonia repository
```
git clone https://github.com/AvaloniaUI/Avalonia.git
@ -52,7 +66,7 @@ cd Avalonia
git submodule update --init --recursive
```
### Build native libraries (macOS only)
## Build native libraries (macOS only)
On macOS it is necessary to build and manually install the respective native libraries using [Xcode](https://developer.apple.com/xcode/). Execute the build script in the root project with the `CompileNative` task. It will build the headers, build the libraries, and place them in the appropriate place to allow .NET to find them at compilation and run time.
@ -60,7 +74,7 @@ On macOS it is necessary to build and manually install the respective native lib
./build.sh CompileNative
```
### Build and Run Avalonia
## Build and Run Avalonia
```
cd samples/ControlCatalog.NetCore

2
readme.md

@ -104,7 +104,7 @@ Support this project by becoming a sponsor. Your logo will show up here with a l
## Commercial Support
We have a range of [support plans available](https://avaloniaui.net/support.html) for those looking to partner with the creators of Avalonia, enabling access to the best support at every step of the development process.
We have a range of [support plans available](https://avaloniaui.net/support) for those looking to partner with the creators of Avalonia, enabling access to the best support at every step of the development process.
*Please note that donations are not considered payment for commercial support agreements. Please contact us to discuss your needs first. [team@avaloniaui.net](mailto://team@avaloniaui.net)*
## .NET Foundation

16
src/Avalonia.Base/Controls/NameScopeEventArgs.cs

@ -1,16 +0,0 @@
using System;
namespace Avalonia.Controls
{
public class NameScopeEventArgs : EventArgs
{
public NameScopeEventArgs(string name, object element)
{
Name = name;
Element = element;
}
public string Name { get; }
public object Element { get; }
}
}

2
src/Avalonia.Base/Input/DragEventArgs.cs

@ -32,7 +32,7 @@ namespace Avalonia.Input
return point;
}
public DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers)
internal DragEventArgs(RoutedEvent<DragEventArgs> routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers)
: base(routedEvent)
{
Data = data;

5
src/Avalonia.Base/Input/GotFocusEventArgs.cs

@ -7,6 +7,11 @@ namespace Avalonia.Input
/// </summary>
public class GotFocusEventArgs : RoutedEventArgs
{
internal GotFocusEventArgs()
{
}
/// <summary>
/// Gets or sets a value indicating how the change in focus occurred.
/// </summary>

5
src/Avalonia.Base/Input/KeyEventArgs.cs

@ -5,6 +5,11 @@ namespace Avalonia.Input
{
public class KeyEventArgs : RoutedEventArgs
{
internal KeyEventArgs()
{
}
public IKeyboardDevice? Device { get; set; }
public Key Key { get; set; }

2
src/Avalonia.Base/Input/PointerDeltaEventArgs.cs

@ -7,7 +7,7 @@ namespace Avalonia.Input
{
public Vector Delta { get; set; }
public PointerDeltaEventArgs(RoutedEvent routedEvent, IInteractive? source,
internal PointerDeltaEventArgs(RoutedEvent routedEvent, IInteractive? source,
IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp,
PointerPointProperties properties, KeyModifiers modifiers, Vector delta)
: base(routedEvent, source, pointer, rootVisual, rootVisualPosition,

12
src/Avalonia.Base/Input/PointerEventArgs.cs

@ -13,7 +13,7 @@ namespace Avalonia.Input
private readonly PointerPointProperties _properties;
private readonly Lazy<IReadOnlyList<RawPointerPoint>?>? _previousPoints;
public PointerEventArgs(RoutedEvent routedEvent,
internal PointerEventArgs(RoutedEvent routedEvent,
IInteractive? source,
IPointer pointer,
IVisual? rootVisual, Point rootVisualPosition,
@ -30,8 +30,8 @@ namespace Avalonia.Input
Timestamp = timestamp;
KeyModifiers = modifiers;
}
public PointerEventArgs(RoutedEvent routedEvent,
internal PointerEventArgs(RoutedEvent routedEvent,
IInteractive? source,
IPointer pointer,
IVisual? rootVisual, Point rootVisualPosition,
@ -124,7 +124,7 @@ namespace Avalonia.Input
public class PointerPressedEventArgs : PointerEventArgs
{
public PointerPressedEventArgs(
internal PointerPressedEventArgs(
IInteractive source,
IPointer pointer,
IVisual rootVisual, Point rootVisualPosition,
@ -143,7 +143,7 @@ namespace Avalonia.Input
public class PointerReleasedEventArgs : PointerEventArgs
{
public PointerReleasedEventArgs(
internal PointerReleasedEventArgs(
IInteractive source, IPointer pointer,
IVisual rootVisual, Point rootVisualPosition, ulong timestamp,
PointerPointProperties properties, KeyModifiers modifiers,
@ -164,7 +164,7 @@ namespace Avalonia.Input
{
public IPointer Pointer { get; }
public PointerCaptureLostEventArgs(IInteractive source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent)
internal PointerCaptureLostEventArgs(IInteractive source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent)
{
Pointer = pointer;
Source = source;

2
src/Avalonia.Base/Input/PointerWheelEventArgs.cs

@ -7,7 +7,7 @@ namespace Avalonia.Input
{
public Vector Delta { get; set; }
public PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual,
internal PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual,
Point rootVisualPosition, ulong timestamp,
PointerPointProperties properties, KeyModifiers modifiers, Vector delta)
: base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition,

6
src/Avalonia.Base/Input/ScrollGestureEventArgs.cs

@ -9,8 +9,8 @@ namespace Avalonia.Input
private static int _nextId = 1;
public static int GetNextFreeId() => _nextId++;
public ScrollGestureEventArgs(int id, Vector delta) : base(Gestures.ScrollGestureEvent)
internal ScrollGestureEventArgs(int id, Vector delta) : base(Gestures.ScrollGestureEvent)
{
Id = id;
Delta = delta;
@ -21,7 +21,7 @@ namespace Avalonia.Input
{
public int Id { get; }
public ScrollGestureEndedEventArgs(int id) : base(Gestures.ScrollGestureEndedEvent)
internal ScrollGestureEndedEventArgs(int id) : base(Gestures.ScrollGestureEndedEvent)
{
Id = id;
}

2
src/Avalonia.Base/Input/TappedEventArgs.cs

@ -7,7 +7,7 @@ namespace Avalonia.Input
{
private readonly PointerEventArgs lastPointerEventArgs;
public TappedEventArgs(RoutedEvent routedEvent, PointerEventArgs lastPointerEventArgs)
internal TappedEventArgs(RoutedEvent routedEvent, PointerEventArgs lastPointerEventArgs)
: base(routedEvent)
{
this.lastPointerEventArgs = lastPointerEventArgs;

4
src/Avalonia.Base/Input/TextInputEventArgs.cs

@ -4,6 +4,10 @@ namespace Avalonia.Input
{
public class TextInputEventArgs : RoutedEventArgs
{
internal TextInputEventArgs()
{
}
public IKeyboardDevice? Device { get; set; }
public string? Text { get; set; }

5
src/Avalonia.Base/Input/VectorEventArgs.cs

@ -5,6 +5,11 @@ namespace Avalonia.Input
{
public class VectorEventArgs : RoutedEventArgs
{
internal VectorEventArgs()
{
}
public Vector Vector { get; set; }
}
}

2
src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs

@ -7,7 +7,7 @@ namespace Avalonia.Layout
/// </summary>
public class EffectiveViewportChangedEventArgs : EventArgs
{
public EffectiveViewportChangedEventArgs(Rect effectiveViewport)
internal EffectiveViewportChangedEventArgs(Rect effectiveViewport)
{
EffectiveViewport = effectiveViewport;
}

18
src/Avalonia.Base/Media/Imaging/Bitmap.cs

@ -121,18 +121,28 @@ namespace Avalonia.Media.Imaging
/// Saves the bitmap to a file.
/// </summary>
/// <param name="fileName">The filename.</param>
public void Save(string fileName)
/// <param name="quality">
/// The optional quality for compression.
/// The quality value is interpreted from 0 - 100. If quality is null the default quality
/// setting is applied.
/// </param>
public void Save(string fileName, int? quality = null)
{
PlatformImpl.Item.Save(fileName);
PlatformImpl.Item.Save(fileName, quality);
}
/// <summary>
/// Saves the bitmap to a stream.
/// </summary>
/// <param name="stream">The stream.</param>
public void Save(Stream stream)
/// <param name="quality">
/// The optional quality for compression.
/// The quality value is interpreted from 0 - 100. If quality is null the default quality
/// setting is applied.
/// </param>
public void Save(Stream stream, int? quality = null)
{
PlatformImpl.Item.Save(stream);
PlatformImpl.Item.Save(stream, quality);
}
/// <inheritdoc/>

14
src/Avalonia.Base/Media/Imaging/IBitmap.cs

@ -35,12 +35,22 @@ namespace Avalonia.Media.Imaging
/// Saves the bitmap to a file.
/// </summary>
/// <param name="fileName">The filename.</param>
void Save(string fileName);
/// <param name="quality">
/// The optional quality for compression if supported by the specific backend.
/// The quality value is interpreted from 0 - 100. If quality is null the default quality
/// setting of the backend is applied.
/// </param>
void Save(string fileName, int? quality = null);
/// <summary>
/// Saves the bitmap to a stream in png format.
/// </summary>
/// <param name="stream">The stream.</param>
void Save(Stream stream);
/// <param name="quality">
/// The optional quality for compression if supported by the specific backend.
/// The quality value is interpreted from 0 - 100. If quality is null the default quality
/// setting of the backend is applied.
/// </param>
void Save(Stream stream, int? quality = null);
}
}

14
src/Avalonia.Base/Platform/IBitmapImpl.cs

@ -29,12 +29,22 @@ namespace Avalonia.Platform
/// Saves the bitmap to a file.
/// </summary>
/// <param name="fileName">The filename.</param>
void Save(string fileName);
/// <param name="quality">
/// The optional quality for compression if supported by the specific backend.
/// The quality value is interpreted from 0 - 100. If quality is null the default quality
/// setting of the backend is applied.
/// </param>
void Save(string fileName, int? quality = null);
/// <summary>
/// Saves the bitmap to a stream in png format.
/// </summary>
/// <param name="stream">The stream.</param>
void Save(Stream stream);
/// <param name="quality">
/// The optional quality for compression if supported by the specific backend.
/// The quality value is interpreted from 0 - 100. If quality is null the default quality
/// setting of the backend is applied.
/// </param>
void Save(Stream stream, int? quality = null);
}
}

2
src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs

@ -12,7 +12,7 @@ namespace Avalonia.Rendering
/// </summary>
/// <param name="root">The render root that has been updated.</param>
/// <param name="dirtyRect">The updated area.</param>
public SceneInvalidatedEventArgs(
internal SceneInvalidatedEventArgs(
IRenderRoot root,
Rect dirtyRect)
{

55
src/Avalonia.Controls/Documents/InlineCollection.cs

@ -89,56 +89,45 @@ namespace Avalonia.Controls.Documents
}
public override void Add(Inline inline)
{
if (InlineHost is TextBlock textBlock && !string.IsNullOrEmpty(textBlock._text))
{
base.Add(new Run(textBlock._text));
textBlock._text = null;
}
base.Add(inline);
}
/// <summary>
/// Add a text segment to the collection.
/// Adds a text segment to the collection.
/// <remarks>
/// For non complex content this appends the text to the end of currently held text.
/// For complex content this adds a <see cref="Run"/> to the collection.
/// </remarks>
/// </summary>
/// <param name="text"></param>
/// <param name="text">The to be added text.</param>
public void Add(string text)
{
AddText(text);
}
public override void Add(Inline inline)
{
OnAdd();
base.Add(inline);
}
public void Add(IControl child)
{
OnAdd();
base.Add(new InlineUIContainer(child));
}
private void AddText(string text)
{
if (LogicalChildren is TextBlock textBlock && !textBlock.HasComplexContent)
if (InlineHost is TextBlock textBlock && !textBlock.HasComplexContent)
{
textBlock._text += text;
}
else
{
base.Add(new Run(text));
Add(new Run(text));
}
}
private void OnAdd()
/// <summary>
/// Adds a control wrapped inside a <see cref="InlineUIContainer"/> to the collection.
/// </summary>
/// <param name="control">The to be added control.</param>
public void Add(IControl control)
{
if (LogicalChildren is TextBlock textBlock)
{
if (!textBlock.HasComplexContent && !string.IsNullOrEmpty(textBlock._text))
{
base.Add(new Run(textBlock._text));
textBlock._text = null;
}
}
Add(new InlineUIContainer(control));
}
/// <summary>

19
src/Avalonia.Controls/ProgressBar.cs

@ -110,7 +110,7 @@ namespace Avalonia.Controls
public static readonly StyledProperty<string> ProgressTextFormatProperty =
AvaloniaProperty.Register<ProgressBar, string>(nameof(ProgressTextFormat), "{1:0}%");
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<ProgressBar, Orientation>(nameof(Orientation), Orientation.Horizontal);
@ -136,7 +136,7 @@ namespace Avalonia.Controls
get { return _percentage; }
private set { SetAndRaise(PercentageProperty, ref _percentage, value); }
}
public double IndeterminateStartingOffset
{
get => _indeterminateStartingOffset;
@ -156,6 +156,7 @@ namespace Avalonia.Controls
MinimumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
MaximumProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
IsIndeterminateProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
OrientationProperty.Changed.AddClassHandler<ProgressBar>((x, e) => x.UpdateIndicatorWhenPropChanged(e));
}
public ProgressBar()
@ -216,13 +217,13 @@ namespace Avalonia.Controls
{
// dispose any previous track size listener
_trackSizeChangedListener?.Dispose();
_indicator = e.NameScope.Get<Border>("PART_Indicator");
// listen to size changes of the indicators track (parent) and update the indicator there.
_trackSizeChangedListener = _indicator.Parent?.GetPropertyChangedObservable(BoundsProperty)
.Subscribe(_ => UpdateIndicator());
UpdateIndicator();
}
@ -230,7 +231,7 @@ namespace Avalonia.Controls
{
// Gets the size of the parent indicator container
var barSize = _indicator?.Parent?.Bounds.Size ?? Bounds.Size;
if (_indicator != null)
{
if (IsIndeterminate)
@ -268,10 +269,18 @@ namespace Avalonia.Controls
{
double percent = Maximum == Minimum ? 1.0 : (Value - Minimum) / (Maximum - Minimum);
// When the Orientation changed, the indicator's Width or Height should set to double.NaN.
if (Orientation == Orientation.Horizontal)
{
_indicator.Width = barSize.Width * percent;
_indicator.Height = double.NaN;
}
else
{
_indicator.Width = double.NaN;
_indicator.Height = barSize.Height * percent;
}
Percentage = percent * 100;
}

19
src/Avalonia.Controls/TextBlock.cs

@ -597,23 +597,12 @@ namespace Avalonia.Controls
protected virtual void SetText(string? text)
{
if (Inlines != null && Inlines.Count > 0)
{
var oldValue = Inlines.Text;
if (!string.IsNullOrEmpty(text))
{
Inlines.Add(text);
}
text = Inlines.Text;
RaisePropertyChanged(TextProperty, oldValue, text);
}
else
if (HasComplexContent)
{
SetAndRaise(TextProperty, ref _text, text);
Inlines?.Clear();
}
SetAndRaise(TextProperty, ref _text, text);
}
/// <summary>

4
src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs

@ -323,12 +323,12 @@ namespace Avalonia.Headless
public Vector Dpi { get; }
public PixelSize PixelSize { get; }
public int Version { get; set; }
public void Save(string fileName)
public void Save(string fileName, int? quality = null)
{
}
public void Save(Stream stream)
public void Save(Stream stream, int? quality = null)
{
}

4
src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs

@ -25,9 +25,9 @@ namespace Avalonia.Skia
public Vector Dpi { get; }
public PixelSize PixelSize { get; }
public int Version { get; private set; }
public void Save(string fileName) => throw new NotSupportedException();
public void Save(string fileName, int? quality = null) => throw new NotSupportedException();
public void Save(Stream stream) => throw new NotSupportedException();
public void Save(Stream stream, int? quality = null) => throw new NotSupportedException();
public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint)
{

33
src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs

@ -14,14 +14,19 @@ namespace Avalonia.Skia.Helpers
/// </summary>
/// <param name="image">Image to save</param>
/// <param name="fileName">Target file.</param>
public static void SaveImage(SKImage image, string fileName)
/// <param name="quality">
/// The optional quality for PNG compression.
/// The quality value is interpreted from 0 - 100. If quality is null
/// the encoder applies the default quality value.
/// </param>
public static void SaveImage(SKImage image, string fileName, int? quality = null)
{
if (image == null) throw new ArgumentNullException(nameof(image));
if (fileName == null) throw new ArgumentNullException(nameof(fileName));
using (var stream = File.Create(fileName))
{
SaveImage(image, stream);
SaveImage(image, stream, quality);
}
}
@ -29,16 +34,30 @@ namespace Avalonia.Skia.Helpers
/// Save Skia image to a stream.
/// </summary>
/// <param name="image">Image to save</param>
/// <param name="stream">Target stream.</param>
public static void SaveImage(SKImage image, Stream stream)
/// <param name="quality">
/// The optional quality for PNG compression.
/// The quality value is interpreted from 0 - 100. If quality is null
/// the encoder applies the default quality value.
/// </param>
public static void SaveImage(SKImage image, Stream stream, int? quality = null)
{
if (image == null) throw new ArgumentNullException(nameof(image));
if (stream == null) throw new ArgumentNullException(nameof(stream));
using (var data = image.Encode())
if (quality == null)
{
data.SaveTo(stream);
using (var data = image.Encode())
{
data.SaveTo(stream);
}
}
else
{
using (var data = image.Encode(SKEncodedImageFormat.Png, (int)quality))
{
data.SaveTo(stream);
}
}
}
}
}
}

4
src/Skia/Avalonia.Skia/ImmutableBitmap.cs

@ -139,13 +139,13 @@ namespace Avalonia.Skia
}
/// <inheritdoc />
public void Save(string fileName)
public void Save(string fileName, int? quality = null)
{
ImageSavingHelper.SaveImage(_image, fileName);
}
/// <inheritdoc />
public void Save(Stream stream)
public void Save(Stream stream, int? quality = null)
{
ImageSavingHelper.SaveImage(_image, stream);
}

8
src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs

@ -116,20 +116,20 @@ namespace Avalonia.Skia
public int Version { get; private set; } = 1;
/// <inheritdoc />
public void Save(string fileName)
public void Save(string fileName, int? quality = null)
{
using (var image = SnapshotImage())
{
ImageSavingHelper.SaveImage(image, fileName);
ImageSavingHelper.SaveImage(image, fileName, quality);
}
}
/// <inheritdoc />
public void Save(Stream stream)
public void Save(Stream stream, int? quality = null)
{
using (var image = SnapshotImage())
{
ImageSavingHelper.SaveImage(image, stream);
ImageSavingHelper.SaveImage(image, stream, quality);
}
}

8
src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs

@ -137,20 +137,20 @@ namespace Avalonia.Skia
}
/// <inheritdoc />
public void Save(Stream stream)
public void Save(Stream stream, int? quality = null)
{
using (var image = GetSnapshot())
{
ImageSavingHelper.SaveImage(image, stream);
ImageSavingHelper.SaveImage(image, stream, quality);
}
}
/// <inheritdoc />
public void Save(string fileName)
public void Save(string fileName, int? quality = null)
{
using (var image = GetSnapshot())
{
ImageSavingHelper.SaveImage(image, fileName);
ImageSavingHelper.SaveImage(image, fileName, quality);
}
}

6
src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs

@ -15,7 +15,7 @@ namespace Avalonia.Direct2D1.Media
public abstract OptionalDispose<D2DBitmap> GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target);
public void Save(string fileName)
public void Save(string fileName, int? quality = null)
{
if (Path.GetExtension(fileName) != ".png")
{
@ -25,11 +25,11 @@ namespace Avalonia.Direct2D1.Media
using (FileStream s = new FileStream(fileName, FileMode.Create))
{
Save(s);
Save(s, quality);
}
}
public abstract void Save(Stream stream);
public abstract void Save(Stream stream, int? quality = null);
public virtual void Dispose()
{

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

@ -43,7 +43,7 @@ namespace Avalonia.Direct2D1.Media
return new OptionalDispose<Bitmap>(_direct2DBitmap, false);
}
public override void Save(Stream stream)
public override void Save(Stream stream, int? quality = null)
{
using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream))
using (var frame = new BitmapFrameEncode(encoder))

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

@ -56,7 +56,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
return new OptionalDispose<D2DBitmap>(_renderTarget.Bitmap, false);
}
public override void Save(Stream stream)
public override void Save(Stream stream, int? quality = null)
{
using (var wic = new WicRenderTargetBitmapImpl(PixelSize, Dpi))
{

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

@ -186,7 +186,7 @@ namespace Avalonia.Direct2D1.Media
return new OptionalDispose<D2DBitmap>(D2DBitmap.FromWicBitmap(renderTarget, converter), true);
}
public override void Save(Stream stream)
public override void Save(Stream stream, int? quality = null)
{
using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream))
using (var frame = new BitmapFrameEncode(encoder))

20
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -1083,6 +1083,20 @@ namespace Avalonia.Win32.Interop
public const int SizeOf_BITMAPINFOHEADER = 40;
[DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)]
unsafe internal static extern int GetMouseMovePointsEx(
uint cbSize, MOUSEMOVEPOINT* pointsIn,
MOUSEMOVEPOINT* pointsBufferOut, int nBufPoints, uint resolution);
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] // For GetMouseMovePointsEx
public struct MOUSEMOVEPOINT
{
public int x; //Specifies the x-coordinate of the mouse
public int y; //Specifies the x-coordinate of the mouse
public int time; //Specifies the time stamp of the mouse coordinate
public IntPtr dwExtraInfo; //Specifies extra information associated with this coordinate.
}
[DllImport("user32.dll", SetLastError = true)]
public static extern bool IsMouseInPointerEnabled();
@ -2104,6 +2118,12 @@ namespace Avalonia.Win32.Interop
public int Y;
}
public struct SIZE_F
{
public float X;
public float Y;
}
public struct RECT
{
public int left;

3
src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs

@ -174,8 +174,9 @@ namespace Avalonia.Win32.WinRT.Composition
using var sc = _syncContext.EnsureLocked();
using var desktopTarget = _compositorDesktopInterop.CreateDesktopWindowTarget(hWnd, 0);
using var target = desktopTarget.QueryInterface<ICompositionTarget>();
using var device2 = _device.QueryInterface<ICompositionGraphicsDevice2>();
using var drawingSurface = _device.CreateDrawingSurface(new UnmanagedMethods.SIZE(), DirectXPixelFormat.B8G8R8A8UIntNormalized,
using var drawingSurface = device2.CreateDrawingSurface2(new UnmanagedMethods.SIZE(), DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied);
using var surface = drawingSurface.QueryInterface<ICompositionSurface>();
using var surfaceInterop = drawingSurface.QueryInterface<ICompositionDrawingSurfaceInterop>();

11
src/Windows/Avalonia.Win32/WinRT/winrt.idl

@ -8,6 +8,7 @@
@clr-map Matrix4x4 System.Numerics.Matrix4x4
@clr-map RECT Avalonia.Win32.Interop.UnmanagedMethods.RECT
@clr-map SIZE Avalonia.Win32.Interop.UnmanagedMethods.SIZE
@clr-map SIZE_F Avalonia.Win32.Interop.UnmanagedMethods.SIZE_F
@clr-map POINT Avalonia.Win32.Interop.UnmanagedMethods.POINT
@clr-map HWND IntPtr
@clr-map BOOL int
@ -442,12 +443,18 @@ interface IDesktopWindowContentBridgeInterop : IUnknown
[uuid(FB22C6E1-80A2-4667-9936-DBEAF6EEFE95)]
interface ICompositionGraphicsDevice : IInspectable
{
HRESULT CreateDrawingSurface([in] SIZE sizePixels, [in] DirectXPixelFormat pixelFormat,
HRESULT CreateDrawingSurface([in] SIZE_F sizePixels, [in] DirectXPixelFormat pixelFormat,
[in] DirectXAlphaMode alphaMode, [out] [retval] ICompositionDrawingSurface** result);
HRESULT AddRenderingDeviceReplaced(void* handler, void* token);
HRESULT RemoveRenderingDeviceReplaced([in] int token);
}
[uuid(0FB8BDF6-C0F0-4BCC-9FB8-084982490D7D)]
interface ICompositionGraphicsDevice2 : IInspectable
{
HRESULT CreateDrawingSurface2([in] SIZE sizePixels, [in] DirectXPixelFormat pixelFormat, [in] DirectXAlphaMode alphaMode, [out] [retval] ICompositionDrawingSurface** result);
}
[uuid(1527540D-42C7-47A6-A408-668F79A90DFB)]
interface ICompositionSurface : IInspectable
{
@ -465,7 +472,7 @@ interface ICompositionDrawingSurface : IInspectable
{
[propget] HRESULT GetAlphaMode([out] [retval] DirectXAlphaMode* value);
[propget] HRESULT GetPixelFormat([out] [retval] DirectXPixelFormat* value);
[propget] HRESULT GetSize([out] [retval] POINT* value);
[propget] HRESULT GetSize([out] [retval] SIZE_F* value);
}
enum CompositionBitmapInterpolationMode

84
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -272,13 +272,34 @@ namespace Avalonia.Win32
TrackMouseEvent(ref tm);
}
var point = DipFromLParam(lParam);
// Prepare points for the IntermediatePoints call.
var p = new POINT()
{
X = (int)(point.X * RenderScaling),
Y = (int)(point.Y * RenderScaling)
};
ClientToScreen(_hwnd, ref p);
var currPoint = new MOUSEMOVEPOINT()
{
x = p.X & 0xFFFF,
y = p.Y & 0xFFFF,
time = (int)timestamp
};
var prevPoint = _lastWmMousePoint;
_lastWmMousePoint = currPoint;
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
_owner,
RawPointerEventType.Move,
DipFromLParam(lParam),
GetMouseModifiers(wParam));
point,
GetMouseModifiers(wParam))
{
IntermediatePoints = new Lazy<IReadOnlyList<RawPointerPoint>>(() => CreateLazyIntermediatePoints(currPoint, prevPoint))
};
break;
}
@ -782,6 +803,65 @@ namespace Avalonia.Win32
return null;
}
private unsafe IReadOnlyList<RawPointerPoint> CreateLazyIntermediatePoints(MOUSEMOVEPOINT movePoint, MOUSEMOVEPOINT prevMovePoint)
{
// To understand some of this code, please check MS docs:
// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmousemovepointsex#remarks
fixed (MOUSEMOVEPOINT* movePoints = s_mouseHistoryInfos)
{
var movePointCopy = movePoint;
movePointCopy.time = 0; // empty "time" as otherwise WinAPI will always fail
int pointsCount = GetMouseMovePointsEx(
(uint)(Marshal.SizeOf(movePointCopy)),
&movePointCopy, movePoints, s_mouseHistoryInfos.Length,
1);
// GetMouseMovePointsEx can return -1 if point wasn't found or there is so beeg delay that original points were erased from the buffer.
if (pointsCount <= 1)
{
return Array.Empty<RawPointerPoint>();
}
s_intermediatePointsPooledList.Clear();
s_intermediatePointsPooledList.Capacity = pointsCount;
for (int i = pointsCount - 1; i >= 1; i--)
{
var historyInfo = s_mouseHistoryInfos[i];
// Skip points newer than current point.
if (historyInfo.time > movePoint.time ||
(historyInfo.time == movePoint.time &&
historyInfo.x == movePoint.x &&
historyInfo.y == movePoint.y))
{
continue;
}
// Skip poins older from previous WM_MOUSEMOVE point.
if (historyInfo.time < prevMovePoint.time ||
(historyInfo.time == prevMovePoint.time &&
historyInfo.x == prevMovePoint.x &&
historyInfo.y == prevMovePoint.y))
{
continue;
}
// To support multiple screens.
if (historyInfo.x > 32767)
historyInfo.x -= 65536;
if (historyInfo.y > 32767)
historyInfo.y -= 65536;
var point = PointToClient(new PixelPoint(historyInfo.x, historyInfo.y));
s_intermediatePointsPooledList.Add(new RawPointerPoint
{
Position = point
});
}
return s_intermediatePointsPooledList;
}
}
private RawPointerEventArgs CreatePointerArgs(IInputDevice device, ulong timestamp, RawPointerEventType eventType, RawPointerPoint point, RawInputModifiers modifiers, uint rawPointerId)
{
return device is TouchDevice

2
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -65,6 +65,7 @@ namespace Avalonia.Win32
private bool _isUsingComposition;
private IBlurHost _blurHost;
private PlatformResizeReason _resizeReason;
private MOUSEMOVEPOINT _lastWmMousePoint;
#if USE_MANAGED_DRAG
private readonly ManagedWindowResizeDragHelper _managedDrag;
@ -107,6 +108,7 @@ namespace Avalonia.Win32
private static readonly POINTER_TOUCH_INFO[] s_historyTouchInfos = new POINTER_TOUCH_INFO[MaxPointerHistorySize];
private static readonly POINTER_PEN_INFO[] s_historyPenInfos = new POINTER_PEN_INFO[MaxPointerHistorySize];
private static readonly POINTER_INFO[] s_historyInfos = new POINTER_INFO[MaxPointerHistorySize];
private static readonly MOUSEMOVEPOINT[] s_mouseHistoryInfos = new MOUSEMOVEPOINT[64];
public WindowImpl()
{

22
tests/Avalonia.Controls.UnitTests/TextBlockTests.cs

@ -3,6 +3,7 @@ using Avalonia.Controls.Documents;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Moq;
@ -181,5 +182,26 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(60, button.Bounds.Left);
}
}
[Fact]
public void Setting_Text_Should_Reset_Inlines()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var target = new TextBlock();
target.Inlines.Add(new Run("Hello World"));
Assert.Equal("Hello World", target.Text);
Assert.Equal(1, target.Inlines.Count);
target.Text = "1234";
Assert.Equal("1234", target.Text);
Assert.Equal(0, target.Inlines.Count);
}
}
}
}

Loading…
Cancel
Save