diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf new file mode 100644 index 0000000000..6ba05332be --- /dev/null +++ b/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" + ] + } +} \ No newline at end of file diff --git a/Documentation/build.md b/Documentation/build.md index ddd38be887..fd6b26337c 100644 --- a/Documentation/build.md +++ b/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 diff --git a/readme.md b/readme.md index 1009e86c29..c2be487af3 100644 --- a/readme.md +++ b/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 diff --git a/src/Avalonia.Base/Controls/NameScopeEventArgs.cs b/src/Avalonia.Base/Controls/NameScopeEventArgs.cs deleted file mode 100644 index 3e9eaa6057..0000000000 --- a/src/Avalonia.Base/Controls/NameScopeEventArgs.cs +++ /dev/null @@ -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; } - } -} diff --git a/src/Avalonia.Base/Input/DragEventArgs.cs b/src/Avalonia.Base/Input/DragEventArgs.cs index 0e613c0f21..41276e2f06 100644 --- a/src/Avalonia.Base/Input/DragEventArgs.cs +++ b/src/Avalonia.Base/Input/DragEventArgs.cs @@ -32,7 +32,7 @@ namespace Avalonia.Input return point; } - public DragEventArgs(RoutedEvent routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers) + internal DragEventArgs(RoutedEvent routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers) : base(routedEvent) { Data = data; diff --git a/src/Avalonia.Base/Input/GotFocusEventArgs.cs b/src/Avalonia.Base/Input/GotFocusEventArgs.cs index 5cce138ee0..f3de55ebae 100644 --- a/src/Avalonia.Base/Input/GotFocusEventArgs.cs +++ b/src/Avalonia.Base/Input/GotFocusEventArgs.cs @@ -7,6 +7,11 @@ namespace Avalonia.Input /// public class GotFocusEventArgs : RoutedEventArgs { + internal GotFocusEventArgs() + { + + } + /// /// Gets or sets a value indicating how the change in focus occurred. /// diff --git a/src/Avalonia.Base/Input/KeyEventArgs.cs b/src/Avalonia.Base/Input/KeyEventArgs.cs index b8291e9096..39c9766105 100644 --- a/src/Avalonia.Base/Input/KeyEventArgs.cs +++ b/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; } diff --git a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs b/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs index b3085a038d..d5577d77af 100644 --- a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs +++ b/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, diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index a0e8d4a074..f84dec42cb 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -13,7 +13,7 @@ namespace Avalonia.Input private readonly PointerPointProperties _properties; private readonly Lazy?>? _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; diff --git a/src/Avalonia.Base/Input/PointerWheelEventArgs.cs b/src/Avalonia.Base/Input/PointerWheelEventArgs.cs index e5701dcf23..dbc06ec934 100644 --- a/src/Avalonia.Base/Input/PointerWheelEventArgs.cs +++ b/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, diff --git a/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs b/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs index a682e8f0a4..fd1d0f42c3 100644 --- a/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs +++ b/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; } diff --git a/src/Avalonia.Base/Input/TappedEventArgs.cs b/src/Avalonia.Base/Input/TappedEventArgs.cs index daaab70632..8af6164fc1 100644 --- a/src/Avalonia.Base/Input/TappedEventArgs.cs +++ b/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; diff --git a/src/Avalonia.Base/Input/TextInputEventArgs.cs b/src/Avalonia.Base/Input/TextInputEventArgs.cs index cda0103749..787bf1abd3 100644 --- a/src/Avalonia.Base/Input/TextInputEventArgs.cs +++ b/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; } diff --git a/src/Avalonia.Base/Input/VectorEventArgs.cs b/src/Avalonia.Base/Input/VectorEventArgs.cs index 000fd52f69..3e8098f904 100644 --- a/src/Avalonia.Base/Input/VectorEventArgs.cs +++ b/src/Avalonia.Base/Input/VectorEventArgs.cs @@ -5,6 +5,11 @@ namespace Avalonia.Input { public class VectorEventArgs : RoutedEventArgs { + internal VectorEventArgs() + { + + } + public Vector Vector { get; set; } } } diff --git a/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs b/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs index 1cdc775b13..749d2ecc2b 100644 --- a/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs +++ b/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs @@ -7,7 +7,7 @@ namespace Avalonia.Layout /// public class EffectiveViewportChangedEventArgs : EventArgs { - public EffectiveViewportChangedEventArgs(Rect effectiveViewport) + internal EffectiveViewportChangedEventArgs(Rect effectiveViewport) { EffectiveViewport = effectiveViewport; } diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs index cf8a31c3e9..ce38fc5abc 100644 --- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs @@ -121,18 +121,28 @@ namespace Avalonia.Media.Imaging /// Saves the bitmap to a file. /// /// The filename. - public void Save(string fileName) + /// + /// The optional quality for compression. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting is applied. + /// + public void Save(string fileName, int? quality = null) { - PlatformImpl.Item.Save(fileName); + PlatformImpl.Item.Save(fileName, quality); } /// /// Saves the bitmap to a stream. /// /// The stream. - public void Save(Stream stream) + /// + /// The optional quality for compression. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting is applied. + /// + public void Save(Stream stream, int? quality = null) { - PlatformImpl.Item.Save(stream); + PlatformImpl.Item.Save(stream, quality); } /// diff --git a/src/Avalonia.Base/Media/Imaging/IBitmap.cs b/src/Avalonia.Base/Media/Imaging/IBitmap.cs index bd04d5ce86..e7d1862aa2 100644 --- a/src/Avalonia.Base/Media/Imaging/IBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/IBitmap.cs @@ -35,12 +35,22 @@ namespace Avalonia.Media.Imaging /// Saves the bitmap to a file. /// /// The filename. - void Save(string fileName); + /// + /// 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. + /// + void Save(string fileName, int? quality = null); /// /// Saves the bitmap to a stream in png format. /// /// The stream. - void Save(Stream stream); + /// + /// 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. + /// + void Save(Stream stream, int? quality = null); } } diff --git a/src/Avalonia.Base/Platform/IBitmapImpl.cs b/src/Avalonia.Base/Platform/IBitmapImpl.cs index 8f11f68e7c..299a758961 100644 --- a/src/Avalonia.Base/Platform/IBitmapImpl.cs +++ b/src/Avalonia.Base/Platform/IBitmapImpl.cs @@ -29,12 +29,22 @@ namespace Avalonia.Platform /// Saves the bitmap to a file. /// /// The filename. - void Save(string fileName); + /// + /// 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. + /// + void Save(string fileName, int? quality = null); /// /// Saves the bitmap to a stream in png format. /// /// The stream. - void Save(Stream stream); + /// + /// 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. + /// + void Save(Stream stream, int? quality = null); } } diff --git a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs index cac4d1693a..73840376fe 100644 --- a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs +++ b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs @@ -12,7 +12,7 @@ namespace Avalonia.Rendering /// /// The render root that has been updated. /// The updated area. - public SceneInvalidatedEventArgs( + internal SceneInvalidatedEventArgs( IRenderRoot root, Rect dirtyRect) { diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index a265f88e21..5b68402f87 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/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); + } + /// - /// Add a text segment to the collection. + /// Adds a text segment to the collection. /// /// For non complex content this appends the text to the end of currently held text. /// For complex content this adds a to the collection. /// /// - /// + /// The to be added text. 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() + /// + /// Adds a control wrapped inside a to the collection. + /// + /// The to be added control. + 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)); } /// diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs index 1e406157d7..71a7a58da4 100644 --- a/src/Avalonia.Controls/ProgressBar.cs +++ b/src/Avalonia.Controls/ProgressBar.cs @@ -110,7 +110,7 @@ namespace Avalonia.Controls public static readonly StyledProperty ProgressTextFormatProperty = AvaloniaProperty.Register(nameof(ProgressTextFormat), "{1:0}%"); - + public static readonly StyledProperty OrientationProperty = AvaloniaProperty.Register(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((x, e) => x.UpdateIndicatorWhenPropChanged(e)); MaximumProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); IsIndeterminateProperty.Changed.AddClassHandler((x, e) => x.UpdateIndicatorWhenPropChanged(e)); + OrientationProperty.Changed.AddClassHandler((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("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; } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 0492c2c1e3..c8e05e5cb3 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/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); } /// diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index 501d239cee..7abc0ca131 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/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) { } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs index d700d4848e..857433f95f 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs +++ b/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) { diff --git a/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs b/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs index 5fa961cc99..b4dd754822 100644 --- a/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs +++ b/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs @@ -14,14 +14,19 @@ namespace Avalonia.Skia.Helpers /// /// Image to save /// Target file. - public static void SaveImage(SKImage image, string fileName) + /// + /// 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. + /// + 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. /// /// Image to save - /// Target stream. - public static void SaveImage(SKImage image, Stream stream) + /// + /// 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. + /// + 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); + } } } } -} \ No newline at end of file +} diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 6400d67fde..e24d805050 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -139,13 +139,13 @@ namespace Avalonia.Skia } /// - public void Save(string fileName) + public void Save(string fileName, int? quality = null) { ImageSavingHelper.SaveImage(_image, fileName); } /// - public void Save(Stream stream) + public void Save(Stream stream, int? quality = null) { ImageSavingHelper.SaveImage(_image, stream); } diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 01b7449b64..d3231c92a5 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -116,20 +116,20 @@ namespace Avalonia.Skia public int Version { get; private set; } = 1; /// - 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); } } /// - 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); } } diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 80bfcc5973..d437f514bb 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -137,20 +137,20 @@ namespace Avalonia.Skia } /// - 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); } } /// - 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); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index 843efe2cc4..059105c112 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -15,7 +15,7 @@ namespace Avalonia.Direct2D1.Media public abstract OptionalDispose 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() { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index 2656ab4c58..a321b225a0 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -43,7 +43,7 @@ namespace Avalonia.Direct2D1.Media return new OptionalDispose(_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)) diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 357e472d34..c5f8e837ce 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -56,7 +56,7 @@ namespace Avalonia.Direct2D1.Media.Imaging return new OptionalDispose(_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)) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index 1156246b29..051790ef03 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -186,7 +186,7 @@ namespace Avalonia.Direct2D1.Media return new OptionalDispose(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)) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f1ed53ea99..9b86154043 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/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; diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs index 8a41c00add..6da17b8ea5 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs +++ b/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(); + using var device2 = _device.QueryInterface(); - 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(); using var surfaceInterop = drawingSurface.QueryInterface(); diff --git a/src/Windows/Avalonia.Win32/WinRT/winrt.idl b/src/Windows/Avalonia.Win32/WinRT/winrt.idl index 851df9dae6..ffb98fafc8 100644 --- a/src/Windows/Avalonia.Win32/WinRT/winrt.idl +++ b/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 diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs index f8785371d9..c4136a239e 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs +++ b/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>(() => CreateLazyIntermediatePoints(currPoint, prevPoint)) + }; break; } @@ -782,6 +803,65 @@ namespace Avalonia.Win32 return null; } + private unsafe IReadOnlyList 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(); + } + + 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 diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs index 5374614379..9ed1a50ff2 100644 --- a/src/Windows/Avalonia.Win32/WindowImpl.cs +++ b/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() { diff --git a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs index be8062322e..de5e5a8ea3 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs +++ b/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); + } + } } }