diff --git a/.editorconfig b/.editorconfig
index 30edee1633..128b6f1712 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -137,6 +137,9 @@ space_within_single_line_array_initializer_braces = true
# Wrapping preferences
csharp_wrap_before_ternary_opsigns = false
+# Avalonia DevAnalyzer preferences
+dotnet_diagnostic.AVADEV2001.severity = error
+
# Xaml files
[*.{xaml,axaml}]
indent_size = 2
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 46e8665945..2f63750cdc 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -21,7 +21,7 @@
- [ ] Consider submitting a PR to https://github.com/AvaloniaUI/Documentation with user documentation
## Breaking changes
-
+
## Obsoletions / Deprecations
diff --git a/.gitignore b/.gitignore
index 44fe5e4ba4..84faae1806 100644
--- a/.gitignore
+++ b/.gitignore
@@ -212,3 +212,6 @@ coc-settings.json
*.map
src/Web/Avalonia.Web.Blazor/wwwroot/*.js
src/Web/Avalonia.Web.Blazor/Interop/Typescript/*.js
+node_modules
+src/Web/Avalonia.Web.Blazor/webapp/package-lock.json
+src/Web/Avalonia.Web.Blazor/wwwroot
diff --git a/Avalonia.sln b/Avalonia.sln
index 4999719676..35b6b2108a 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -13,7 +13,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Direct2D1", "src\W
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Controls", "src\Avalonia.Controls\Avalonia.Controls.csproj", "{D2221C82-4A25-4583-9B43-D791E3F6820C}"
EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Default", "src\Avalonia.Themes.Default\Avalonia.Themes.Default.csproj", "{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Themes.Simple", "src\Avalonia.Themes.Simple\Avalonia.Themes.Simple.csproj", "{3E10A5FA-E8DA-48B1-AD44-6A5B6CB7750F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Diagnostics", "src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj", "{7062AE20-5DCC-4442-9645-8195BDECE63E}"
EndProject
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index edf3c3d819..52fc8db53c 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -59,7 +59,7 @@ jobs:
variables:
SolutionDir: '$(Build.SourcesDirectory)'
pool:
- vmImage: 'macOS-10.15'
+ vmImage: 'macos-12'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.418'
@@ -91,10 +91,10 @@ jobs:
inputs:
actions: 'build'
scheme: ''
- sdk: 'macosx11.1'
+ sdk: 'macosx12.3'
configuration: 'Release'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
- xcodeVersion: '12' # Options: 8, 9, default, specifyPath
+ xcodeVersion: '13' # Options: 8, 9, default, specifyPath
args: '-derivedDataPath ./'
- task: CmdLine@2
diff --git a/build/HarfBuzzSharp.props b/build/HarfBuzzSharp.props
index 85e7a1f34d..620ec58ff3 100644
--- a/build/HarfBuzzSharp.props
+++ b/build/HarfBuzzSharp.props
@@ -1,7 +1,7 @@
-
-
-
+
+
+
diff --git a/build/ReactiveUI.props b/build/ReactiveUI.props
index c3b136d41d..1911c02677 100644
--- a/build/ReactiveUI.props
+++ b/build/ReactiveUI.props
@@ -1,5 +1,5 @@
-
+
diff --git a/build/SharedVersion.props b/build/SharedVersion.props
index 3d9548ab9d..1b60bb4df9 100644
--- a/build/SharedVersion.props
+++ b/build/SharedVersion.props
@@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
Avalonia
- 0.10.999
+ 11.0.999
Copyright 2022 © The AvaloniaUI Project
https://avaloniaui.net
https://github.com/AvaloniaUI/Avalonia/
diff --git a/build/SkiaSharp.props b/build/SkiaSharp.props
index d54cffba08..31619399f9 100644
--- a/build/SkiaSharp.props
+++ b/build/SkiaSharp.props
@@ -1,7 +1,7 @@
-
-
-
+
+
+
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm
index 95f61422cb..ddc50c26b6 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm
@@ -91,8 +91,6 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
if(_parent != nullptr)
{
_parent->_children.remove(this);
-
- _parent->BringToFront();
}
auto cparent = dynamic_cast(parent);
@@ -121,7 +119,7 @@ void WindowImpl::BringToFront()
{
if(Window != nullptr)
{
- if (![Window isMiniaturized])
+ if ([Window isVisible] && ![Window isMiniaturized])
{
if(IsDialog())
{
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index 9fcb9d6b7f..4bbb667154 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -36,25 +36,6 @@ partial class Build : NukeBuild
{
[Solution("Avalonia.sln")] readonly Solution Solution;
- static Lazy MsBuildExe = new Lazy(() =>
- {
- if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
- return null;
-
- var msBuildDirectory = VSWhere("-latest -nologo -property installationPath -format value -prerelease").FirstOrDefault().Text;
-
- if (!string.IsNullOrWhiteSpace(msBuildDirectory))
- {
- string msBuildExe = Path.Combine(msBuildDirectory, @"MSBuild\Current\Bin\MSBuild.exe");
- if (!System.IO.File.Exists(msBuildExe))
- msBuildExe = Path.Combine(msBuildDirectory, @"MSBuild\15.0\Bin\MSBuild.exe");
-
- return msBuildExe;
- }
-
- return null;
- }, false);
-
BuildParameters Parameters { get; set; }
protected override void OnBuildInitialized()
{
@@ -89,25 +70,28 @@ partial class Build : NukeBuild
}
ExecWait("dotnet version:", "dotnet", "--info");
ExecWait("dotnet workloads:", "dotnet", "workload list");
+ Information("Processor count: " + Environment.ProcessorCount);
+ Information("Available RAM: " + GC.GetGCMemoryInfo().TotalAvailableMemoryBytes / 0x100000 + "MB");
}
- IReadOnlyCollection
-
+
diff --git a/samples/interop/Direct3DInteropSample/App.paml b/samples/interop/Direct3DInteropSample/App.paml
index d9630eef58..e6d77dfaf4 100644
--- a/samples/interop/Direct3DInteropSample/App.paml
+++ b/samples/interop/Direct3DInteropSample/App.paml
@@ -1,6 +1,5 @@
-
-
+
-
\ No newline at end of file
+
diff --git a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
index 81b94b4d09..f9ef4693d5 100644
--- a/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
+++ b/samples/interop/Direct3DInteropSample/Direct3DInteropSample.csproj
@@ -22,7 +22,7 @@
-
+
diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs
index 0c72c389dc..ed5b46d398 100644
--- a/src/Android/Avalonia.Android/AndroidPlatform.cs
+++ b/src/Android/Avalonia.Android/AndroidPlatform.cs
@@ -8,7 +8,8 @@ using Avalonia.Input.Platform;
using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Rendering;
-using Avalonia.Skia;
+using Avalonia.Rendering.Composition;
+using Avalonia.OpenGL;
namespace Avalonia
{
@@ -16,10 +17,8 @@ namespace Avalonia
{
public static T UseAndroid(this T builder) where T : AppBuilderBase, new()
{
- var options = AvaloniaLocator.Current.GetService() ?? new AndroidPlatformOptions();
-
return builder
- .UseWindowingSubsystem(() => AndroidPlatform.Initialize(options), "Android")
+ .UseWindowingSubsystem(() => AndroidPlatform.Initialize(), "Android")
.UseSkia();
}
}
@@ -42,9 +41,11 @@ namespace Avalonia.Android
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500);
- public static void Initialize(AndroidPlatformOptions options)
+ internal static Compositor Compositor { get; private set; }
+
+ public static void Initialize()
{
- Options = options;
+ Options = AvaloniaLocator.Current.GetService() ?? new AndroidPlatformOptions();
AvaloniaLocator.CurrentMutable
.Bind().ToTransient()
@@ -58,16 +59,24 @@ namespace Avalonia.Android
.Bind().ToConstant(new RenderLoop())
.Bind().ToSingleton();
- if (options.UseGpu)
+ if (Options.UseGpu)
{
EglPlatformOpenGlInterface.TryInitialize();
}
+
+ if (Options.UseCompositor)
+ {
+ Compositor = new Compositor(
+ AvaloniaLocator.Current.GetRequiredService(),
+ AvaloniaLocator.Current.GetService());
+ }
}
}
public sealed class AndroidPlatformOptions
{
- public bool UseDeferredRendering { get; set; } = true;
+ public bool UseDeferredRendering { get; set; } = false;
public bool UseGpu { get; set; } = true;
+ public bool UseCompositor { get; set; } = true;
}
}
diff --git a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs
index e72f0aed90..de9149e9a1 100644
--- a/src/Android/Avalonia.Android/AndroidThreadingInterface.cs
+++ b/src/Android/Avalonia.Android/AndroidThreadingInterface.cs
@@ -14,6 +14,7 @@ namespace Avalonia.Android
internal sealed class AndroidThreadingInterface : IPlatformThreadingInterface
{
private Handler _handler;
+ private static Thread s_uiThread;
public AndroidThreadingInterface()
{
@@ -26,46 +27,33 @@ namespace Avalonia.Android
{
if (interval.TotalMilliseconds < 10)
interval = TimeSpan.FromMilliseconds(10);
- object l = new object();
+
var stopped = false;
Timer timer = null;
- var scheduled = false;
timer = new Timer(_ =>
{
- lock (l)
+ if (stopped)
+ return;
+
+ EnsureInvokeOnMainThread(() =>
{
- if (stopped)
+ try
{
- timer.Dispose();
- return;
+ tick();
}
- if (scheduled)
- return;
- scheduled = true;
- EnsureInvokeOnMainThread(() =>
+ finally
{
- try
- {
- tick();
- }
- finally
- {
- lock (l)
- {
- scheduled = false;
- }
- }
- });
- }
- }, null, TimeSpan.Zero, interval);
+ if (!stopped)
+ timer.Change(interval, Timeout.InfiniteTimeSpan);
+ }
+ });
+ },
+ null, interval, Timeout.InfiniteTimeSpan);
return Disposable.Create(() =>
{
- lock (l)
- {
- stopped = true;
- timer.Dispose();
- }
+ stopped = true;
+ timer.Dispose();
});
}
@@ -76,7 +64,25 @@ namespace Avalonia.Android
EnsureInvokeOnMainThread(() => Signaled?.Invoke(null));
}
- public bool CurrentThreadIsLoopThread => Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread());
+ public bool CurrentThreadIsLoopThread
+ {
+ get
+ {
+ if (s_uiThread != null)
+ return s_uiThread == Thread.CurrentThread;
+
+ var isOnMainThread = OperatingSystem.IsAndroidVersionAtLeast(23)
+ ? Looper.MainLooper.IsCurrentThread
+ : Looper.MainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread());
+ if (isOnMainThread)
+ {
+ s_uiThread = Thread.CurrentThread;
+ return true;
+ }
+
+ return false;
+ }
+ }
public event Action Signaled;
}
}
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
index 5343b57251..33501ece06 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/InvalidationAwareSurfaceView.cs
@@ -47,18 +47,6 @@ namespace Avalonia.Android
}
}
- [Obsolete("deprecated")]
- public override void Invalidate(global::Android.Graphics.Rect dirty)
- {
- Invalidate();
- }
-
- [Obsolete("deprecated")]
- public override void Invalidate(int l, int t, int r, int b)
- {
- Invalidate();
- }
-
public void SurfaceChanged(ISurfaceHolder holder, Format format, int width, int height)
{
Log.Info("AVALONIA", "Surface Changed");
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
index fd97e293f9..17a0d6b63a 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+
using Android.Content;
using Android.Graphics;
using Android.Views;
@@ -19,6 +20,7 @@ using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Rendering;
+using Avalonia.Rendering.Composition;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@@ -29,7 +31,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly IFramebufferPlatformSurface _framebuffer;
private readonly AndroidKeyboardEventsHelper _keyboardHelper;
- private readonly AndroidTouchEventsHelper _touchHelper;
+ private readonly AndroidMotionEventsHelper _pointerHelper;
private readonly ITextInputMethodImpl _textInputMethod;
private ViewImpl _view;
@@ -38,8 +40,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_view = new ViewImpl(avaloniaView.Context, this, placeOnTop);
_textInputMethod = new AndroidInputMethod(_view);
_keyboardHelper = new AndroidKeyboardEventsHelper(this);
- _touchHelper = new AndroidTouchEventsHelper(this, () => InputRoot,
- GetAvaloniaPointFromEvent);
+ _pointerHelper = new AndroidMotionEventsHelper(this);
_gl = GlPlatformSurface.TryCreate(this);
_framebuffer = new FramebufferManager(this);
@@ -84,9 +85,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IEnumerable Surfaces => new object[] { _gl, _framebuffer, Handle };
public IRenderer CreateRenderer(IRenderRoot root) =>
- AndroidPlatform.Options.UseDeferredRendering
- ? new DeferredRenderer(root, AvaloniaLocator.Current.GetService()) { RenderOnlyOnRenderThread = true }
- : new ImmediateRenderer(root);
+ AndroidPlatform.Options.UseCompositor
+ ? new CompositingRenderer(root, AndroidPlatform.Compositor)
+ : AndroidPlatform.Options.UseDeferredRendering
+ ? new DeferredRenderer(root, AvaloniaLocator.Current.GetRequiredService()) { RenderOnlyOnRenderThread = true }
+ : new ImmediateRenderer(root);
public virtual void Hide()
{
@@ -157,10 +160,19 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_tl.Draw();
}
+ protected override bool DispatchGenericPointerEvent(MotionEvent e)
+ {
+ bool callBase;
+ bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase);
+ bool baseResult = callBase ? base.DispatchGenericPointerEvent(e) : false;
+
+ return result != null ? result.Value : baseResult;
+ }
+
public override bool DispatchTouchEvent(MotionEvent e)
{
bool callBase;
- bool? result = _tl._touchHelper.DispatchTouchEvent(e, out callBase);
+ bool? result = _tl._pointerHelper.DispatchMotionEvent(e, out callBase);
bool baseResult = callBase ? base.DispatchTouchEvent(e) : false;
return result != null ? result.Value : baseResult;
diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
new file mode 100644
index 0000000000..ce385ebe34
--- /dev/null
+++ b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidMotionEventsHelper.cs
@@ -0,0 +1,251 @@
+using System;
+using System.Collections.Generic;
+
+using Android.Views;
+
+using Avalonia.Android.Platform.SkiaPlatform;
+using Avalonia.Collections.Pooled;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+
+#nullable enable
+
+namespace Avalonia.Android.Platform.Specific.Helpers
+{
+ internal class AndroidMotionEventsHelper : IDisposable
+ {
+ private static readonly PooledList s_intermediatePointsPooledList = new(ClearMode.Never);
+ private static readonly float s_radiansToDegree = (float)(180f * Math.PI);
+ private readonly TouchDevice _touchDevice;
+ private readonly MouseDevice _mouseDevice;
+ private readonly PenDevice _penDevice;
+ private readonly TopLevelImpl _view;
+ private bool _disposed;
+
+ public AndroidMotionEventsHelper(TopLevelImpl view)
+ {
+ _touchDevice = new TouchDevice();
+ _penDevice = new PenDevice();
+ _mouseDevice = new MouseDevice();
+ _view = view;
+ }
+
+ public bool? DispatchMotionEvent(MotionEvent e, out bool callBase)
+ {
+ callBase = true;
+ if (_disposed)
+ {
+ return null;
+ }
+
+ var eventTime = (ulong)DateTime.Now.Millisecond;
+ var inputRoot = _view.InputRoot;
+ var actionMasked = e.ActionMasked;
+ var modifiers = GetModifiers(e.MetaState, e.ButtonState);
+
+ if (actionMasked == MotionEventActions.Move)
+ {
+ for (int index = 0; index < e.PointerCount; index++)
+ {
+ var toolType = e.GetToolType(index);
+ var device = GetDevice(toolType);
+ var eventType = toolType == MotionEventToolType.Finger ? RawPointerEventType.TouchUpdate : RawPointerEventType.Move;
+ var point = CreatePoint(e, index);
+ modifiers |= GetToolModifiers(toolType);
+
+ // ButtonState reports only mouse buttons, but not touch or stylus pointer.
+ if (toolType != MotionEventToolType.Mouse)
+ {
+ modifiers |= RawInputModifiers.LeftMouseButton;
+ }
+
+ var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index))
+ {
+ IntermediatePoints = new Lazy?>(() =>
+ {
+ var site = e.HistorySize;
+ s_intermediatePointsPooledList.Clear();
+ s_intermediatePointsPooledList.Capacity = site;
+
+ for (int pos = 0; pos < site; pos++)
+ {
+ s_intermediatePointsPooledList.Add(CreateHistoricalPoint(e, index, pos));
+ }
+
+ return s_intermediatePointsPooledList;
+ })
+ };
+ _view.Input(args);
+ }
+ }
+ else
+ {
+ var index = e.ActionIndex;
+ var toolType = e.GetToolType(index);
+ var device = GetDevice(toolType);
+ modifiers |= GetToolModifiers(toolType);
+ var point = CreatePoint(e, index);
+
+ if (actionMasked == MotionEventActions.Scroll && toolType == MotionEventToolType.Mouse)
+ {
+ var delta = new Vector(e.GetAxisValue(Axis.Hscroll), e.GetAxisValue(Axis.Vscroll));
+ var args = new RawMouseWheelEventArgs(device, eventTime, inputRoot, point.Position, delta, RawInputModifiers.None);
+ _view.Input(args);
+ }
+ else
+ {
+ var eventType = GetActionType(e, actionMasked, toolType);
+ if (eventType >= 0)
+ {
+ var args = new RawTouchEventArgs(device, eventTime, inputRoot, eventType, point, modifiers, e.GetPointerId(index));
+ _view.Input(args);
+ }
+ }
+ }
+
+ return true;
+ }
+
+ private static RawInputModifiers GetModifiers(MetaKeyStates metaState, MotionEventButtonState buttonState)
+ {
+ var modifiers = RawInputModifiers.None;
+ if (metaState.HasAnyFlag(MetaKeyStates.ShiftOn))
+ {
+ modifiers |= RawInputModifiers.Shift;
+ }
+ if (metaState.HasAnyFlag(MetaKeyStates.CtrlOn))
+ {
+ modifiers |= RawInputModifiers.Control;
+ }
+ if (metaState.HasAnyFlag(MetaKeyStates.AltOn))
+ {
+ modifiers |= RawInputModifiers.Alt;
+ }
+ if (metaState.HasAnyFlag(MetaKeyStates.MetaOn))
+ {
+ modifiers |= RawInputModifiers.Meta;
+ }
+ if (buttonState.HasAnyFlag(MotionEventButtonState.Primary))
+ {
+ modifiers |= RawInputModifiers.LeftMouseButton;
+ }
+ if (buttonState.HasAnyFlag(MotionEventButtonState.Secondary))
+ {
+ modifiers |= RawInputModifiers.RightMouseButton;
+ }
+ if (buttonState.HasAnyFlag(MotionEventButtonState.Tertiary))
+ {
+ modifiers |= RawInputModifiers.MiddleMouseButton;
+ }
+ if (buttonState.HasAnyFlag(MotionEventButtonState.Back))
+ {
+ modifiers |= RawInputModifiers.XButton1MouseButton;
+ }
+ if (buttonState.HasAnyFlag(MotionEventButtonState.Forward))
+ {
+ modifiers |= RawInputModifiers.XButton2MouseButton;
+ }
+ if (buttonState.HasAnyFlag(MotionEventButtonState.StylusPrimary))
+ {
+ modifiers |= RawInputModifiers.PenBarrelButton;
+ }
+ return modifiers;
+ }
+
+#pragma warning disable CA1416 // Validate platform compatibility
+ private static RawPointerEventType GetActionType(MotionEvent e, MotionEventActions actionMasked, MotionEventToolType toolType)
+ {
+ var isTouch = toolType == MotionEventToolType.Finger;
+ var isMouse = toolType == MotionEventToolType.Mouse;
+ switch (actionMasked)
+ {
+ // DOWN
+ case MotionEventActions.Down when !isMouse:
+ case MotionEventActions.PointerDown when !isMouse:
+ return isTouch ? RawPointerEventType.TouchBegin : RawPointerEventType.LeftButtonDown;
+ case MotionEventActions.ButtonPress:
+ return e.ActionButton switch
+ {
+ MotionEventButtonState.Back => RawPointerEventType.XButton1Down,
+ MotionEventButtonState.Forward => RawPointerEventType.XButton2Down,
+ MotionEventButtonState.Primary => RawPointerEventType.LeftButtonDown,
+ MotionEventButtonState.Secondary => RawPointerEventType.RightButtonDown,
+ MotionEventButtonState.StylusPrimary => RawPointerEventType.LeftButtonDown,
+ MotionEventButtonState.StylusSecondary => RawPointerEventType.RightButtonDown,
+ MotionEventButtonState.Tertiary => RawPointerEventType.MiddleButtonDown,
+ _ => RawPointerEventType.LeftButtonDown
+ };
+ // UP
+ case MotionEventActions.Up when !isMouse:
+ case MotionEventActions.PointerUp when !isMouse:
+ return isTouch ? RawPointerEventType.TouchEnd : RawPointerEventType.LeftButtonUp;
+ case MotionEventActions.ButtonRelease:
+ return e.ActionButton switch
+ {
+ MotionEventButtonState.Back => RawPointerEventType.XButton1Up,
+ MotionEventButtonState.Forward => RawPointerEventType.XButton2Up,
+ MotionEventButtonState.Primary => RawPointerEventType.LeftButtonUp,
+ MotionEventButtonState.Secondary => RawPointerEventType.RightButtonUp,
+ MotionEventButtonState.StylusPrimary => RawPointerEventType.LeftButtonUp,
+ MotionEventButtonState.StylusSecondary => RawPointerEventType.RightButtonUp,
+ MotionEventButtonState.Tertiary => RawPointerEventType.MiddleButtonUp,
+ _ => RawPointerEventType.LeftButtonUp
+ };
+ // MOVE
+ case MotionEventActions.Outside:
+ case MotionEventActions.HoverMove:
+ case MotionEventActions.Move:
+ return isTouch ? RawPointerEventType.TouchUpdate : RawPointerEventType.Move;
+ // CANCEL
+ case MotionEventActions.Cancel:
+ return isTouch ? RawPointerEventType.TouchCancel : RawPointerEventType.LeaveWindow;
+ default:
+ return (RawPointerEventType)(-1);
+ }
+ }
+#pragma warning restore CA1416 // Validate platform compatibility
+
+ private IPointerDevice GetDevice(MotionEventToolType type)
+ {
+ return type switch
+ {
+ MotionEventToolType.Mouse => _mouseDevice,
+ MotionEventToolType.Stylus => _penDevice,
+ MotionEventToolType.Eraser => _penDevice,
+ MotionEventToolType.Finger => _touchDevice,
+ _ => _touchDevice
+ };
+ }
+
+ private RawPointerPoint CreatePoint(MotionEvent e, int index)
+ {
+ return new RawPointerPoint
+ {
+ Position = new Point(e.GetX(index), e.GetY(index)) / _view.RenderScaling,
+ Pressure = Math.Min(e.GetPressure(index), 1), // android pressure can depend on the device, can be mixed up with "GetSize", may be larger than 1.0f on some devices
+ Twist = e.GetOrientation(index) * s_radiansToDegree
+ };
+ }
+
+ private RawPointerPoint CreateHistoricalPoint(MotionEvent e, int index, int pos)
+ {
+ return new RawPointerPoint
+ {
+ Position = new Point(e.GetHistoricalX(index, pos), e.GetHistoricalY(index, pos)) / _view.RenderScaling,
+ Pressure = Math.Min(e.GetHistoricalPressure(index, pos), 1),
+ Twist = e.GetHistoricalOrientation(index, pos) * s_radiansToDegree
+ };
+ }
+
+ private static RawInputModifiers GetToolModifiers(MotionEventToolType toolType)
+ {
+ // Android "Eraser" indicates Inverted pen OR actual Eraser. So we have to go both here.
+ return toolType == MotionEventToolType.Eraser ? RawInputModifiers.PenInverted | RawInputModifiers.PenEraser : RawInputModifiers.None;
+ }
+
+ public void Dispose()
+ {
+ _disposed = true;
+ }
+ }
+}
diff --git a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs b/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
deleted file mode 100644
index 6142598514..0000000000
--- a/src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidTouchEventsHelper.cs
+++ /dev/null
@@ -1,85 +0,0 @@
-using System;
-using Android.Views;
-using Avalonia.Input;
-using Avalonia.Input.Raw;
-using Avalonia.Platform;
-
-namespace Avalonia.Android.Platform.Specific.Helpers
-{
- public class AndroidTouchEventsHelper : IDisposable where TView : ITopLevelImpl, IAndroidView
- {
- private TView _view;
- public bool HandleEvents { get; set; }
-
- public AndroidTouchEventsHelper(TView view, Func getInputRoot, Func getPointfunc)
- {
- this._view = view;
- HandleEvents = true;
- _getPointFunc = getPointfunc;
- _getInputRoot = getInputRoot;
- }
-
- private TouchDevice _touchDevice = new TouchDevice();
- private Func _getPointFunc;
- private Func _getInputRoot;
-
- public bool? DispatchTouchEvent(MotionEvent e, out bool callBase)
- {
- if (!HandleEvents)
- {
- callBase = true;
- return null;
- }
-
- var eventTime = DateTime.Now;
-
- //Basic touch support
- var pointerEventType = e.Action switch
- {
- MotionEventActions.Down => RawPointerEventType.TouchBegin,
- MotionEventActions.Up => RawPointerEventType.TouchEnd,
- MotionEventActions.Cancel => RawPointerEventType.TouchCancel,
- _ => RawPointerEventType.TouchUpdate
- };
-
- if (e.Action.HasFlag(MotionEventActions.PointerDown))
- {
- pointerEventType = RawPointerEventType.TouchBegin;
- }
-
- if (e.Action.HasFlag(MotionEventActions.PointerUp))
- {
- pointerEventType = RawPointerEventType.TouchEnd;
- }
-
- for (int i = 0; i < e.PointerCount; i++)
- {
- //if point is in view otherwise it's possible avalonia not to find the proper window to dispatch the event
- var point = _getPointFunc(e, i);
-
- double x = _view.View.GetX();
- double y = _view.View.GetY();
- double r = x + _view.View.Width;
- double b = y + _view.View.Height;
-
- if (x <= point.X && r >= point.X && y <= point.Y && b >= point.Y)
- {
- var inputRoot = _getInputRoot();
-
- var mouseEvent = new RawTouchEventArgs(_touchDevice, (uint)eventTime.Ticks, inputRoot,
- i == e.ActionIndex ? pointerEventType : RawPointerEventType.TouchUpdate, point, RawInputModifiers.None, e.GetPointerId(i));
- _view.Input(mouseEvent);
- }
- }
-
- callBase = true;
- //if return false events for move and up are not received!!!
- return e.Action != MotionEventActions.Up;
- }
-
- public void Dispose()
- {
- HandleEvents = false;
- }
- }
-}
diff --git a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
index 50581d47b1..a9b2e16d43 100644
--- a/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
+++ b/src/Android/Avalonia.Android/Platform/Storage/AndroidStorageItem.cs
@@ -1,6 +1,7 @@
#nullable enable
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Linq;
@@ -35,13 +36,13 @@ internal abstract class AndroidStorageItem : IStorageBookmarkItem
public bool CanBookmark => true;
- public Task SaveBookmark()
+ public Task SaveBookmarkAsync()
{
Context.ContentResolver?.TakePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
return Task.FromResult(Uri.ToString());
}
- public Task ReleaseBookmark()
+ public Task ReleaseBookmarkAsync()
{
Context.ContentResolver?.ReleasePersistableUriPermission(Uri, ActivityFlags.GrantWriteUriPermission | ActivityFlags.GrantReadUriPermission);
return Task.CompletedTask;
@@ -106,6 +107,30 @@ internal sealed class AndroidStorageFolder : AndroidStorageItem, IStorageBookmar
{
return Task.FromResult(new StorageItemProperties());
}
+
+ public async Task> GetItemsAsync()
+ {
+ using var javaFile = new JavaFile(Uri.Path!);
+
+ // Java file represents files AND directories. Don't be confused.
+ var files = await javaFile.ListFilesAsync().ConfigureAwait(false);
+ if (files is null)
+ {
+ return Array.Empty();
+ }
+
+ return files
+ .Select(f => (file: f, uri: AndroidUri.FromFile(f)))
+ .Where(t => t.uri is not null)
+ .Select(t => t.file switch
+ {
+ { IsFile: true } => (IStorageItem)new AndroidStorageFile(Context, t.uri!),
+ { IsDirectory: true } => new AndroidStorageFolder(Context, t.uri!),
+ _ => null
+ })
+ .Where(i => i is not null)
+ .ToArray()!;
+ }
}
internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkFile
@@ -118,10 +143,10 @@ internal sealed class AndroidStorageFile : AndroidStorageItem, IStorageBookmarkF
public bool CanOpenWrite => true;
- public Task OpenRead() => Task.FromResult(OpenContentStream(Context, Uri, false)
+ public Task OpenReadAsync() => Task.FromResult(OpenContentStream(Context, Uri, false)
?? throw new InvalidOperationException("Failed to open content stream"));
- public Task OpenWrite() => Task.FromResult(OpenContentStream(Context, Uri, true)
+ public Task OpenWriteAsync() => Task.FromResult(OpenContentStream(Context, Uri, true)
?? throw new InvalidOperationException("Failed to open content stream"));
private Stream? OpenContentStream(Context context, AndroidUri uri, bool isOutput)
diff --git a/src/Avalonia.Base/Animation/Animation.cs b/src/Avalonia.Base/Animation/Animation.cs
index 03b2d17e44..e0883901fd 100644
--- a/src/Avalonia.Base/Animation/Animation.cs
+++ b/src/Avalonia.Base/Animation/Animation.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
@@ -172,23 +173,6 @@ namespace Avalonia.Animation
set { SetAndRaise(SpeedRatioProperty, ref _speedRatio, value); }
}
- ///
- /// Obsolete: Do not use this property, use instead.
- ///
- ///
- [Obsolete("This property has been superceded by IterationCount.")]
- public string RepeatCount
- {
- get { return IterationCount.ToString(); }
- set
- {
- var val = value.ToUpper();
- val = val.Replace("LOOP", "INFINITE");
- val = val.Replace("NONE", "1");
- IterationCount = IterationCount.Parse(val);
- }
- }
-
///
/// Gets the children of the .
///
@@ -196,14 +180,14 @@ namespace Avalonia.Animation
public KeyFrames Children { get; } = new KeyFrames();
// Store values for the Animator attached properties for IAnimationSetter objects.
- private static readonly Dictionary s_animators = new Dictionary();
+ private static readonly Dictionary Factory)> s_animators = new();
///
/// Gets the value of the Animator attached property for a setter.
///
/// The animation setter.
/// The property animator type.
- public static Type? GetAnimator(IAnimationSetter setter)
+ public static (Type Type, Func Factory)? GetAnimator(IAnimationSetter setter)
{
if (s_animators.TryGetValue(setter, out var type))
{
@@ -217,24 +201,28 @@ namespace Avalonia.Animation
///
/// The animation setter.
/// The property animator value.
- public static void SetAnimator(IAnimationSetter setter, Type value)
+ public static void SetAnimator(IAnimationSetter setter,
+#if NET6_0_OR_GREATER
+ [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor | DynamicallyAccessedMemberTypes.PublicMethods)]
+#endif
+ Type value)
{
- s_animators[setter] = value;
+ s_animators[setter] = (value, () => (IAnimator)Activator.CreateInstance(value)!);
}
- private readonly static List<(Func Condition, Type Animator)> Animators = new List<(Func, Type)>
+ private readonly static List<(Func Condition, Type Animator, Func Factory)> Animators = new()
{
- ( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator) ),
- ( prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator) ),
- ( prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator) ),
- ( prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator) ),
- ( prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator) ),
- ( prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator) ),
- ( prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator) ),
- ( prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator) ),
- ( prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator) ),
- ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator) ),
- ( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator) ),
+ ( prop => typeof(bool).IsAssignableFrom(prop.PropertyType), typeof(BoolAnimator), () => new BoolAnimator() ),
+ ( prop => typeof(byte).IsAssignableFrom(prop.PropertyType), typeof(ByteAnimator), () => new ByteAnimator() ),
+ ( prop => typeof(Int16).IsAssignableFrom(prop.PropertyType), typeof(Int16Animator), () => new Int16Animator() ),
+ ( prop => typeof(Int32).IsAssignableFrom(prop.PropertyType), typeof(Int32Animator), () => new Int32Animator() ),
+ ( prop => typeof(Int64).IsAssignableFrom(prop.PropertyType), typeof(Int64Animator), () => new Int64Animator() ),
+ ( prop => typeof(UInt16).IsAssignableFrom(prop.PropertyType), typeof(UInt16Animator), () => new UInt16Animator() ),
+ ( prop => typeof(UInt32).IsAssignableFrom(prop.PropertyType), typeof(UInt32Animator), () => new UInt32Animator() ),
+ ( prop => typeof(UInt64).IsAssignableFrom(prop.PropertyType), typeof(UInt64Animator), () => new UInt64Animator() ),
+ ( prop => typeof(float).IsAssignableFrom(prop.PropertyType), typeof(FloatAnimator), () => new FloatAnimator() ),
+ ( prop => typeof(double).IsAssignableFrom(prop.PropertyType), typeof(DoubleAnimator), () => new DoubleAnimator() ),
+ ( prop => typeof(decimal).IsAssignableFrom(prop.PropertyType), typeof(DecimalAnimator), () => new DecimalAnimator() ),
};
///
@@ -249,18 +237,18 @@ namespace Avalonia.Animation
/// The type of the animator to instantiate.
///
public static void RegisterAnimator(Func condition)
- where TAnimator : IAnimator
+ where TAnimator : IAnimator, new()
{
- Animators.Insert(0, (condition, typeof(TAnimator)));
+ Animators.Insert(0, (condition, typeof(TAnimator), () => new TAnimator()));
}
- private static Type? GetAnimatorType(AvaloniaProperty property)
+ private static (Type Type, Func Factory)? GetAnimatorType(AvaloniaProperty property)
{
- foreach (var (condition, type) in Animators)
+ foreach (var (condition, type, factory) in Animators)
{
if (condition(property))
{
- return type;
+ return (type, factory);
}
}
return null;
@@ -268,7 +256,7 @@ namespace Avalonia.Animation
private (IList Animators, IList subscriptions) InterpretKeyframes(Animatable control)
{
- var handlerList = new List<(Type type, AvaloniaProperty property)>();
+ var handlerList = new Dictionary<(Type type, AvaloniaProperty Property), Func>();
var animatorKeyFrames = new List();
var subscriptions = new List();
@@ -288,8 +276,10 @@ namespace Avalonia.Animation
throw new InvalidOperationException($"No animator registered for the property {setter.Property}. Add an animator to the Animation.Animators collection that matches this property to animate it.");
}
- if (!handlerList.Contains((handler, setter.Property)))
- handlerList.Add((handler, setter.Property));
+ var (type, factory) = handler.Value;
+
+ if (!handlerList.ContainsKey((type, setter.Property)))
+ handlerList[(type, setter.Property)] = factory;
var cue = keyframe.Cue;
@@ -298,7 +288,7 @@ namespace Avalonia.Animation
cue = new Cue(keyframe.KeyTime.TotalSeconds / Duration.TotalSeconds);
}
- var newKF = new AnimatorKeyFrame(handler, cue, keyframe.KeySpline);
+ var newKF = new AnimatorKeyFrame(type, factory, cue, keyframe.KeySpline);
subscriptions.Add(newKF.BindSetter(setter, control));
@@ -308,10 +298,10 @@ namespace Avalonia.Animation
var newAnimatorInstances = new List();
- foreach (var (handlerType, property) in handlerList)
+ foreach (var handler in handlerList)
{
- var newInstance = (IAnimator)Activator.CreateInstance(handlerType)!;
- newInstance.Property = property;
+ var newInstance = handler.Value();
+ newInstance.Property = handler.Key.Property;
newAnimatorInstances.Add(newInstance);
}
diff --git a/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs b/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs
index 8af31f2948..0356723f00 100644
--- a/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs
+++ b/src/Avalonia.Base/Animation/AnimatorKeyFrame.cs
@@ -20,22 +20,25 @@ namespace Avalonia.Animation
}
- public AnimatorKeyFrame(Type? animatorType, Cue cue)
+ public AnimatorKeyFrame(Type? animatorType, Func? animatorFactory, Cue cue)
{
AnimatorType = animatorType;
+ AnimatorFactory = animatorFactory;
Cue = cue;
KeySpline = null;
}
- public AnimatorKeyFrame(Type? animatorType, Cue cue, KeySpline? keySpline)
+ public AnimatorKeyFrame(Type? animatorType, Func? animatorFactory, Cue cue, KeySpline? keySpline)
{
AnimatorType = animatorType;
+ AnimatorFactory = animatorFactory;
Cue = cue;
KeySpline = keySpline;
}
internal bool isNeutral;
public Type? AnimatorType { get; }
+ public Func? AnimatorFactory { get; }
public Cue Cue { get; }
public KeySpline? KeySpline { get; }
public AvaloniaProperty? Property { get; private set; }
diff --git a/src/Avalonia.Base/Animation/Animators/Animator`1.cs b/src/Avalonia.Base/Animation/Animators/Animator`1.cs
index 248ca61c1d..8765cfb4c9 100644
--- a/src/Avalonia.Base/Animation/Animators/Animator`1.cs
+++ b/src/Avalonia.Base/Animation/Animators/Animator`1.cs
@@ -171,12 +171,12 @@ namespace Avalonia.Animation.Animators
{
if (!hasStartKey)
{
- _convertedKeyframes.Insert(0, new AnimatorKeyFrame(null, new Cue(0.0d)) { Value = default(T), isNeutral = true });
+ _convertedKeyframes.Insert(0, new AnimatorKeyFrame(null, null, new Cue(0.0d)) { Value = default(T), isNeutral = true });
}
if (!hasEndKey)
{
- _convertedKeyframes.Add(new AnimatorKeyFrame(null, new Cue(1.0d)) { Value = default(T), isNeutral = true });
+ _convertedKeyframes.Add(new AnimatorKeyFrame(null, null, new Cue(1.0d)) { Value = default(T), isNeutral = true });
}
}
}
diff --git a/src/Avalonia.Base/Animation/Animators/BaseBrushAnimator.cs b/src/Avalonia.Base/Animation/Animators/BaseBrushAnimator.cs
index 5f22254fb5..07c147ef6d 100644
--- a/src/Avalonia.Base/Animation/Animators/BaseBrushAnimator.cs
+++ b/src/Avalonia.Base/Animation/Animators/BaseBrushAnimator.cs
@@ -17,8 +17,7 @@ namespace Avalonia.Animation.Animators
///
public class BaseBrushAnimator : Animator
{
- private static readonly List<(Func Match, Type AnimatorType)> _brushAnimators =
- new List<(Func Match, Type AnimatorType)>();
+ private static readonly List<(Func Match, Type AnimatorType, Func AnimatorFactory)> _brushAnimators = new();
///
/// Register an that handles a specific
@@ -34,7 +33,7 @@ namespace Avalonia.Animation.Animators
public static void RegisterBrushAnimator(Func condition)
where TAnimator : IAnimator, new()
{
- _brushAnimators.Insert(0, (condition, typeof(TAnimator)));
+ _brushAnimators.Insert(0, (condition, typeof(TAnimator), () => new TAnimator()));
}
///
@@ -85,14 +84,14 @@ namespace Avalonia.Animation.Animators
{
if (keyframe.Value is ISolidColorBrush solidColorBrush)
{
- gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
+ gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), () => new GradientBrushAnimator(), keyframe.Cue, keyframe.KeySpline)
{
Value = GradientBrushAnimator.ConvertSolidColorBrushToGradient(firstGradient, solidColorBrush)
});
}
else if (keyframe.Value is IGradientBrush)
{
- gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), keyframe.Cue, keyframe.KeySpline)
+ gradientAnimator.Add(new AnimatorKeyFrame(typeof(GradientBrushAnimator), () => new GradientBrushAnimator(), keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
@@ -117,7 +116,7 @@ namespace Avalonia.Animation.Animators
{
if (keyframe.Value is ISolidColorBrush)
{
- solidColorBrushAnimator.Add(new AnimatorKeyFrame(typeof(ISolidColorBrushAnimator), keyframe.Cue, keyframe.KeySpline)
+ solidColorBrushAnimator.Add(new AnimatorKeyFrame(typeof(ISolidColorBrushAnimator), () => new ISolidColorBrushAnimator(), keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
@@ -137,18 +136,18 @@ namespace Avalonia.Animation.Animators
{
if (_brushAnimators.Count > 0 && this[0].Value?.GetType() is Type firstKeyType)
{
- foreach (var (match, animatorType) in _brushAnimators)
+ foreach (var (match, animatorType, animatorFactory) in _brushAnimators)
{
if (!match(firstKeyType))
continue;
- animator = (IAnimator?)Activator.CreateInstance(animatorType);
+ animator = animatorFactory();
if (animator != null)
{
animator.Property = Property;
foreach (var keyframe in this)
{
- animator.Add(new AnimatorKeyFrame(animatorType, keyframe.Cue, keyframe.KeySpline)
+ animator.Add(new AnimatorKeyFrame(animatorType, animatorFactory, keyframe.Cue, keyframe.KeySpline)
{
Value = keyframe.Value
});
diff --git a/src/Avalonia.Base/Animation/Animators/SolidColorBrushAnimator.cs b/src/Avalonia.Base/Animation/Animators/SolidColorBrushAnimator.cs
index b87b2681d6..9256c7be5e 100644
--- a/src/Avalonia.Base/Animation/Animators/SolidColorBrushAnimator.cs
+++ b/src/Avalonia.Base/Animation/Animators/SolidColorBrushAnimator.cs
@@ -37,17 +37,4 @@ namespace Avalonia.Animation.Animators
}
}
- [Obsolete("Use ISolidColorBrushAnimator instead")]
- public class SolidColorBrushAnimator : Animator
- {
- public override SolidColorBrush? Interpolate(double progress, SolidColorBrush? oldValue, SolidColorBrush? newValue)
- {
- if (oldValue is null || newValue is null)
- {
- return progress >= 0.5 ? newValue : oldValue;
- }
-
- return new SolidColorBrush(ColorAnimator.InterpolateCore(progress, oldValue.Color, newValue.Color));
- }
- }
}
diff --git a/src/Avalonia.Base/Avalonia.Base.csproj b/src/Avalonia.Base/Avalonia.Base.csproj
index 15feed388b..d091b9072d 100644
--- a/src/Avalonia.Base/Avalonia.Base.csproj
+++ b/src/Avalonia.Base/Avalonia.Base.csproj
@@ -6,6 +6,7 @@
True
true
$(BaseIntermediateOutputPath)\GeneratedFiles
+ true
diff --git a/src/Avalonia.Base/AvaloniaObjectExtensions.cs b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
index 134e3b2ac7..2d7bab6cd6 100644
--- a/src/Avalonia.Base/AvaloniaObjectExtensions.cs
+++ b/src/Avalonia.Base/AvaloniaObjectExtensions.cs
@@ -468,41 +468,6 @@ namespace Avalonia
});
}
- ///
- /// Subscribes to a property changed notifications for changes that originate from a
- /// .
- ///
- /// The type of the property change sender.
- /// The property changed observable.
- /// Given a TTarget, returns the handler.
- /// A disposable that can be used to terminate the subscription.
- [Obsolete("Use overload taking Action.")]
- public static IDisposable AddClassHandler(
- this IObservable observable,
- Func> handler)
- where TTarget : class
- {
- return observable.Subscribe(e => SubscribeAdapter(e, handler));
- }
-
- ///
- /// Observer method for .
- ///
- /// The sender type to accept.
- /// The event args.
- /// Given a TTarget, returns the handler.
- private static void SubscribeAdapter(
- AvaloniaPropertyChangedEventArgs e,
- Func> handler)
- where TTarget : class
- {
- if (e.Sender is TTarget target)
- {
- handler(target)(e);
- }
- }
-
private class BindingAdaptor : IBinding
{
private IObservable _source;
diff --git a/src/Avalonia.Base/AvaloniaProperty`1.cs b/src/Avalonia.Base/AvaloniaProperty`1.cs
index 3937ee5658..14acc2c47f 100644
--- a/src/Avalonia.Base/AvaloniaProperty`1.cs
+++ b/src/Avalonia.Base/AvaloniaProperty`1.cs
@@ -30,21 +30,6 @@ namespace Avalonia
_changed = new Subject>();
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// The property to copy.
- /// The new owner type.
- /// Optional overridden metadata.
- [Obsolete("Use constructor with AvaloniaProperty instead.", true)]
- protected AvaloniaProperty(
- AvaloniaProperty source,
- Type ownerType,
- AvaloniaPropertyMetadata? metadata)
- : this(source as AvaloniaProperty ?? throw new InvalidOperationException(), ownerType, metadata)
- {
- }
-
///
/// Initializes a new instance of the class.
///
diff --git a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
index fb70729cf8..c4684960d6 100644
--- a/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaListExtensions.cs
@@ -140,21 +140,6 @@ namespace Avalonia.Collections
}
}
- [Obsolete("Causes memory leaks. Use DynamicData or similar instead.")]
- public static IAvaloniaReadOnlyList CreateDerivedList(
- this IAvaloniaReadOnlyList collection,
- Func select)
- {
- var result = new AvaloniaList();
-
- collection.ForEachItem(
- (i, item) => result.Insert(i, select(item)),
- (i, item) => result.RemoveAt(i),
- () => result.Clear());
-
- return result;
- }
-
///
/// Listens for property changed events from all items in a collection.
///
diff --git a/src/Avalonia.Base/Controls/ResourceDictionary.cs b/src/Avalonia.Base/Controls/ResourceDictionary.cs
index 77863e5101..d6197c50c6 100644
--- a/src/Avalonia.Base/Controls/ResourceDictionary.cs
+++ b/src/Avalonia.Base/Controls/ResourceDictionary.cs
@@ -3,6 +3,7 @@ using System.Collections;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Collections;
+using Avalonia.Controls.Templates;
namespace Avalonia.Controls
{
@@ -29,7 +30,11 @@ namespace Avalonia.Controls
public object? this[object key]
{
- get => _inner?[key];
+ get
+ {
+ TryGetValue(key, out var value);
+ return value;
+ }
set
{
Inner[key] = value;
@@ -119,6 +124,12 @@ namespace Avalonia.Controls
Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
}
+ public void AddDeferred(object key, Func factory)
+ {
+ Inner.Add(key, new DeferredItem(factory));
+ Owner?.NotifyHostedResourcesChanged(ResourcesChangedEventArgs.Empty);
+ }
+
public void Clear()
{
if (_inner?.Count > 0)
@@ -143,10 +154,8 @@ namespace Avalonia.Controls
public bool TryGetResource(object key, out object? value)
{
- if (_inner is not null && _inner.TryGetValue(key, out value))
- {
+ if (TryGetValue(key, out value))
return true;
- }
if (_mergedDictionaries != null)
{
@@ -165,12 +174,28 @@ namespace Avalonia.Controls
public bool TryGetValue(object key, out object? value)
{
- if (_inner is not null)
- return _inner.TryGetValue(key, out value);
+ if (_inner is not null && _inner.TryGetValue(key, out value))
+ {
+ if (value is DeferredItem deffered)
+ {
+ _inner[key] = value = deffered.Factory(null) switch
+ {
+ ITemplateResult t => t.Result,
+ object v => v,
+ _ => null,
+ };
+ }
+ return true;
+ }
+
value = null;
return false;
}
+ public IEnumerator> GetEnumerator()
+ {
+ return _inner?.GetEnumerator() ?? Enumerable.Empty>().GetEnumerator();
+ }
void ICollection>.Add(KeyValuePair item)
{
@@ -198,12 +223,17 @@ namespace Avalonia.Controls
return false;
}
- public IEnumerator> GetEnumerator()
+ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+
+ internal bool ContainsDeferredKey(object key)
{
- return _inner?.GetEnumerator() ?? Enumerable.Empty>().GetEnumerator();
- }
+ if (_inner is not null && _inner.TryGetValue(key, out var result))
+ {
+ return result is DeferredItem;
+ }
- IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
+ return false;
+ }
void IResourceProvider.AddOwner(IResourceHost owner)
{
@@ -258,5 +288,11 @@ namespace Avalonia.Controls
}
}
}
+
+ private class DeferredItem
+ {
+ public DeferredItem(Func factory) => Factory = factory;
+ public Func Factory { get; }
+ }
}
}
diff --git a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
index 1758c45650..6121646107 100644
--- a/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
+++ b/src/Avalonia.Base/Controls/ResourceNodeExtensions.cs
@@ -132,6 +132,11 @@ namespace Avalonia.Controls
{
_target.OwnerChanged += OwnerChanged;
_owner = _target.Owner;
+
+ if (_owner is object)
+ {
+ _owner.ResourcesChanged += ResourcesChanged;
+ }
}
protected override void Deinitialize()
diff --git a/src/Avalonia.Base/Controls/Templates/ITemplateResult.cs b/src/Avalonia.Base/Controls/Templates/ITemplateResult.cs
new file mode 100644
index 0000000000..6bd4d735a7
--- /dev/null
+++ b/src/Avalonia.Base/Controls/Templates/ITemplateResult.cs
@@ -0,0 +1,8 @@
+namespace Avalonia.Controls.Templates
+{
+ public interface ITemplateResult
+ {
+ public object? Result { get; }
+ public INameScope NameScope { get; }
+ }
+}
diff --git a/src/Avalonia.Controls/Templates/TemplateResult.cs b/src/Avalonia.Base/Controls/Templates/TemplateResult.cs
similarity index 80%
rename from src/Avalonia.Controls/Templates/TemplateResult.cs
rename to src/Avalonia.Base/Controls/Templates/TemplateResult.cs
index 770aecc329..0e38c6c0ce 100644
--- a/src/Avalonia.Controls/Templates/TemplateResult.cs
+++ b/src/Avalonia.Base/Controls/Templates/TemplateResult.cs
@@ -1,9 +1,10 @@
namespace Avalonia.Controls.Templates
{
- public class TemplateResult
+ public class TemplateResult : ITemplateResult
{
public T Result { get; }
public INameScope NameScope { get; }
+ object? ITemplateResult.Result => Result;
public TemplateResult(T result, INameScope nameScope)
{
diff --git a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
index 1ca70140ec..0d51a6ed36 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/MethodAccessorPlugin.cs
@@ -55,13 +55,20 @@ namespace Avalonia.Data.Core.Plugins
var methods = type.GetMethods(bindingFlags);
- foreach (MethodInfo methodInfo in methods)
+ foreach (var methodInfo in methods)
{
if (methodInfo.Name == methodName)
{
- found = methodInfo;
-
- break;
+ var parameters = methodInfo.GetParameters();
+ if (parameters.Length == 1 && parameters[0].ParameterType == typeof(object))
+ {
+ found = methodInfo;
+ break;
+ }
+ else if (parameters.Length == 0)
+ {
+ found = methodInfo;
+ }
}
}
diff --git a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
index 0288dbce91..6232fa39a1 100644
--- a/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
+++ b/src/Avalonia.Base/Data/Core/Plugins/ObservableStreamPlugin.cs
@@ -42,7 +42,7 @@ namespace Avalonia.Data.Core.Plugins
if (target is IObservable 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`.
diff --git a/src/Avalonia.Base/DirectPropertyBase.cs b/src/Avalonia.Base/DirectPropertyBase.cs
index efcb7dfecb..86e4bffaa8 100644
--- a/src/Avalonia.Base/DirectPropertyBase.cs
+++ b/src/Avalonia.Base/DirectPropertyBase.cs
@@ -29,21 +29,6 @@ namespace Avalonia
{
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// The property to copy.
- /// The new owner type.
- /// Optional overridden metadata.
- [Obsolete("Use constructor with DirectPropertyBase instead.", true)]
- protected DirectPropertyBase(
- AvaloniaProperty source,
- Type ownerType,
- AvaloniaPropertyMetadata metadata)
- : this(source as DirectPropertyBase ?? throw new InvalidOperationException(), ownerType, metadata)
- {
- }
-
///
/// Initializes a new instance of the class.
///
diff --git a/src/Avalonia.Base/EnumExtensions.cs b/src/Avalonia.Base/EnumExtensions.cs
index 19eb42a700..9b74266b09 100644
--- a/src/Avalonia.Base/EnumExtensions.cs
+++ b/src/Avalonia.Base/EnumExtensions.cs
@@ -8,10 +8,6 @@ namespace Avalonia
///
public static class EnumExtensions
{
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- [Obsolete("This method is obsolete. Use HasAllFlags instead.")]
- public static bool HasFlagCustom(this T value, T flag) where T : unmanaged, Enum
- => value.HasAllFlags(flag);
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool HasAllFlags(this T value, T flags) where T : unmanaged, Enum
diff --git a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
index 889b7e3b82..4f5585ab6c 100644
--- a/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
+++ b/src/Avalonia.Base/Input/GestureRecognizers/ScrollGestureRecognizer.cs
@@ -71,7 +71,7 @@ namespace Avalonia.Input.GestureRecognizers
{
EndGesture();
_tracking = e.Pointer;
- _gestureId = ScrollGestureEventArgs.GetNextFreeId();;
+ _gestureId = ScrollGestureEventArgs.GetNextFreeId();
_trackedRootPoint = e.GetPosition(_target);
}
}
diff --git a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs
index a95d66346a..2ebf01bcf6 100644
--- a/src/Avalonia.Base/Input/PointerOverPreProcessor.cs
+++ b/src/Avalonia.Base/Input/PointerOverPreProcessor.cs
@@ -158,6 +158,8 @@ namespace Avalonia.Input
ClearPointerOver(pointer, root, timestamp, position, properties, inputModifiers);
}
}
+
+ _lastPointer = (pointer, root.PointToScreen(position));
}
private void SetPointerOverToElement(IPointer pointer, IInputRoot root, IInputElement element,
@@ -195,7 +197,6 @@ namespace Avalonia.Input
}
el = root.PointerOverElement = element;
- _lastPointer = (pointer, root.PointToScreen(position));
e.RoutedEvent = InputElement.PointerEnteredEvent;
diff --git a/src/Avalonia.Base/Input/PointerPoint.cs b/src/Avalonia.Base/Input/PointerPoint.cs
index 71145b5cb0..c51f286053 100644
--- a/src/Avalonia.Base/Input/PointerPoint.cs
+++ b/src/Avalonia.Base/Input/PointerPoint.cs
@@ -5,7 +5,7 @@ namespace Avalonia.Input
///
/// Provides basic properties for the input pointer associated with a single mouse, pen/stylus, or touch contact.
///
- public sealed class PointerPoint
+ public struct PointerPoint
{
public PointerPoint(IPointer pointer, Point position, PointerPointProperties properties)
{
@@ -33,47 +33,47 @@ namespace Avalonia.Input
///
/// Provides extended properties for a PointerPoint object.
///
- public sealed class PointerPointProperties
+ public struct PointerPointProperties
{
///
/// Gets a value that indicates whether the pointer input was triggered by the primary action mode of an input device.
///
- public bool IsLeftButtonPressed { get; }
+ public bool IsLeftButtonPressed { get; } = false;
///
/// Gets a value that indicates whether the pointer input was triggered by the tertiary action mode of an input device.
///
- public bool IsMiddleButtonPressed { get; }
+ public bool IsMiddleButtonPressed { get; } = false;
///
/// Gets a value that indicates whether the pointer input was triggered by the secondary action mode (if supported) of an input device.
///
- public bool IsRightButtonPressed { get; }
+ public bool IsRightButtonPressed { get; } = false;
///
/// Gets a value that indicates whether the pointer input was triggered by the first extended mouse button (XButton1).
///
- public bool IsXButton1Pressed { get; }
+ public bool IsXButton1Pressed { get; } = false;
///
/// Gets a value that indicates whether the pointer input was triggered by the second extended mouse button (XButton2).
///
- public bool IsXButton2Pressed { get; }
+ public bool IsXButton2Pressed { get; } = false;
///
/// Gets a value that indicates whether the barrel button of the pen/stylus device is pressed.
///
- public bool IsBarrelButtonPressed { get; }
+ public bool IsBarrelButtonPressed { get; } = false;
///
/// Gets a value that indicates whether the input is from a pen eraser.
///
- public bool IsEraser { get; }
+ public bool IsEraser { get; } = false;
///
/// Gets a value that indicates whether the digitizer pen is inverted.
///
- public bool IsInverted { get; }
+ public bool IsInverted { get; } = false;
///
/// Gets the clockwise rotation in degrees of a pen device around its own major axis (such as when the user spins the pen in their fingers).
@@ -81,7 +81,7 @@ namespace Avalonia.Input
///
/// A value between 0.0 and 359.0 in degrees of rotation. The default value is 0.0.
///
- public float Twist { get; }
+ public float Twist { get; } = 0.0F;
///
/// Gets a value that indicates the force that the pointer device (typically a pen/stylus) exerts on the surface of the digitizer.
@@ -97,7 +97,7 @@ namespace Avalonia.Input
///
/// The value is 0.0 when the finger or pen is perpendicular to the digitizer surface, between 0.0 and 90.0 when tilted to the right of perpendicular, and between 0.0 and -90.0 when tilted to the left of perpendicular. The default value is 0.0.
///
- public float XTilt { get; }
+ public float XTilt { get; } = 0.0F;
///
/// Gets the plane angle between the X-Z plane and the plane that contains the X axis and the axis of the input device (typically a pen/stylus).
@@ -105,14 +105,14 @@ namespace Avalonia.Input
///
/// The value is 0.0 when the finger or pen is perpendicular to the digitizer surface, between 0.0 and 90.0 when tilted towards the user, and between 0.0 and -90.0 when tilted away from the user. The default value is 0.0.
///
- public float YTilt { get; }
+ public float YTilt { get; } = 0.0F;
///
/// Gets the kind of pointer state change.
///
- public PointerUpdateKind PointerUpdateKind { get; }
+ public PointerUpdateKind PointerUpdateKind { get; } = PointerUpdateKind.LeftButtonPressed;
- private PointerPointProperties()
+ public PointerPointProperties()
{
}
diff --git a/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs b/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs
index 0e4e0ed3e2..b8f6f99ae8 100644
--- a/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs
+++ b/src/Avalonia.Base/Input/Raw/RawPointerEventArgs.cs
@@ -137,9 +137,13 @@ namespace Avalonia.Input.Raw
///
public Point Position { get; set; }
+ ///
public float Twist { get; set; }
+ ///
public float Pressure { get; set; }
+ ///
public float XTilt { get; set; }
+ ///
public float YTilt { get; set; }
diff --git a/src/Avalonia.Base/Interactivity/RoutedEvent.cs b/src/Avalonia.Base/Interactivity/RoutedEvent.cs
index a9b7dc8c89..37edb24cc1 100644
--- a/src/Avalonia.Base/Interactivity/RoutedEvent.cs
+++ b/src/Avalonia.Base/Interactivity/RoutedEvent.cs
@@ -113,24 +113,6 @@ namespace Avalonia.Interactivity
{
}
- [Obsolete("Use overload taking Action.")]
- public IDisposable AddClassHandler(
- Func> handler,
- RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
- bool handledEventsToo = false)
- where TTarget : class, IInteractive
- {
- void Adapter(object? sender, RoutedEventArgs e)
- {
- if (sender is TTarget target && e is TEventArgs args)
- {
- handler(target)(args);
- }
- }
-
- return AddClassHandler(typeof(TTarget), Adapter, routes, handledEventsToo);
- }
-
public IDisposable AddClassHandler(
Action handler,
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
diff --git a/src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs b/src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs
index 3e516b4f97..e6a95d1ffb 100644
--- a/src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs
+++ b/src/Avalonia.Base/Layout/FlowLayoutAlgorithm.cs
@@ -523,7 +523,7 @@ namespace Avalonia.Layout
{
firstRealizedElement = _elementManager.GetAt(0);
firstBounds = _elementManager.GetLayoutBoundsForRealizedIndex(0);
- firstDataIndex = _elementManager.GetDataIndexFromRealizedRangeIndex(0);;
+ firstDataIndex = _elementManager.GetDataIndexFromRealizedRangeIndex(0);
int last = _elementManager.GetRealizedElementCount() - 1;
lastRealizedElement = _elementManager.GetAt(last);
diff --git a/src/Avalonia.Base/Layout/ILayoutManager.cs b/src/Avalonia.Base/Layout/ILayoutManager.cs
index 143ce13a1b..88d95c81fd 100644
--- a/src/Avalonia.Base/Layout/ILayoutManager.cs
+++ b/src/Avalonia.Base/Layout/ILayoutManager.cs
@@ -44,17 +44,6 @@ namespace Avalonia.Layout
///
void ExecuteInitialLayoutPass();
- ///
- /// Executes the initial layout pass on a layout root.
- ///
- /// The control to lay out.
- ///
- /// You should not usually need to call this method explictly, the layout root will call
- /// it to carry out the initial layout of the control.
- ///
- [Obsolete("Call ExecuteInitialLayoutPass without parameter")]
- void ExecuteInitialLayoutPass(ILayoutRoot root);
-
///
/// Registers a control as wanting to receive effective viewport notifications.
///
diff --git a/src/Avalonia.Base/Layout/LayoutHelper.cs b/src/Avalonia.Base/Layout/LayoutHelper.cs
index 404d19906a..0851dbaea9 100644
--- a/src/Avalonia.Base/Layout/LayoutHelper.cs
+++ b/src/Avalonia.Base/Layout/LayoutHelper.cs
@@ -251,6 +251,17 @@ namespace Avalonia.Layout
{
double newValue;
+ // Round the value to avoid FP errors. This is needed because if `value` has a floating
+ // point precision error (e.g. 79.333333333333343) then when it's multiplied by
+ // `dpiScale` and rounded up, it will be rounded up to a value one greater than it
+ // should be.
+#if NET6_0_OR_GREATER
+ value = Math.Round(value, 8, MidpointRounding.ToZero);
+#else
+ // MidpointRounding.ToZero isn't available in netstandard2.0.
+ value = Math.Truncate(value * 1e8) / 1e8;
+#endif
+
// If DPI == 1, don't use DPI-aware rounding.
if (!MathUtilities.IsOne(dpiScale))
{
diff --git a/src/Avalonia.Base/Layout/LayoutManager.cs b/src/Avalonia.Base/Layout/LayoutManager.cs
index b9ca6bfbd7..826947c39a 100644
--- a/src/Avalonia.Base/Layout/LayoutManager.cs
+++ b/src/Avalonia.Base/Layout/LayoutManager.cs
@@ -196,17 +196,6 @@ namespace Avalonia.Layout
ExecuteLayoutPass();
}
- [Obsolete("Call ExecuteInitialLayoutPass without parameter")]
- public void ExecuteInitialLayoutPass(ILayoutRoot root)
- {
- if (root != _owner)
- {
- throw new ArgumentException("ExecuteInitialLayoutPass called with incorrect root.");
- }
-
- ExecuteInitialLayoutPass();
- }
-
public void Dispose()
{
_disposed = true;
diff --git a/src/Avalonia.Base/Layout/Layoutable.cs b/src/Avalonia.Base/Layout/Layoutable.cs
index 101e867d56..527b63292d 100644
--- a/src/Avalonia.Base/Layout/Layoutable.cs
+++ b/src/Avalonia.Base/Layout/Layoutable.cs
@@ -460,20 +460,6 @@ namespace Avalonia.Layout
_effectiveViewportChanged?.Invoke(this, e);
}
- ///
- /// Marks a property as affecting the control's measurement.
- ///
- /// The properties.
- ///
- /// After a call to this method in a control's static constructor, any change to the
- /// property will cause to be called on the element.
- ///
- [Obsolete("Use AffectsMeasure and specify the control type.")]
- protected static void AffectsMeasure(params AvaloniaProperty[] properties)
- {
- AffectsMeasure(properties);
- }
-
///
/// Marks a property as affecting the control's measurement.
///
@@ -497,20 +483,6 @@ namespace Avalonia.Layout
}
}
- ///
- /// Marks a property as affecting the control's arrangement.
- ///
- /// The properties.
- ///
- /// After a call to this method in a control's static constructor, any change to the
- /// property will cause to be called on the element.
- ///
- [Obsolete("Use AffectsArrange and specify the control type.")]
- protected static void AffectsArrange(params AvaloniaProperty[] properties)
- {
- AffectsArrange(properties);
- }
-
///
/// Marks a property as affecting the control's arrangement.
///
diff --git a/src/Avalonia.Base/Layout/StackLayout.cs b/src/Avalonia.Base/Layout/StackLayout.cs
index 00ac4a37f0..e3c2ab3817 100644
--- a/src/Avalonia.Base/Layout/StackLayout.cs
+++ b/src/Avalonia.Base/Layout/StackLayout.cs
@@ -322,6 +322,7 @@ namespace Avalonia.Layout
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
+ base.OnPropertyChanged(change);
if (change.Property == OrientationProperty)
{
var orientation = change.GetNewValue();
diff --git a/src/Avalonia.Base/Layout/UniformGridLayout.cs b/src/Avalonia.Base/Layout/UniformGridLayout.cs
index 47c994a350..a7880a1545 100644
--- a/src/Avalonia.Base/Layout/UniformGridLayout.cs
+++ b/src/Avalonia.Base/Layout/UniformGridLayout.cs
@@ -473,6 +473,7 @@ namespace Avalonia.Layout
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
+ base.OnPropertyChanged(change);
if (change.Property == OrientationProperty)
{
var orientation = change.GetNewValue();
diff --git a/src/Avalonia.Base/Media/DashStyle.cs b/src/Avalonia.Base/Media/DashStyle.cs
index abee580020..3a30b2d32f 100644
--- a/src/Avalonia.Base/Media/DashStyle.cs
+++ b/src/Avalonia.Base/Media/DashStyle.cs
@@ -35,7 +35,6 @@ namespace Avalonia.Media
/// Initializes a new instance of the class.
///
public DashStyle()
- : this(null, 0)
{
}
diff --git a/src/Avalonia.Base/Media/DrawingGroup.cs b/src/Avalonia.Base/Media/DrawingGroup.cs
index 603bb1c1c1..e71f568207 100644
--- a/src/Avalonia.Base/Media/DrawingGroup.cs
+++ b/src/Avalonia.Base/Media/DrawingGroup.cs
@@ -228,6 +228,8 @@ namespace Avalonia.Media
throw new NotImplementedException();
}
+ public object? GetFeature(Type t) => null;
+
public void DrawBitmap(IRef source, double opacity, Rect sourceRect, Rect destRect, BitmapInterpolationMode bitmapInterpolationMode = BitmapInterpolationMode.Default)
{
throw new NotImplementedException();
diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs
index 2a7f3360ad..cae7a8fe75 100644
--- a/src/Avalonia.Base/Media/GlyphRun.cs
+++ b/src/Avalonia.Base/Media/GlyphRun.cs
@@ -265,7 +265,7 @@ namespace Avalonia.Media
//RightToLeft
var glyphIndex = FindGlyphIndex(characterIndex);
- if (GlyphClusters != null)
+ if (GlyphClusters != null && GlyphClusters.Count > 0)
{
if (characterIndex > GlyphClusters[0])
{
diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs
index 5f1617d778..cf8a31c3e9 100644
--- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs
+++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs
@@ -89,22 +89,6 @@ namespace Avalonia.Media.Imaging
PlatformImpl.Dispose();
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// The pixel format.
- /// The pointer to the source bytes.
- /// The size of the bitmap in device pixels.
- /// The DPI of the bitmap.
- /// The number of bytes per row.
- [Obsolete("Use overload taking an AlphaFormat.")]
- public Bitmap(PixelFormat format, IntPtr data, PixelSize size, Vector dpi, int stride)
- {
- var ri = GetFactory();
- PlatformImpl = RefCountable.Create(ri
- .LoadBitmap(format, ri.DefaultAlphaFormat, data, size, dpi, stride));
- }
-
///
/// Initializes a new instance of the class.
///
diff --git a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
index 1f39b1344d..1aac8efac7 100644
--- a/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
+++ b/src/Avalonia.Base/Media/Imaging/WriteableBitmap.cs
@@ -9,18 +9,6 @@ namespace Avalonia.Media.Imaging
///
public class WriteableBitmap : Bitmap
{
- ///
- /// Initializes a new instance of the class.
- ///
- /// The size of the bitmap in device pixels.
- /// The DPI of the bitmap.
- /// The pixel format (optional).
- /// An .
- [Obsolete("Use overload taking an AlphaFormat.")]
- public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null)
- : base(CreatePlatformImpl(size, dpi, format, null))
- {
- }
///
/// Initializes a new instance of the class.
@@ -30,7 +18,7 @@ namespace Avalonia.Media.Imaging
/// The pixel format (optional).
/// The alpha format (optional).
/// An .
- public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat format, AlphaFormat alphaFormat)
+ public WriteableBitmap(PixelSize size, Vector dpi, PixelFormat? format = null, AlphaFormat? alphaFormat = null)
: base(CreatePlatformImpl(size, dpi, format, alphaFormat))
{
}
diff --git a/src/Avalonia.Base/Media/KnownColors.cs b/src/Avalonia.Base/Media/KnownColors.cs
index 8f07b5f8ac..ae2fca5a60 100644
--- a/src/Avalonia.Base/Media/KnownColors.cs
+++ b/src/Avalonia.Base/Media/KnownColors.cs
@@ -1,10 +1,11 @@
using System;
using System.Reflection;
using System.Collections.Generic;
+using Avalonia.SourceGenerator;
namespace Avalonia.Media
{
- internal static class KnownColors
+ internal static partial class KnownColors
{
private static readonly IReadOnlyDictionary _knownColorNames;
private static readonly IReadOnlyDictionary _knownColors;
@@ -12,23 +13,25 @@ namespace Avalonia.Media
private static readonly Dictionary _knownBrushes;
#endif
+ [GenerateEnumValueDictionary()]
+ private static partial Dictionary GetKnownColors();
+
static KnownColors()
{
var knownColorNames = new Dictionary(StringComparer.OrdinalIgnoreCase);
var knownColors = new Dictionary();
- foreach (var field in typeof(KnownColor).GetRuntimeFields())
+ foreach (var field in GetKnownColors())
{
- if (field.FieldType != typeof(KnownColor)) continue;
- var knownColor = (KnownColor)field.GetValue(null)!;
+ var knownColor = field.Value;
if (knownColor == KnownColor.None) continue;
- knownColorNames.Add(field.Name, knownColor);
+ knownColorNames.Add(field.Key, knownColor);
// some known colors have the same value, so use the first
if (!knownColors.ContainsKey((uint)knownColor))
{
- knownColors.Add((uint)knownColor, field.Name);
+ knownColors.Add((uint)knownColor, field.Key);
}
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs
index 97df87d3d9..7ab67ea34d 100644
--- a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Utilities;
namespace Avalonia.Media.TextFormatting
@@ -116,7 +117,30 @@ namespace Avalonia.Media.TextFormatting
length = text.Length;
}
+ length = CoerceLength(text, length);
+
return new ValueSpan(firstTextSourceIndex, length, currentProperties);
}
+
+ private static int CoerceLength(ReadOnlySlice text, int length)
+ {
+ var finalLength = 0;
+
+ var graphemeEnumerator = new GraphemeEnumerator(text);
+
+ while (graphemeEnumerator.MoveNext())
+ {
+ var grapheme = graphemeEnumerator.Current;
+
+ finalLength += grapheme.Text.Length;
+
+ if (finalLength >= length)
+ {
+ return finalLength;
+ }
+ }
+
+ return length;
+ }
}
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs
index df83ada34a..a49e4ef13b 100644
--- a/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/InterWordJustification.cs
@@ -15,6 +15,13 @@ namespace Avalonia.Media.TextFormatting
public override void Justify(TextLine textLine)
{
+ var lineImpl = textLine as TextLineImpl;
+
+ if(lineImpl is null)
+ {
+ return;
+ }
+
var paragraphWidth = Width;
if (double.IsInfinity(paragraphWidth))
@@ -22,12 +29,12 @@ namespace Avalonia.Media.TextFormatting
return;
}
- if (textLine.NewLineLength > 0)
+ if (lineImpl.NewLineLength > 0)
{
return;
}
- var textLineBreak = textLine.TextLineBreak;
+ var textLineBreak = lineImpl.TextLineBreak;
if (textLineBreak is not null && textLineBreak.TextEndOfLine is not null)
{
@@ -39,7 +46,7 @@ namespace Avalonia.Media.TextFormatting
var breakOportunities = new Queue();
- foreach (var textRun in textLine.TextRuns)
+ foreach (var textRun in lineImpl.TextRuns)
{
var text = textRun.Text;
@@ -68,10 +75,10 @@ namespace Avalonia.Media.TextFormatting
return;
}
- var remainingSpace = Math.Max(0, paragraphWidth - textLine.WidthIncludingTrailingWhitespace);
+ var remainingSpace = Math.Max(0, paragraphWidth - lineImpl.WidthIncludingTrailingWhitespace);
var spacing = remainingSpace / breakOportunities.Count;
- foreach (var textRun in textLine.TextRuns)
+ foreach (var textRun in lineImpl.TextRuns)
{
var text = textRun.Text;
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
index ab72601c3e..42a9e61c36 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs
@@ -38,7 +38,7 @@ namespace Avalonia.Media.TextFormatting
/// Gets a list of .
///
/// The shapeable text characters.
- internal IReadOnlyList GetShapeableCharacters(ReadOnlySlice runText, sbyte biDiLevel,
+ internal IReadOnlyList GetShapeableCharacters(ReadOnlySlice runText, sbyte biDiLevel,
ref TextRunProperties? previousProperties)
{
var shapeableCharacters = new List(2);
@@ -65,7 +65,7 @@ namespace Avalonia.Media.TextFormatting
/// The bidi level of the run.
///
/// A list of shapeable text runs.
- private static ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice text,
+ private static ShapeableTextCharacters CreateShapeableRun(ReadOnlySlice text,
TextRunProperties defaultProperties, sbyte biDiLevel, ref TextRunProperties? previousProperties)
{
var defaultTypeface = defaultProperties.Typeface;
@@ -76,7 +76,7 @@ namespace Avalonia.Media.TextFormatting
{
if (script == Script.Common && previousTypeface is not null)
{
- if(TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out var fallbackCount, out _))
+ if (TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out var fallbackCount, out _))
{
return new ShapeableTextCharacters(text.Take(fallbackCount),
defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel);
@@ -86,10 +86,10 @@ namespace Avalonia.Media.TextFormatting
return new ShapeableTextCharacters(text.Take(count), defaultProperties.WithTypeface(currentTypeface),
biDiLevel);
}
-
+
if (previousTypeface is not null)
{
- if(TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out count, out _))
+ if (TryGetShapeableLength(text, previousTypeface.Value, defaultTypeface, out count, out _))
{
return new ShapeableTextCharacters(text.Take(count),
defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel);
@@ -106,12 +106,12 @@ namespace Avalonia.Media.TextFormatting
{
continue;
}
-
+
codepoint = codepointEnumerator.Current;
-
+
break;
}
-
+
//ToDo: Fix FontFamily fallback
var matchFound =
FontManager.Current.TryMatchCharacter(codepoint, defaultTypeface.Style, defaultTypeface.Weight,
@@ -157,14 +157,14 @@ namespace Avalonia.Media.TextFormatting
///
///
protected static bool TryGetShapeableLength(
- ReadOnlySlice text,
- Typeface typeface,
+ ReadOnlySlice text,
+ Typeface typeface,
Typeface? defaultTypeface,
out int length,
out Script script)
{
length = 0;
- script = Script.Unknown;
+ script = Script.Unknown;
if (text.Length == 0)
{
@@ -182,7 +182,7 @@ namespace Avalonia.Media.TextFormatting
var currentScript = currentGrapheme.FirstCodepoint.Script;
- if (currentScript != Script.Common && defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
+ if (!currentGrapheme.FirstCodepoint.IsWhiteSpace && defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _))
{
break;
}
@@ -192,7 +192,7 @@ namespace Avalonia.Media.TextFormatting
{
break;
}
-
+
if (currentScript != script)
{
if (script is Script.Unknown || currentScript != Script.Common &&
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
index f3e8b5969c..0828b6518a 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs
@@ -537,8 +537,13 @@ namespace Avalonia.Media.TextFormatting
///
/// The collapsing width.
/// The .
- private TextCollapsingProperties GetCollapsingProperties(double width)
+ private TextCollapsingProperties? GetCollapsingProperties(double width)
{
+ if(_textTrimming == TextTrimming.None)
+ {
+ return null;
+ }
+
return _textTrimming.CreateCollapsingProperties(new TextCollapsingCreateInfo(width, _paragraphProperties.DefaultTextRunProperties));
}
}
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs
index c8a23097db..61b24dc8c5 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLine.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLine.cs
@@ -153,7 +153,7 @@ namespace Avalonia.Media.TextFormatting
///
/// A value that represents a collapsed line that can be displayed.
///
- public abstract TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList);
+ public abstract TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList);
///
/// Create a justified line based on justification text properties.
diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
index f3c62f4994..fa1ab6fd29 100644
--- a/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/TextLineImpl.cs
@@ -119,7 +119,7 @@ namespace Avalonia.Media.TextFormatting
}
///
- public override TextLine Collapse(params TextCollapsingProperties[] collapsingPropertiesList)
+ public override TextLine Collapse(params TextCollapsingProperties?[] collapsingPropertiesList)
{
if (collapsingPropertiesList.Length == 0)
{
@@ -128,6 +128,11 @@ namespace Avalonia.Media.TextFormatting
var collapsingProperties = collapsingPropertiesList[0];
+ if(collapsingProperties is null)
+ {
+ return this;
+ }
+
var collapsedRuns = collapsingProperties.Collapse(this);
if (collapsedRuns is null)
@@ -171,7 +176,7 @@ namespace Avalonia.Media.TextFormatting
return GetRunCharacterHit(firstRun, FirstTextSourceIndex, 0);
}
- if (distance > WidthIncludingTrailingWhitespace)
+ if (distance >= WidthIncludingTrailingWhitespace)
{
var lastRun = _textRuns[_textRuns.Count - 1];
@@ -183,8 +188,52 @@ namespace Avalonia.Media.TextFormatting
var currentPosition = FirstTextSourceIndex;
var currentDistance = 0.0;
- foreach (var currentRun in _textRuns)
+ for (var i = 0; i < _textRuns.Count; i++)
{
+ var currentRun = _textRuns[i];
+
+ if(currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight)
+ {
+ var rightToLeftIndex = i;
+ currentPosition += currentRun.TextSourceLength;
+
+ while (rightToLeftIndex + 1 <= _textRuns.Count - 1)
+ {
+ var nextShaped = _textRuns[rightToLeftIndex + 1] as ShapedTextCharacters;
+
+ if (nextShaped == null || nextShaped.ShapedBuffer.IsLeftToRight)
+ {
+ break;
+ }
+
+ currentPosition += nextShaped.TextSourceLength;
+
+ rightToLeftIndex++;
+ }
+
+ for (var j = i; i <= rightToLeftIndex; j++)
+ {
+ if(j > _textRuns.Count - 1)
+ {
+ break;
+ }
+
+ currentRun = _textRuns[j];
+
+ if(currentDistance + currentRun.Size.Width <= distance)
+ {
+ currentDistance += currentRun.Size.Width;
+ currentPosition -= currentRun.TextSourceLength;
+
+ continue;
+ }
+
+ characterHit = GetRunCharacterHit(currentRun, currentPosition, distance - currentDistance);
+
+ break;
+ }
+ }
+
if (currentDistance + currentRun.Size.Width < distance)
{
currentDistance += currentRun.Size.Width;
@@ -211,12 +260,16 @@ namespace Avalonia.Media.TextFormatting
{
characterHit = shapedRun.GlyphRun.GetCharacterHitFromDistance(distance, out _);
- var offset = Math.Max(0, currentPosition - shapedRun.Text.Start);
+ var offset = 0;
- if (!shapedRun.GlyphRun.IsLeftToRight)
+ if (shapedRun.GlyphRun.IsLeftToRight)
{
- offset = Math.Max(0, offset - shapedRun.Text.End);
+ offset = Math.Max(0, currentPosition - shapedRun.Text.Start);
}
+ //else
+ //{
+ // offset = Math.Max(0, currentPosition - shapedRun.Text.Start + shapedRun.Text.Length);
+ //}
characterHit = new CharacterHit(characterHit.FirstCharacterIndex + offset, characterHit.TrailingLength);
@@ -255,10 +308,56 @@ namespace Avalonia.Media.TextFormatting
{
var currentRun = _textRuns[index];
- if (TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength,
- flowDirection, out var distance, out _))
+ if (currentRun is ShapedTextCharacters shapedRun && !shapedRun.ShapedBuffer.IsLeftToRight)
+ {
+ var i = index;
+
+ var rightToLeftWidth = currentRun.Size.Width;
+
+ while (i + 1 <= _textRuns.Count - 1)
+ {
+ var nextRun = _textRuns[i + 1];
+
+ if (nextRun is ShapedTextCharacters nextShapedRun && !nextShapedRun.ShapedBuffer.IsLeftToRight)
+ {
+ i++;
+
+ rightToLeftWidth += nextRun.Size.Width;
+
+ continue;
+ }
+
+ break;
+ }
+
+ if(i > index)
+ {
+ while (i >= index)
+ {
+ currentRun = _textRuns[i];
+
+ rightToLeftWidth -= currentRun.Size.Width;
+
+ if (currentPosition + currentRun.TextSourceLength >= characterIndex)
+ {
+ break;
+ }
+
+ currentPosition += currentRun.TextSourceLength;
+
+ remainingLength -= currentRun.TextSourceLength;
+
+ i--;
+ }
+
+ currentDistance += rightToLeftWidth;
+ }
+ }
+
+ if (currentPosition + currentRun.TextSourceLength >= characterIndex &&
+ TryGetDistanceFromCharacterHit(currentRun, characterHit, currentPosition, remainingLength, flowDirection, out var distance, out _))
{
- return currentDistance + distance;
+ return Math.Max(0, currentDistance + distance);
}
//No hit hit found so we add the full width
@@ -283,7 +382,7 @@ namespace Avalonia.Media.TextFormatting
distance = currentGlyphRun.Size.Width - distance;
}
- return currentDistance - distance;
+ return Math.Max(0, currentDistance - distance);
}
//No hit hit found so we add the full width
@@ -293,7 +392,7 @@ namespace Avalonia.Media.TextFormatting
}
}
- return currentDistance;
+ return Math.Max(0, currentDistance);
}
private static bool TryGetDistanceFromCharacterHit(
@@ -442,92 +541,139 @@ namespace Avalonia.Media.TextFormatting
continue;
}
- if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
- {
- startX += currentRun.Size.Width;
-
- currentPosition += currentRun.TextSourceLength;
-
- continue;
- }
-
var characterLength = 0;
var endX = startX;
+ var runWidth = 0.0;
+ TextRunBounds? currentRunBounds = null;
- if (currentRun is ShapedTextCharacters currentShapedRun)
+ var currentShapedRun = currentRun as ShapedTextCharacters;
+
+ if (currentShapedRun != null && !currentShapedRun.ShapedBuffer.IsLeftToRight)
{
- var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
+ var rightToLeftIndex = index;
+ startX += currentShapedRun.Size.Width;
- currentPosition += offset;
+ while (rightToLeftIndex + 1 <= _textRuns.Count - 1)
+ {
+ var nextShapedRun = _textRuns[rightToLeftIndex + 1] as ShapedTextCharacters;
- var startIndex = currentRun.Text.Start + offset;
+ if (nextShapedRun == null || nextShapedRun.ShapedBuffer.IsLeftToRight)
+ {
+ break;
+ }
- double startOffset;
- double endOffset;
+ startX += nextShapedRun.Size.Width;
- if (currentShapedRun.ShapedBuffer.IsLeftToRight)
+ rightToLeftIndex++;
+ }
+
+ if (TryGetTextRunBoundsRightToLeft(startX, firstTextSourceIndex, characterIndex, rightToLeftIndex, ref currentPosition, ref remainingLength, out currentRunBounds))
{
- startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
+ startX = currentRunBounds!.Rectangle.Left;
+ endX = currentRunBounds.Rectangle.Right;
- endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
+ runWidth = currentRunBounds.Rectangle.Width;
}
- else
+
+ currentDirection = FlowDirection.RightToLeft;
+ }
+ else
+ {
+ if (currentShapedRun != null)
{
- endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
+ if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
+ {
+ startX += currentRun.Size.Width;
- if (currentPosition < startIndex)
+ currentPosition += currentRun.TextSourceLength;
+
+ continue;
+ }
+
+ var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
+
+ currentPosition += offset;
+
+ var startIndex = currentRun.Text.Start + offset;
+
+ double startOffset;
+ double endOffset;
+
+ if (currentShapedRun.ShapedBuffer.IsLeftToRight)
{
- startOffset = endOffset;
+ startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
+
+ endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
}
else
{
- startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
+ endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
+
+ if (currentPosition < startIndex)
+ {
+ startOffset = endOffset;
+ }
+ else
+ {
+ startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
+ }
}
- }
- startX += startOffset;
+ startX += startOffset;
- endX += endOffset;
+ endX += endOffset;
- var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
- var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
+ var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
+ var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
- characterLength = Math.Abs(endHit.FirstCharacterIndex + endHit.TrailingLength - startHit.FirstCharacterIndex - startHit.TrailingLength);
+ characterLength = Math.Abs(endHit.FirstCharacterIndex + endHit.TrailingLength - startHit.FirstCharacterIndex - startHit.TrailingLength);
- currentDirection = currentShapedRun.ShapedBuffer.IsLeftToRight ?
- FlowDirection.LeftToRight :
- FlowDirection.RightToLeft;
- }
- else
- {
- if (currentPosition < firstTextSourceIndex)
+ currentDirection = FlowDirection.LeftToRight;
+ }
+ else
{
- startX += currentRun.Size.Width;
+ if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
+ {
+ startX += currentRun.Size.Width;
+
+ currentPosition += currentRun.TextSourceLength;
+
+ continue;
+ }
+
+ if (currentPosition < firstTextSourceIndex)
+ {
+ startX += currentRun.Size.Width;
+ }
+
+ if (currentPosition + currentRun.TextSourceLength <= characterIndex)
+ {
+ endX += currentRun.Size.Width;
+
+ characterLength = currentRun.TextSourceLength;
+ }
}
- if (currentPosition + currentRun.TextSourceLength <= characterIndex)
+ if (endX < startX)
{
- endX += currentRun.Size.Width;
+ (endX, startX) = (startX, endX);
+ }
- characterLength = currentRun.TextSourceLength;
+ //Lines that only contain a linebreak need to be covered here
+ if (characterLength == 0)
+ {
+ characterLength = NewLineLength;
}
- }
- if (endX < startX)
- {
- (endX, startX) = (startX, endX);
- }
+ runWidth = endX - startX;
+ currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
- //Lines that only contain a linebreak need to be covered here
- if (characterLength == 0)
- {
- characterLength = NewLineLength;
- }
+ currentPosition += characterLength;
- var runWidth = endX - startX;
- var currentRunBounds = new TextRunBounds(new Rect(startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
+ remainingLength -= characterLength;
+ }
- if (!MathUtilities.IsZero(runWidth) || NewLineLength > 0)
+ if (currentRunBounds != null && !MathUtilities.IsZero(runWidth) || NewLineLength > 0)
{
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, startX))
{
@@ -537,32 +683,26 @@ namespace Avalonia.Media.TextFormatting
textBounds.Rectangle = currentRect;
- textBounds.TextRunBounds.Add(currentRunBounds);
+ textBounds.TextRunBounds.Add(currentRunBounds!);
}
else
{
- currentRect = currentRunBounds.Rectangle;
+ currentRect = currentRunBounds!.Rectangle;
result.Add(new TextBounds(currentRect, currentDirection, new List { currentRunBounds }));
}
}
currentWidth += runWidth;
- currentPosition += characterLength;
+
- if (currentPosition > characterIndex)
+ if (remainingLength <= 0 || currentPosition >= characterIndex)
{
break;
}
startX = endX;
lastDirection = currentDirection;
- remainingLength -= characterLength;
-
- if (remainingLength <= 0)
- {
- break;
- }
}
return result;
@@ -674,7 +814,7 @@ namespace Avalonia.Media.TextFormatting
var currentRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
- if(!MathUtilities.IsZero(runWidth) || NewLineLength > 0)
+ if (!MathUtilities.IsZero(runWidth) || NewLineLength > 0)
{
if (lastDirection == currentDirection && result.Count > 0 && MathUtilities.AreClose(currentRect.Right, Start + startX))
{
@@ -692,7 +832,7 @@ namespace Avalonia.Media.TextFormatting
result.Add(new TextBounds(currentRect, currentDirection, new List { currentRunBounds }));
}
- }
+ }
currentWidth += runWidth;
currentPosition += characterLength;
@@ -716,6 +856,107 @@ namespace Avalonia.Media.TextFormatting
return result;
}
+ private bool TryGetTextRunBoundsRightToLeft(double startX, int firstTextSourceIndex, int characterIndex, int runIndex, ref int currentPosition, ref int remainingLength, out TextRunBounds? textRunBounds)
+ {
+ textRunBounds = null;
+
+ for (var index = runIndex; index >= 0; index--)
+ {
+ if (TextRuns[index] is not DrawableTextRun currentRun)
+ {
+ continue;
+ }
+
+ if (currentPosition + currentRun.TextSourceLength <= firstTextSourceIndex)
+ {
+ startX -= currentRun.Size.Width;
+
+ currentPosition += currentRun.TextSourceLength;
+
+ continue;
+ }
+
+ var characterLength = 0;
+ var endX = startX;
+
+ if (currentRun is ShapedTextCharacters currentShapedRun)
+ {
+ var offset = Math.Max(0, firstTextSourceIndex - currentPosition);
+
+ currentPosition += offset;
+
+ var startIndex = currentRun.Text.Start + offset;
+ double startOffset;
+ double endOffset;
+
+ if (currentShapedRun.ShapedBuffer.IsLeftToRight)
+ {
+ if (currentPosition < startIndex)
+ {
+ startOffset = endOffset = 0;
+ }
+ else
+ {
+ endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
+
+ startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
+ }
+ }
+ else
+ {
+ endOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex));
+
+ startOffset = currentShapedRun.GlyphRun.GetDistanceFromCharacterHit(new CharacterHit(startIndex + remainingLength));
+ }
+
+ startX -= currentRun.Size.Width - startOffset;
+ endX -= currentRun.Size.Width - endOffset;
+
+ var endHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(endOffset, out _);
+ var startHit = currentShapedRun.GlyphRun.GetCharacterHitFromDistance(startOffset, out _);
+
+ characterLength = Math.Abs(startHit.FirstCharacterIndex + startHit.TrailingLength - endHit.FirstCharacterIndex - endHit.TrailingLength);
+ }
+ else
+ {
+ if (currentPosition + currentRun.TextSourceLength <= characterIndex)
+ {
+ endX -= currentRun.Size.Width;
+ }
+
+ if (currentPosition < firstTextSourceIndex)
+ {
+ startX -= currentRun.Size.Width;
+
+ characterLength = currentRun.TextSourceLength;
+ }
+ }
+
+ if (endX < startX)
+ {
+ (endX, startX) = (startX, endX);
+ }
+
+ //Lines that only contain a linebreak need to be covered here
+ if (characterLength == 0)
+ {
+ characterLength = NewLineLength;
+ }
+
+ var runWidth = endX - startX;
+
+ remainingLength -= characterLength;
+
+ currentPosition += characterLength;
+
+ textRunBounds = new TextRunBounds(new Rect(Start + startX, 0, runWidth, Height), currentPosition, characterLength, currentRun);
+
+ return true;
+ }
+
+ return false;
+ }
+
public override IReadOnlyList GetTextBounds(int firstTextSourceIndex, int textLength)
{
if (_paragraphProperties.FlowDirection == FlowDirection.LeftToRight)
@@ -1295,6 +1536,11 @@ namespace Avalonia.Media.TextFormatting
var textAlignment = _paragraphProperties.TextAlignment;
var paragraphFlowDirection = _paragraphProperties.FlowDirection;
+ if(textAlignment == TextAlignment.Justify)
+ {
+ textAlignment = TextAlignment.Start;
+ }
+
switch (textAlignment)
{
case TextAlignment.Start:
@@ -1319,12 +1565,12 @@ namespace Avalonia.Media.TextFormatting
case TextAlignment.Center:
var start = (_paragraphWidth - width) / 2;
- if(paragraphFlowDirection == FlowDirection.RightToLeft)
+ if (paragraphFlowDirection == FlowDirection.RightToLeft)
{
start -= (widthIncludingTrailingWhitespace - width);
}
- return Math.Max(0, start);
+ return Math.Max(0, start);
case TextAlignment.Right:
return Math.Max(0, _paragraphWidth - widthIncludingTrailingWhitespace);
diff --git a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs
index 56a90f31ea..ab17263806 100644
--- a/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs
+++ b/src/Avalonia.Base/Media/TextFormatting/Unicode/Codepoint.cs
@@ -224,7 +224,7 @@ namespace Avalonia.Media.TextFormatting.Unicode
}
///
- /// Returns if is between
+ /// Returns if is between
/// and , inclusive.
///
[MethodImpl(MethodImplOptions.AggressiveInlining)]
diff --git a/src/Avalonia.Base/Media/TextTrimming.cs b/src/Avalonia.Base/Media/TextTrimming.cs
index b6f5be496f..e2737210be 100644
--- a/src/Avalonia.Base/Media/TextTrimming.cs
+++ b/src/Avalonia.Base/Media/TextTrimming.cs
@@ -8,7 +8,7 @@ namespace Avalonia.Media
///
public abstract class TextTrimming
{
- public static char s_defaultEllipsisChar = '\u2026';
+ internal const char DefaultEllipsisChar = '\u2026';
///
/// Text is not trimmed.
@@ -18,22 +18,22 @@ namespace Avalonia.Media
///
/// Text is trimmed at a character boundary. An ellipsis (...) is drawn in place of remaining text.
///
- public static TextTrimming CharacterEllipsis { get; } = new TextTrailingTrimming(s_defaultEllipsisChar, false);
+ public static TextTrimming CharacterEllipsis { get; } = new TextTrailingTrimming(DefaultEllipsisChar, false);
///
/// Text is trimmed at a word boundary. An ellipsis (...) is drawn in place of remaining text.
///
- public static TextTrimming WordEllipsis { get; } = new TextTrailingTrimming(s_defaultEllipsisChar, true);
+ public static TextTrimming WordEllipsis { get; } = new TextTrailingTrimming(DefaultEllipsisChar, true);
///
/// Text is trimmed after a given prefix length. An ellipsis (...) is drawn in between prefix and suffix and represents remaining text.
///
- public static TextTrimming PrefixCharacterEllipsis { get; } = new TextLeadingPrefixTrimming(s_defaultEllipsisChar, 8);
+ public static TextTrimming PrefixCharacterEllipsis { get; } = new TextLeadingPrefixTrimming(DefaultEllipsisChar, 8);
///
/// Text is trimmed at a character boundary starting from the beginning. An ellipsis (...) is drawn in place of remaining text.
///
- public static TextTrimming LeadingCharacterEllipsis { get; } = new TextLeadingPrefixTrimming(s_defaultEllipsisChar, 0);
+ public static TextTrimming LeadingCharacterEllipsis { get; } = new TextLeadingPrefixTrimming(DefaultEllipsisChar, 0);
///
/// Creates properties that will be used for collapsing lines of text.
diff --git a/src/Avalonia.Base/Media/UnicodeRange.cs b/src/Avalonia.Base/Media/UnicodeRange.cs
index 0077dd64ac..e2338b9b26 100644
--- a/src/Avalonia.Base/Media/UnicodeRange.cs
+++ b/src/Avalonia.Base/Media/UnicodeRange.cs
@@ -9,7 +9,7 @@ namespace Avalonia.Media
///
public readonly struct UnicodeRange
{
- public static UnicodeRange Default = Parse("0-10FFFD");
+ public readonly static UnicodeRange Default = Parse("0-10FFFD");
private readonly UnicodeRangeSegment _single;
private readonly IReadOnlyList? _segments = null;
diff --git a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
index d84a509234..6aa5eeea3d 100644
--- a/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
+++ b/src/Avalonia.Base/Platform/IDrawingContextImpl.cs
@@ -172,6 +172,20 @@ namespace Avalonia.Platform
///
/// Custom draw operation
void Custom(ICustomDrawOperation custom);
+
+ ///
+ /// Attempts to get an optional feature from the drawing context implementation
+ ///
+ object? GetFeature(Type t);
+ }
+
+ public static class DrawingContextImplExtensions
+ {
+ ///
+ /// Attempts to get an optional feature from the drawing context implementation
+ ///
+ public static T? GetFeature(this IDrawingContextImpl context) where T : class =>
+ (T?)context.GetFeature(typeof(T));
}
public interface IDrawingContextLayerImpl : IRenderTargetBitmapImpl
diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
index 5af02219ce..cec20678cf 100644
--- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
+++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFile.cs
@@ -12,6 +12,11 @@ public class BclStorageFile : IStorageBookmarkFile
{
private readonly FileInfo _fileInfo;
+ public BclStorageFile(string fileName)
+ {
+ _fileInfo = new FileInfo(fileName);
+ }
+
public BclStorageFile(FileInfo fileInfo)
{
_fileInfo = fileInfo ?? throw new ArgumentNullException(nameof(fileInfo));
@@ -27,15 +32,14 @@ public class BclStorageFile : IStorageBookmarkFile
public Task GetBasicPropertiesAsync()
{
- var props = new StorageItemProperties();
if (_fileInfo.Exists)
{
- props = new StorageItemProperties(
+ return Task.FromResult(new StorageItemProperties(
(ulong)_fileInfo.Length,
_fileInfo.CreationTimeUtc,
- _fileInfo.LastAccessTimeUtc);
+ _fileInfo.LastAccessTimeUtc));
}
- return Task.FromResult(props);
+ return Task.FromResult(new StorageItemProperties());
}
public Task GetParentAsync()
@@ -47,22 +51,22 @@ public class BclStorageFile : IStorageBookmarkFile
return Task.FromResult(null);
}
- public Task OpenRead()
+ public Task OpenReadAsync()
{
return Task.FromResult(_fileInfo.OpenRead());
}
- public Task OpenWrite()
+ public Task OpenWriteAsync()
{
return Task.FromResult(_fileInfo.OpenWrite());
}
- public virtual Task SaveBookmark()
+ public virtual Task SaveBookmarkAsync()
{
return Task.FromResult(_fileInfo.FullName);
}
- public Task ReleaseBookmark()
+ public Task ReleaseBookmarkAsync()
{
// No-op
return Task.CompletedTask;
diff --git a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
index 7267017eaf..b91e910777 100644
--- a/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
+++ b/src/Avalonia.Base/Platform/Storage/FileIO/BclStorageFolder.cs
@@ -1,6 +1,8 @@
using System;
+using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.IO;
+using System.Linq;
using System.Security;
using System.Threading.Tasks;
using Avalonia.Metadata;
@@ -12,6 +14,15 @@ public class BclStorageFolder : IStorageBookmarkFolder
{
private readonly DirectoryInfo _directoryInfo;
+ public BclStorageFolder(string path)
+ {
+ _directoryInfo = new DirectoryInfo(path);
+ if (!_directoryInfo.Exists)
+ {
+ throw new ArgumentException("Directory must exist");
+ }
+ }
+
public BclStorageFolder(DirectoryInfo directoryInfo)
{
_directoryInfo = directoryInfo ?? throw new ArgumentNullException(nameof(directoryInfo));
@@ -43,12 +54,22 @@ public class BclStorageFolder : IStorageBookmarkFolder
return Task.FromResult(null);
}
- public virtual Task SaveBookmark()
+ public Task> GetItemsAsync()
+ {
+ var items = _directoryInfo.GetDirectories()
+ .Select(d => (IStorageItem)new BclStorageFolder(d))
+ .Concat(_directoryInfo.GetFiles().Select(f => new BclStorageFile(f)))
+ .ToArray();
+
+ return Task.FromResult>(items);
+ }
+
+ public virtual Task SaveBookmarkAsync()
{
return Task.FromResult(_directoryInfo.FullName);
}
- public Task ReleaseBookmark()
+ public Task ReleaseBookmarkAsync()
{
// No-op
return Task.CompletedTask;
diff --git a/src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs b/src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs
index d21c950862..40f2720ee8 100644
--- a/src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs
+++ b/src/Avalonia.Base/Platform/Storage/IStorageBookmarkItem.cs
@@ -6,7 +6,7 @@ namespace Avalonia.Platform.Storage;
[NotClientImplementable]
public interface IStorageBookmarkItem : IStorageItem
{
- Task ReleaseBookmark();
+ Task ReleaseBookmarkAsync();
}
[NotClientImplementable]
diff --git a/src/Avalonia.Base/Platform/Storage/IStorageFile.cs b/src/Avalonia.Base/Platform/Storage/IStorageFile.cs
index 965caf8216..46aa6efa72 100644
--- a/src/Avalonia.Base/Platform/Storage/IStorageFile.cs
+++ b/src/Avalonia.Base/Platform/Storage/IStorageFile.cs
@@ -18,7 +18,7 @@ public interface IStorageFile : IStorageItem
///
/// Opens a stream for read access.
///
- Task OpenRead();
+ Task OpenReadAsync();
///
/// Returns true, if file is writeable.
@@ -28,5 +28,5 @@ public interface IStorageFile : IStorageItem
///
/// Opens stream for writing to the file.
///
- Task OpenWrite();
+ Task OpenWriteAsync();
}
diff --git a/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs b/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs
index 25b9f01a92..0ffb9f41c6 100644
--- a/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs
+++ b/src/Avalonia.Base/Platform/Storage/IStorageFolder.cs
@@ -1,4 +1,6 @@
-using Avalonia.Metadata;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Avalonia.Metadata;
namespace Avalonia.Platform.Storage;
@@ -8,4 +10,11 @@ namespace Avalonia.Platform.Storage;
[NotClientImplementable]
public interface IStorageFolder : IStorageItem
{
+ ///
+ /// Gets the files and subfolders in the current folder.
+ ///
+ ///
+ /// When this method completes successfully, it returns a list of the files and folders in the current folder. Each item in the list is represented by an implementation object.
+ ///
+ Task> GetItemsAsync();
}
diff --git a/src/Avalonia.Base/Platform/Storage/IStorageItem.cs b/src/Avalonia.Base/Platform/Storage/IStorageItem.cs
index 8513ebc7d9..f5469d31c9 100644
--- a/src/Avalonia.Base/Platform/Storage/IStorageItem.cs
+++ b/src/Avalonia.Base/Platform/Storage/IStorageItem.cs
@@ -44,7 +44,7 @@ public interface IStorageItem : IDisposable
///
/// Returns identifier of a bookmark. Can be null if OS denied request.
///
- Task SaveBookmark();
+ Task SaveBookmarkAsync();
///
/// Gets the parent folder of the current storage item.
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
index 19d316eb85..a6db4330a3 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/CompositionAnimation.cs
@@ -1,12 +1,9 @@
// ReSharper disable InconsistentNaming
// ReSharper disable CheckNamespace
-using System;
-using System.Collections.Generic;
using System.Numerics;
using Avalonia.Rendering.Composition.Expressions;
using Avalonia.Rendering.Composition.Server;
-using Avalonia.Rendering.Composition.Transport;
// Special license applies License.md
@@ -16,10 +13,10 @@ namespace Avalonia.Rendering.Composition.Animations
/// This is the base class for ExpressionAnimation and KeyFrameAnimation.
///
///
- /// Use the method to start the animation.
+ /// Use the method to start the animation.
/// Value parameters (as opposed to reference parameters which are set using )
/// are copied and "embedded" into an expression at the time CompositionObject.StartAnimation is called.
- /// Changing the value of the variable after is called will not affect
+ /// Changing the value of the variable after is called will not affect
/// the value of the ExpressionAnimation.
/// See the remarks section of ExpressionAnimation for additional information.
///
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs
index 577910d975..ec2972044e 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/ExpressionAnimation.cs
@@ -16,7 +16,7 @@ namespace Avalonia.Rendering.Composition.Animations
/// This contrasts s, which use an interpolator to define how the animating
/// property changes over time. The mathematical equation can be defined using references to properties
/// of Composition objects, mathematical functions and operators and Input.
- /// Use the method to start the animation.
+ /// Use the method to start the animation.
///
public class ExpressionAnimation : CompositionAnimation
{
diff --git a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs
index 4692fde5e3..d21a4d06e3 100644
--- a/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Animations/KeyFrameAnimation.cs
@@ -24,9 +24,9 @@ namespace Avalonia.Rendering.Composition.Animations
/// The delay behavior of the key frame animation.
///
public AnimationDelayBehavior DelayBehavior { get; set; }
-
+
///
- /// Delay before the animation starts after is called.
+ /// Delay before the animation starts after is called.
///
public System.TimeSpan DelayTime { get; set; }
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
index db773bc43c..e47c8e7e31 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositingRenderer.cs
@@ -29,6 +29,7 @@ public class CompositingRenderer : IRendererWithCompositor
private bool _queuedUpdate;
private Action _update;
private Action _invalidateScene;
+ private bool _updating;
internal CompositionTarget CompositionTarget;
@@ -71,12 +72,14 @@ public class CompositingRenderer : IRendererWithCompositor
if(_queuedUpdate)
return;
_queuedUpdate = true;
- Dispatcher.UIThread.Post(_update, DispatcherPriority.Composition);
+ _compositor.InvokeWhenReadyForNextCommit(_update);
}
///
public void AddDirty(IVisual visual)
{
+ if (_updating)
+ throw new InvalidOperationException("Visual was invalidated during the render pass");
_dirty.Add((Visual)visual);
QueueUpdate();
}
@@ -84,7 +87,16 @@ public class CompositingRenderer : IRendererWithCompositor
///
public IEnumerable HitTest(Point p, IVisual root, Func? filter)
{
- var res = CompositionTarget.TryHitTest(p, filter);
+ Func? f = null;
+ if (filter != null)
+ f = v =>
+ {
+ if (v is CompositionDrawListVisual dlv)
+ return filter(dlv.Visual);
+ return true;
+ };
+
+ var res = CompositionTarget.TryHitTest(p, f);
if(res == null)
yield break;
foreach(var v in res)
@@ -107,6 +119,8 @@ public class CompositingRenderer : IRendererWithCompositor
///
public void RecalculateChildren(IVisual visual)
{
+ if (_updating)
+ throw new InvalidOperationException("Visual was invalidated during the render pass");
_recalculateChildren.Add((Visual)visual);
QueueUpdate();
}
@@ -191,7 +205,7 @@ public class CompositingRenderer : IRendererWithCompositor
private void InvalidateScene() =>
SceneInvalidated?.Invoke(this, new SceneInvalidatedEventArgs(_root, new Rect(_root.ClientSize)));
- private void Update()
+ private void UpdateCore()
{
_queuedUpdate = false;
foreach (var visual in _dirty)
@@ -240,6 +254,21 @@ public class CompositingRenderer : IRendererWithCompositor
CompositionTarget.Scaling = _root.RenderScaling;
Compositor.InvokeOnNextCommit(_invalidateScene);
}
+
+ private void Update()
+ {
+ if(_updating)
+ return;
+ _updating = true;
+ try
+ {
+ UpdateCore();
+ }
+ finally
+ {
+ _updating = false;
+ }
+ }
public void Resized(Size size)
{
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
index 77b392eee5..b019d1792b 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionDrawListVisual.cs
@@ -54,13 +54,11 @@ internal class CompositionDrawListVisual : CompositionContainerVisual
Visual = visual;
}
- internal override bool HitTest(Point pt, Func? filter)
+ internal override bool HitTest(Point pt)
{
var custom = Visual as ICustomHitTest;
if (DrawList == null && custom == null)
return false;
- if (filter != null && !filter(Visual))
- return false;
if (custom != null)
{
// Simulate the old behavior
diff --git a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
index 4e53e163ec..eb499604e0 100644
--- a/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
+++ b/src/Avalonia.Base/Rendering/Composition/CompositionTarget.cs
@@ -31,7 +31,7 @@ namespace Avalonia.Rendering.Composition
///
///
///
- public PooledList? TryHitTest(Point point, Func? filter)
+ public PooledList? TryHitTest(Point point, Func? filter)
{
Server.Readback.NextRead();
if (Root == null)
@@ -88,10 +88,14 @@ namespace Avalonia.Rendering.Composition
}
void HitTestCore(CompositionVisual visual, Point globalPoint, PooledList result,
- Func? filter)
+ Func? filter)
{
if (visual.Visible == false)
return;
+
+ if (filter != null && !filter(visual))
+ return;
+
if (!TryTransformTo(visual, globalPoint, out var point))
return;
@@ -111,7 +115,7 @@ namespace Avalonia.Rendering.Composition
}
// Hit-test the current node
- if (visual.HitTest(point, filter))
+ if (visual.HitTest(point))
result.Add(visual);
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Compositor.cs b/src/Avalonia.Base/Rendering/Composition/Compositor.cs
index 45212d0f36..10360f7874 100644
--- a/src/Avalonia.Base/Rendering/Composition/Compositor.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Compositor.cs
@@ -33,6 +33,7 @@ namespace Avalonia.Rendering.Composition
internal IEasing DefaultEasing { get; }
private List? _invokeOnNextCommit;
private readonly Stack> _invokeListPool = new();
+ private Task? _lastBatchCompleted;
///
/// Creates a new compositor on a specified render loop that would use a particular GPU
@@ -86,7 +87,7 @@ namespace Avalonia.Rendering.Composition
if (_invokeOnNextCommit != null)
ScheduleCommitCallbacks(batch.Completed);
- return batch.Completed;
+ return _lastBatchCompleted = batch.Completed;
}
async void ScheduleCommitCallbacks(Task task)
@@ -139,5 +140,15 @@ namespace Avalonia.Rendering.Composition
_invokeOnNextCommit ??= _invokeListPool.Count > 0 ? _invokeListPool.Pop() : new();
_invokeOnNextCommit.Add(action);
}
+
+ public void InvokeWhenReadyForNextCommit(Action action)
+ {
+ if (_lastBatchCompleted == null || _lastBatchCompleted.IsCompleted)
+ Dispatcher.UIThread.Post(action, DispatcherPriority.Composition);
+ else
+ _lastBatchCompleted.ContinueWith(
+ static (_, state) => Dispatcher.UIThread.Post((Action)state!, DispatcherPriority.Composition),
+ action);
+ }
}
}
diff --git a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
index 01216d19ed..30b57883fc 100644
--- a/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Drawing/CompositionDrawingContext.cs
@@ -156,6 +156,8 @@ internal class CompositionDrawingContext : IDrawingContextImpl, IDrawingContextW
++_drawOperationIndex;
}
+ public object? GetFeature(Type t) => null;
+
///
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
index 7eb35a68ed..03859d241f 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/DrawingContextProxy.cs
@@ -1,3 +1,4 @@
+using System;
using System.Numerics;
using Avalonia.Media;
using Avalonia.Media.Imaging;
@@ -155,6 +156,8 @@ internal class CompositorDrawingContextProxy : IDrawingContextImpl, IDrawingCont
_impl.Custom(custom);
}
+ public object? GetFeature(Type t) => _impl.GetFeature(t);
+
public class VisualBrushRenderer : IVisualBrushRenderer
{
public CompositionDrawList? VisualBrushDrawList { get; set; }
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
index c20594aaca..5c1ac0312c 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositionTarget.cs
@@ -78,6 +78,13 @@ namespace Avalonia.Rendering.Composition.Server
if (Root == null)
return;
+
+ if ((_renderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true)
+ {
+ _renderTarget!.Dispose();
+ _renderTarget = null;
+ }
+
_renderTarget ??= _renderTargetFactory();
Compositor.UpdateServerTime();
diff --git a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
index 621bc84f4a..bfc2b2d626 100644
--- a/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Server/ServerCompositor.cs
@@ -108,6 +108,7 @@ namespace Avalonia.Rendering.Composition.Server
private void RenderCore()
{
ApplyPendingBatches();
+ CompletePendingBatches();
foreach(var animation in _activeAnimations)
_animationsToUpdate.Add(animation);
@@ -119,8 +120,6 @@ namespace Avalonia.Rendering.Composition.Server
foreach (var t in _activeTargets)
t.Render();
-
- CompletePendingBatches();
}
public void AddCompositionTarget(ServerCompositionTarget target)
diff --git a/src/Avalonia.Base/Rendering/Composition/Visual.cs b/src/Avalonia.Base/Rendering/Composition/Visual.cs
index 7356b7b9e8..6d6818256a 100644
--- a/src/Avalonia.Base/Rendering/Composition/Visual.cs
+++ b/src/Avalonia.Base/Rendering/Composition/Visual.cs
@@ -53,6 +53,6 @@ namespace Avalonia.Rendering.Composition
internal object? Tag { get; set; }
- internal virtual bool HitTest(Point point, Func? filter) => true;
+ internal virtual bool HitTest(Point point) => true;
}
}
diff --git a/src/Avalonia.Base/Rendering/DirtyVisuals.cs b/src/Avalonia.Base/Rendering/DirtyVisuals.cs
index 00bc236b9c..999b12e810 100644
--- a/src/Avalonia.Base/Rendering/DirtyVisuals.cs
+++ b/src/Avalonia.Base/Rendering/DirtyVisuals.cs
@@ -17,8 +17,7 @@ namespace Avalonia.Rendering
{
private SortedDictionary> _inner = new SortedDictionary>();
private Dictionary _index = new Dictionary();
- private List _deferredChanges = new List();
- private int _deferring;
+ private int _enumerating;
///
/// Gets the number of dirty visuals.
@@ -31,10 +30,9 @@ namespace Avalonia.Rendering
/// The dirty visual.
public void Add(IVisual visual)
{
- if (_deferring > 0)
+ if (_enumerating > 0)
{
- _deferredChanges.Add(visual);
- return;
+ throw new InvalidOperationException("Visual was invalidated during a render pass");
}
var distance = visual.CalculateDistanceFromAncestor(visual.VisualRoot);
@@ -65,7 +63,7 @@ namespace Avalonia.Rendering
///
public void Clear()
{
- if (_deferring > 0)
+ if (_enumerating > 0)
{
throw new InvalidOperationException("Cannot clear while enumerating");
}
@@ -80,7 +78,7 @@ namespace Avalonia.Rendering
/// A collection of visuals.
public IEnumerator GetEnumerator()
{
- BeginDefer();
+ _enumerating++;
try
{
foreach (var i in _inner)
@@ -93,27 +91,10 @@ namespace Avalonia.Rendering
}
finally
{
- EndDefer();
+ _enumerating--;
}
}
-
- private void BeginDefer()
- {
- ++_deferring;
- }
-
- private void EndDefer()
- {
- if (--_deferring > 0) return;
-
- foreach (var visual in _deferredChanges)
- {
- Add(visual);
- }
-
- _deferredChanges.Clear();
- }
-
+
///
/// Gets the dirty visuals, in ascending order of distance to their root.
///
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs b/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
index 07082e4ac3..d6766fa9b8 100644
--- a/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
+++ b/src/Avalonia.Base/Rendering/SceneGraph/DeferredDrawingContextImpl.cs
@@ -203,6 +203,8 @@ namespace Avalonia.Rendering.SceneGraph
++_drawOperationindex;
}
+ public object? GetFeature(Type t) => null;
+
///
public void DrawGlyphRun(IBrush foreground, GlyphRun glyphRun)
{
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs
index 4b43f93aee..cf53b86fa7 100644
--- a/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs
+++ b/src/Avalonia.Base/Rendering/SceneGraph/GeometryNode.cs
@@ -1,9 +1,7 @@
using System;
-using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
-using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
@@ -19,7 +17,7 @@ namespace Avalonia.Rendering.SceneGraph
/// The fill brush.
/// The stroke pen.
/// The geometry.
- /// Child scenes for drawing visual brushes.
+ /// Auxiliary data required to draw the brush.
public GeometryNode(Matrix transform,
IBrush? brush,
IPen? pen,
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
index 1f58111ecf..1d85e95835 100644
--- a/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
+++ b/src/Avalonia.Base/Rendering/SceneGraph/GlyphRunNode.cs
@@ -1,10 +1,6 @@
using System;
-using System.Collections.Generic;
-
using Avalonia.Media;
-using Avalonia.Media.Immutable;
using Avalonia.Platform;
-using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
@@ -19,7 +15,7 @@ namespace Avalonia.Rendering.SceneGraph
/// The transform.
/// The foreground brush.
/// The glyph run to draw.
- /// Child scenes for drawing visual brushes.
+ /// Auxiliary data required to draw the brush.
public GlyphRunNode(
Matrix transform,
IBrush foreground,
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs
index ee5ec0a5fc..0af8ba2752 100644
--- a/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs
+++ b/src/Avalonia.Base/Rendering/SceneGraph/LineNode.cs
@@ -1,9 +1,7 @@
using System;
-using System.Collections.Generic;
using Avalonia.Media;
using Avalonia.Media.Immutable;
using Avalonia.Platform;
-using Avalonia.VisualTree;
namespace Avalonia.Rendering.SceneGraph
{
@@ -19,7 +17,7 @@ namespace Avalonia.Rendering.SceneGraph
/// The stroke pen.
/// The start point of the line.
/// The end point of the line.
- /// Child scenes for drawing visual brushes.
+ /// Auxiliary data required to draw the brush.
public LineNode(
Matrix transform,
IPen pen,
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs
index 549c1fd7de..5fd200ddff 100644
--- a/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs
+++ b/src/Avalonia.Base/Rendering/SceneGraph/OpacityMaskNode.cs
@@ -17,7 +17,7 @@ namespace Avalonia.Rendering.SceneGraph
///
/// The opacity mask to push.
/// The bounds of the mask.
- /// Child scenes for drawing visual brushes.
+ /// Auxiliary data required to draw the brush.
public OpacityMaskNode(IBrush mask, Rect bounds, IDisposable? aux = null)
: base(Rect.Empty, Matrix.Identity, aux)
{
diff --git a/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs b/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs
index a9d1bf96e5..f2ffd7411c 100644
--- a/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs
+++ b/src/Avalonia.Base/Rendering/SceneGraph/RectangleNode.cs
@@ -20,7 +20,7 @@ namespace Avalonia.Rendering.SceneGraph
/// The stroke pen.
/// The rectangle to draw.
/// The box shadow parameters
- /// Child scenes for drawing visual brushes.
+ /// Auxiliary data required to draw the brush.
public RectangleNode(
Matrix transform,
IBrush? brush,
diff --git a/src/Avalonia.Base/Rotate3DTransform.cs b/src/Avalonia.Base/Rotate3DTransform.cs
index 2c4e515861..2de33889c8 100644
--- a/src/Avalonia.Base/Rotate3DTransform.cs
+++ b/src/Avalonia.Base/Rotate3DTransform.cs
@@ -205,6 +205,7 @@ public class Rotate3DTransform : Transform
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
+ base.OnPropertyChanged(change);
if (!_isInitializing) RaiseChanged();
}
}
diff --git a/src/Avalonia.Base/Utilities/IWeakSubscriber.cs b/src/Avalonia.Base/Utilities/IWeakSubscriber.cs
index 613c931165..2a5b8d39c5 100644
--- a/src/Avalonia.Base/Utilities/IWeakSubscriber.cs
+++ b/src/Avalonia.Base/Utilities/IWeakSubscriber.cs
@@ -3,7 +3,7 @@ using System;
namespace Avalonia.Utilities
{
///
- /// Defines a listener to a event subscribed vis the .
+ /// Defines a listener to a event subscribed vis the .
///
/// The type of the event arguments.
public interface IWeakSubscriber where T : EventArgs
diff --git a/src/Avalonia.Base/Utilities/MathUtilities.cs b/src/Avalonia.Base/Utilities/MathUtilities.cs
index 3d5be806e1..3c48c3469e 100644
--- a/src/Avalonia.Base/Utilities/MathUtilities.cs
+++ b/src/Avalonia.Base/Utilities/MathUtilities.cs
@@ -255,7 +255,7 @@ namespace Avalonia.Utilities
///
/// Clamps a value between a minimum and maximum value.
///
- /// The value.
+ /// The value.
/// The minimum value.
/// The maximum value.
/// The clamped value.
@@ -324,6 +324,41 @@ namespace Avalonia.Utilities
return angle * 2 * Math.PI;
}
+ ///
+ /// Calculates the point of an angle on an ellipse.
+ ///
+ /// The centre point of the ellipse.
+ /// The x radius of the ellipse.
+ /// The y radius of the ellipse.
+ /// The angle in radians.
+ /// A point on the ellipse.
+ public static Point GetEllipsePoint(Point centre, double radiusX, double radiusY, double angle)
+ {
+ return new Point(radiusX * Math.Cos(angle) + centre.X, radiusY * Math.Sin(angle) + centre.Y);
+ }
+
+ ///
+ /// Gets the minimum and maximum from the specified numbers.
+ ///
+ /// The first number.
+ /// The second number.
+ /// A tuple containing the minimum and maximum of the two specified numbers.
+ public static (double min, double max) GetMinMax(double a, double b)
+ {
+ return a < b ? (a, b) : (b, a);
+ }
+
+ ///
+ /// Gets the minimum and maximum from the specified number and the difference with that number.
+ ///
+ /// The initial value to use.
+ /// The difference for .
+ /// A tuple containing the minimum and maximum of the specified number and the difference with that number.
+ public static (double min, double max) GetMinMaxFromDelta(double initialValue, double delta)
+ {
+ return GetMinMax(initialValue, initialValue + delta);
+ }
+
private static void ThrowCannotBeGreaterThanException(T min, T max)
{
throw new ArgumentException($"{min} cannot be greater than {max}.");
diff --git a/src/Avalonia.Base/Utilities/WeakObservable.cs b/src/Avalonia.Base/Utilities/WeakObservable.cs
index 6bf1d4082f..e1c350d539 100644
--- a/src/Avalonia.Base/Utilities/WeakObservable.cs
+++ b/src/Avalonia.Base/Utilities/WeakObservable.cs
@@ -9,31 +9,6 @@ namespace Avalonia.Utilities
///
public static class WeakObservable
{
- ///
- /// Converts a .NET event conforming to the standard .NET event pattern into an observable
- /// sequence, subscribing weakly.
- ///
- /// The type of target.
- /// The type of the event args.
- /// Object instance that exposes the event to convert.
- /// Name of the event to convert.
- ///
- [Obsolete("Use WeakEvent-based overload")]
- public static IObservable> FromEventPattern(
- TTarget target,
- string eventName)
- where TEventArgs : EventArgs
- {
- _ = target ?? throw new ArgumentNullException(nameof(target));
- _ = eventName ?? throw new ArgumentNullException(nameof(eventName));
-
- return Observable.Create>(observer =>
- {
- var handler = new Handler(observer);
- WeakSubscriptionManager.Subscribe(target, eventName, handler);
- return () => WeakSubscriptionManager.Unsubscribe(target, eventName, handler);
- }).Publish().RefCount();
- }
private class Handler
: IWeakSubscriber,
diff --git a/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs b/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs
deleted file mode 100644
index dc9e86cc32..0000000000
--- a/src/Avalonia.Base/Utilities/WeakSubscriptionManager.cs
+++ /dev/null
@@ -1,193 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using System.Runtime.CompilerServices;
-
-namespace Avalonia.Utilities
-{
- ///
- /// Manages subscriptions to events using weak listeners.
- ///
- public static class WeakSubscriptionManager
- {
- ///
- /// Subscribes to an event on an object using a weak subscription.
- ///
- /// The type of the target.
- /// The type of the event arguments.
- /// The event source.
- /// The name of the event.
- /// The subscriber.
- [Obsolete("Use WeakEvent")]
- public static void Subscribe(TTarget target, string eventName, IWeakSubscriber subscriber)
- where TEventArgs : EventArgs
- {
- _ = target ?? throw new ArgumentNullException(nameof(target));
-
- var dic = SubscriptionTypeStorage.Subscribers.GetOrCreateValue(target);
-
- if (!dic.TryGetValue(eventName, out var sub))
- {
- dic[eventName] = sub = new Subscription(dic, typeof(TTarget), target, eventName);
- }
-
- sub.Add(new WeakReference>(subscriber));
- }
-
- ///
- /// Unsubscribes from an event.
- ///
- /// The type of the event arguments.
- /// The event source.
- /// The name of the event.
- /// The subscriber.
- public static void Unsubscribe(object target, string eventName, IWeakSubscriber subscriber)
- where T : EventArgs
- {
- if (SubscriptionTypeStorage.Subscribers.TryGetValue(target, out var dic))
- {
- if (dic.TryGetValue(eventName, out var sub))
- {
- sub.Remove(subscriber);
- }
- }
- }
-
- private static class SubscriptionTypeStorage
- where T : EventArgs
- {
- public static readonly ConditionalWeakTable> Subscribers
- = new ConditionalWeakTable>();
- }
-
- private class SubscriptionDic : Dictionary>
- where T : EventArgs
- {
- }
-
- private static readonly Dictionary> Accessors
- = new Dictionary>();
-
- private class Subscription where T : EventArgs
- {
- private readonly EventInfo _info;
- private readonly SubscriptionDic _sdic;
- private readonly object _target;
- private readonly string _eventName;
- private readonly Delegate _delegate;
-
- private WeakReference>?[] _data = new WeakReference>?[16];
- private int _count = 0;
-
- public Subscription(SubscriptionDic sdic, Type targetType, object target, string eventName)
- {
- _sdic = sdic;
- _target = target;
- _eventName = eventName;
- if (!Accessors.TryGetValue(targetType, out var evDic))
- Accessors[targetType] = evDic = new Dictionary();
-
- if (evDic.TryGetValue(eventName, out var info))
- {
- _info = info;
- }
- else
- {
- var ev = targetType.GetRuntimeEvents().FirstOrDefault(x => x.Name == eventName);
-
- if (ev == null)
- {
- throw new ArgumentException(
- $"The event {eventName} was not found on {target.GetType()}.");
- }
-
- evDic[eventName] = _info = ev;
- }
-
- var del = new Action(OnEvent);
- _delegate = del.GetMethodInfo().CreateDelegate(_info.EventHandlerType!, del.Target);
- _info.AddMethod!.Invoke(target, new[] { _delegate });
- }
-
- void Destroy()
- {
- _info.RemoveMethod!.Invoke(_target, new[] { _delegate });
- _sdic.Remove(_eventName);
- }
-
- public void Add(WeakReference> s)
- {
- if (_count == _data.Length)
- {
- //Extend capacity
- var ndata = new WeakReference>?[_data.Length*2];
- Array.Copy(_data, ndata, _data.Length);
- _data = ndata;
- }
- _data[_count] = s!;
- _count++;
- }
-
- public void Remove(IWeakSubscriber s)
- {
- var removed = false;
-
- for (int c = 0; c < _count; ++c)
- {
- var reference = _data[c];
- IWeakSubscriber? instance;
-
- if (reference != null && reference.TryGetTarget(out instance) && instance == s)
- {
- _data[c] = null;
- removed = true;
- }
- }
-
- if (removed)
- {
- Compact();
- }
- }
-
- void Compact()
- {
- int empty = -1;
- for (int c = 0; c < _count; c++)
- {
- var r = _data[c];
- //Mark current index as first empty
- if (r == null && empty == -1)
- empty = c;
- //If current element isn't null and we have an empty one
- if (r != null && empty != -1)
- {
- _data[c] = null;
- _data[empty] = r;
- empty++;
- }
- }
- if (empty != -1)
- _count = empty;
- if (_count == 0)
- Destroy();
- }
-
- void OnEvent(object sender, T eventArgs)
- {
- var needCompact = false;
- for(var c=0; c<_count; c++)
- {
- var r = _data[c];
- if (r?.TryGetTarget(out var sub) == true)
- sub!.OnEvent(sender, eventArgs);
- else
- needCompact = true;
- }
- if (needCompact)
- Compact();
- }
- }
- }
-}
diff --git a/src/Avalonia.Base/Visual.cs b/src/Avalonia.Base/Visual.cs
index 8feba116f0..69389def56 100644
--- a/src/Avalonia.Base/Visual.cs
+++ b/src/Avalonia.Base/Visual.cs
@@ -338,22 +338,6 @@ namespace Avalonia
Contract.Requires(context != null);
}
- ///
- /// Indicates that a property change should cause to be
- /// called.
- ///
- /// The properties.
- ///
- /// This method should be called in a control's static constructor with each property
- /// on the control which when changed should cause a redraw. This is similar to WPF's
- /// FrameworkPropertyMetadata.AffectsRender flag.
- ///
- [Obsolete("Use AffectsRender and specify the control type.")]
- protected static void AffectsRender(params AvaloniaProperty[] properties)
- {
- AffectsRender(properties);
- }
-
///
/// Indicates that a property change should cause to be
/// called.
diff --git a/src/Avalonia.Base/VisualTree/IVisualTreeHost.cs b/src/Avalonia.Base/VisualTree/IVisualTreeHost.cs
deleted file mode 100644
index e47377063f..0000000000
--- a/src/Avalonia.Base/VisualTree/IVisualTreeHost.cs
+++ /dev/null
@@ -1,19 +0,0 @@
-using System;
-
-namespace Avalonia.VisualTree
-{
- ///
- /// Interface for controls that host their own separate visual tree, such as popups.
- ///
- [Obsolete]
- public interface IVisualTreeHost
- {
- ///
- /// Gets the root of the hosted visual tree.
- ///
- ///
- /// The root of the hosted visual tree.
- ///
- IVisual? Root { get; }
- }
-}
diff --git a/src/Avalonia.Base/VisualTree/VisualExtensions.cs b/src/Avalonia.Base/VisualTree/VisualExtensions.cs
index 128a39c082..a1b8894604 100644
--- a/src/Avalonia.Base/VisualTree/VisualExtensions.cs
+++ b/src/Avalonia.Base/VisualTree/VisualExtensions.cs
@@ -413,7 +413,7 @@ namespace Avalonia.VisualTree
Index = index,
ZIndex = element.ZIndex,
})
- .OrderBy(x => x, null)
+ .OrderBy(x => x, ZOrderElement.Comparer)
.Select(x => x.Element!);
}
@@ -448,6 +448,19 @@ namespace Avalonia.VisualTree
public int Index { get; set; }
public int ZIndex { get; set; }
+ class ZOrderComparer : IComparer
+ {
+ public int Compare(ZOrderElement? x, ZOrderElement? y)
+ {
+ if (ReferenceEquals(x, y)) return 0;
+ if (ReferenceEquals(null, y)) return 1;
+ if (ReferenceEquals(null, x)) return -1;
+ return x.CompareTo(y);
+ }
+ }
+
+ public static IComparer Comparer { get; } = new ZOrderComparer();
+
public int CompareTo(ZOrderElement? other)
{
if (other is null)
diff --git a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
index dc9064b262..eef574d297 100644
--- a/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
+++ b/src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
@@ -108,4 +108,5 @@
+
diff --git a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
index 39a83c00c4..5915388822 100644
--- a/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
+++ b/src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
@@ -326,8 +326,8 @@ namespace Avalonia.Build.Tasks
var op = i[c].Operand as MethodReference;
// TODO: Throw an error
- // This usually happens when same XAML resource was added twice for some weird reason
- // We currently support it for dual-named default theme resource
+ // This usually happens when the same XAML resource was added twice for some weird reason
+ // We currently support it for dual-named default theme resources
if (op != null
&& op.Name == TrampolineName)
{
diff --git a/src/Avalonia.Controls.ColorPicker/Converters/DoNothingForNullConverter.cs b/src/Avalonia.Controls.ColorPicker/Converters/DoNothingForNullConverter.cs
new file mode 100644
index 0000000000..421a5acb28
--- /dev/null
+++ b/src/Avalonia.Controls.ColorPicker/Converters/DoNothingForNullConverter.cs
@@ -0,0 +1,35 @@
+using System;
+using System.Globalization;
+using Avalonia.Data;
+using Avalonia.Data.Converters;
+
+namespace Avalonia.Controls.Converters
+{
+ ///
+ /// Converter that will do nothing (not update bound values) when a null value is encountered.
+ /// This converter enables binding nullable with non-nullable properties in some scenarios.
+ ///
+ public class DoNothingForNullConverter : IValueConverter
+ {
+ ///
+ public object? Convert(
+ object? value,
+ Type targetType,
+ object? parameter,
+ CultureInfo culture)
+ {
+ return value ?? BindingOperations.DoNothing;
+ }
+
+ ///
+ public object? ConvertBack(
+ object? value,
+ Type targetType,
+ object? parameter,
+ CultureInfo culture)
+ {
+ return value ?? BindingOperations.DoNothing;
+ }
+ }
+}
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml
deleted file mode 100644
index 35cd7a9faa..0000000000
--- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSlider.xaml
+++ /dev/null
@@ -1,188 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml
deleted file mode 100644
index db1fa3ee4e..0000000000
--- a/src/Avalonia.Controls.ColorPicker/Themes/Default/Default.xaml
+++ /dev/null
@@ -1,44 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
index 907b00dfff..74a1df4991 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorPicker.xaml
@@ -3,9 +3,6 @@
xmlns:controls="using:Avalonia.Controls"
x:CompileBindings="True">
-
- 5,5,0,0
-
@@ -25,7 +22,7 @@
Padding="0,0,10,0"
UseLayoutRounding="False">
-
@@ -45,7 +42,7 @@
-
+
+ SelectedIndex="{Binding SelectedIndex, RelativeSource={RelativeSource TemplatedParent}, Mode=TwoWay}">
+
+
+ 5,5,0,0
+
+
@@ -81,10 +83,4 @@
-
-
-
-
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
index 993745b1e5..dab4142001 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/ColorView.xaml
@@ -11,6 +11,7 @@
+
48
30
@@ -77,6 +78,8 @@
17.7761 14 17.5 14H9.94999ZM7.5 16C6.67157 16 6 15.3284 6 14.5C6 13.6716 6.67157 13 7.5
13C8.32843 13 9 13.6716 9 14.5C9 15.3284 8.32843 16 7.5 16Z
+
+ 3
@@ -97,7 +100,7 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Top"
Background="{DynamicResource SystemControlBackgroundBaseLowBrush}"
- CornerRadius="{TemplateBinding CornerRadius}" />
+ CornerRadius="{DynamicResource ColorViewTabBackgroundCornerRadius}" />
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml
index a5a94a2322..0a1a9e60c6 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Fluent/Fluent.xaml
@@ -11,15 +11,20 @@
Stretch="Uniform"
DestinationRect="0,0,8,8">
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
similarity index 89%
rename from src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml
rename to src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
index c3bc7df4a4..e067fe4ab5 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorPreviewer.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorPreviewer.xaml
@@ -1,15 +1,14 @@
-
+
-
-
- 80
- 40
-
+
+ 80
+ 40
-
+
-
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSlider.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSlider.xaml
new file mode 100644
index 0000000000..9aa2dcd9f9
--- /dev/null
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSlider.xaml
@@ -0,0 +1,190 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSpectrum.xaml
similarity index 69%
rename from src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml
rename to src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSpectrum.xaml
index 0e57f6b483..0e137c89c6 100644
--- a/src/Avalonia.Controls.ColorPicker/Themes/Default/ColorSpectrum.xaml
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/ColorSpectrum.xaml
@@ -1,9 +1,10 @@
-
+
-
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
-
+
+
+
+
+
-
+
-
-
-
+
+
+
-
+
+
+
diff --git a/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml b/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml
new file mode 100644
index 0000000000..f21b03cee2
--- /dev/null
+++ b/src/Avalonia.Controls.ColorPicker/Themes/Simple/Simple.xaml
@@ -0,0 +1,55 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs
index ff222658db..ab73ed73c7 100644
--- a/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs
+++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridSortDescription.cs
@@ -12,9 +12,6 @@ namespace Avalonia.Collections
{
public virtual string PropertyPath => null;
- [Obsolete("Use Direction property to read or override sorting direction.")]
- public virtual bool Descending => Direction == ListSortDirection.Descending;
-
public virtual ListSortDirection Direction => ListSortDirection.Ascending;
public bool HasPropertyPath => !String.IsNullOrEmpty(PropertyPath);
public abstract IComparer Comparer { get; }
@@ -254,13 +251,6 @@ namespace Avalonia.Collections
return new DataGridPathSortDescription(propertyPath, direction, null, culture);
}
-
- [Obsolete("Use overload taking a ListSortDirection.")]
- public static DataGridSortDescription FromPath(string propertyPath, bool descending, CultureInfo culture = null)
- {
- return new DataGridPathSortDescription(propertyPath, descending ? ListSortDirection.Descending : ListSortDirection.Ascending, null, culture);
- }
-
public static DataGridSortDescription FromPath(string propertyPath, ListSortDirection direction, IComparer comparer)
{
return new DataGridPathSortDescription(propertyPath, direction, comparer, null);
diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
index d42468f47e..cacd5c5a16 100644
--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
@@ -2167,7 +2167,23 @@ namespace Avalonia.Controls
return desiredSize;
}
+
+ ///
+ protected override void OnDataContextBeginUpdate()
+ {
+ base.OnDataContextBeginUpdate();
+ NotifyDataContextPropertyForAllRowCells(GetAllRows(), true);
+ }
+
+ ///
+ protected override void OnDataContextEndUpdate()
+ {
+ base.OnDataContextEndUpdate();
+
+ NotifyDataContextPropertyForAllRowCells(GetAllRows(), false);
+ }
+
///
/// Raises the BeginningEdit event.
///
@@ -3165,6 +3181,20 @@ namespace Avalonia.Controls
}
}
+ private static void NotifyDataContextPropertyForAllRowCells(IEnumerable rowSource, bool arg2)
+ {
+ foreach (DataGridRow row in rowSource)
+ {
+ foreach (DataGridCell cell in row.Cells)
+ {
+ if (cell.Content is StyledElement cellContent)
+ {
+ DataContextProperty.Notifying?.Invoke(cellContent, arg2);
+ }
+ }
+ }
+ }
+
private void UpdateRowDetailsVisibilityMode(DataGridRowDetailsVisibilityMode newDetailsMode)
{
int itemCount = DataConnection.Count;
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
index d63c738133..69e6766bfd 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
@@ -23,10 +23,10 @@ namespace Avalonia.Controls
[PseudoClasses(":pressed", ":current", ":expanded")]
public class DataGridRowGroupHeader : TemplatedControl
{
- private const string DATAGRIDROWGROUPHEADER_expanderButton = "ExpanderButton";
- private const string DATAGRIDROWGROUPHEADER_indentSpacer = "IndentSpacer";
- private const string DATAGRIDROWGROUPHEADER_itemCountElement = "ItemCountElement";
- private const string DATAGRIDROWGROUPHEADER_propertyNameElement = "PropertyNameElement";
+ private const string DATAGRIDROWGROUPHEADER_expanderButton = "PART_ExpanderButton";
+ private const string DATAGRIDROWGROUPHEADER_indentSpacer = "PART_IndentSpacer";
+ private const string DATAGRIDROWGROUPHEADER_itemCountElement = "PART_ItemCountElement";
+ private const string DATAGRIDROWGROUPHEADER_propertyNameElement = "PART_PropertyNameElement";
private bool _areIsCheckedHandlersSuspended;
private ToggleButton _expanderButton;
diff --git a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
index 5ae83427b5..b4bae02b9b 100644
--- a/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
+++ b/src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
@@ -53,7 +53,6 @@
-
-
@@ -258,10 +258,10 @@
ColumnDefinitions="Auto,Auto,Auto,Auto"
RowDefinitions="Auto,*,Auto">
-
-
+ Theme="{StaticResource SimpleDataGridRowGroupExpanderButtonTheme}" />
-
-
diff --git a/src/Avalonia.Controls/AutoCompleteBox.cs b/src/Avalonia.Controls/AutoCompleteBox.cs
index 5c95932c1f..c675139831 100644
--- a/src/Avalonia.Controls/AutoCompleteBox.cs
+++ b/src/Avalonia.Controls/AutoCompleteBox.cs
@@ -392,6 +392,8 @@ namespace Avalonia.Controls
private AutoCompleteSelector? _itemSelector;
private AutoCompleteSelector? _textSelector;
+ private readonly EventHandler _populateDropDownHandler;
+
public static readonly RoutedEvent SelectionChangedEvent =
RoutedEvent.Register(nameof(SelectionChanged), RoutingStrategies.Bubble, typeof(AutoCompleteBox));
@@ -668,6 +670,7 @@ namespace Avalonia.Controls
if (newValue == TimeSpan.Zero)
{
+ _delayTimer.Tick -= _populateDropDownHandler;
_delayTimer = null;
}
}
@@ -678,7 +681,7 @@ namespace Avalonia.Controls
if (_delayTimer == null)
{
_delayTimer = new DispatcherTimer();
- _delayTimer.Tick += PopulateDropDown;
+ _delayTimer.Tick += _populateDropDownHandler;
}
// Set the new tick interval
@@ -864,6 +867,7 @@ namespace Avalonia.Controls
///
public AutoCompleteBox()
{
+ _populateDropDownHandler = PopulateDropDown;
ClearView();
}
@@ -1771,10 +1775,7 @@ namespace Avalonia.Controls
/// The event arguments.
private void PopulateDropDown(object? sender, EventArgs e)
{
- if (_delayTimer != null)
- {
- _delayTimer.Stop();
- }
+ _delayTimer?.Stop();
// Update the prefix/search text.
SearchText = Text;
diff --git a/src/Avalonia.Controls/Button.cs b/src/Avalonia.Controls/Button.cs
index 6dba33516b..8e5d4e1e06 100644
--- a/src/Avalonia.Controls/Button.cs
+++ b/src/Avalonia.Controls/Button.cs
@@ -1,4 +1,5 @@
using System;
+using System.Diagnostics;
using System.Linq;
using System.Windows.Input;
using Avalonia.Automation.Peers;
@@ -281,24 +282,29 @@ namespace Avalonia.Controls
///
protected override void OnKeyDown(KeyEventArgs e)
{
- if (e.Key == Key.Enter)
+ switch (e.Key)
{
- OnClick();
- e.Handled = true;
- }
- else if (e.Key == Key.Space)
- {
- if (ClickMode == ClickMode.Press)
- {
+ case Key.Enter:
OnClick();
+ e.Handled = true;
+ break;
+
+ case Key.Space:
+ {
+ if (ClickMode == ClickMode.Press)
+ {
+ OnClick();
+ }
+
+ IsPressed = true;
+ e.Handled = true;
+ break;
}
- IsPressed = true;
- e.Handled = true;
- }
- else if (e.Key == Key.Escape && Flyout != null)
- {
- // If Flyout doesn't have focusable content, close the flyout here
- Flyout.Hide();
+
+ case Key.Escape when Flyout != null:
+ // If Flyout doesn't have focusable content, close the flyout here
+ CloseFlyout();
+ break;
}
base.OnKeyDown(e);
@@ -327,7 +333,14 @@ namespace Avalonia.Controls
{
if (IsEffectivelyEnabled)
{
- OpenFlyout();
+ if (_isFlyoutOpen)
+ {
+ CloseFlyout();
+ }
+ else
+ {
+ OpenFlyout();
+ }
var e = new RoutedEventArgs(ClickEvent);
RaiseEvent(e);
@@ -348,6 +361,14 @@ namespace Avalonia.Controls
Flyout?.ShowAt(this);
}
+ ///
+ /// Closes the button's flyout.
+ ///
+ protected virtual void CloseFlyout()
+ {
+ Flyout?.Hide();
+ }
+
///
/// Invoked when the button's flyout is opened.
///
@@ -494,8 +515,7 @@ namespace Avalonia.Controls
// If flyout is changed while one is already open, make sure we
// close the old one first
- if (oldFlyout != null &&
- oldFlyout.IsOpen)
+ if (oldFlyout != null && oldFlyout.IsOpen)
{
oldFlyout.Hide();
}
diff --git a/src/Avalonia.Controls/Calendar/Calendar.cs b/src/Avalonia.Controls/Calendar/Calendar.cs
index 2dbb5f02f9..0de068a416 100644
--- a/src/Avalonia.Controls/Calendar/Calendar.cs
+++ b/src/Avalonia.Controls/Calendar/Calendar.cs
@@ -224,7 +224,7 @@ namespace Avalonia.Controls
///
///
[TemplatePart(PART_ElementMonth, typeof(CalendarItem))]
- [TemplatePart(PART_ElementRoot, typeof(Panel))]
+ [TemplatePart(PART_ElementRoot, typeof(Panel))]
public class Calendar : TemplatedControl
{
internal const int RowsPerMonth = 7;
@@ -338,14 +338,11 @@ namespace Avalonia.Controls
/// The DependencyPropertyChangedEventArgs.
private void OnIsTodayHighlightedChanged(AvaloniaPropertyChangedEventArgs e)
{
- if (DisplayDate != null)
- {
- int i = DateTimeHelper.CompareYearMonth(DisplayDateInternal, DateTime.Today);
+ int i = DateTimeHelper.CompareYearMonth(DisplayDateInternal, DateTime.Today);
- if (i > -2 && i < 2)
- {
- UpdateMonths();
- }
+ if (i > -2 && i < 2)
+ {
+ UpdateMonths();
}
}
@@ -655,7 +652,7 @@ namespace Avalonia.Controls
SelectedDatesChanged?.Invoke(this, e);
}
}
-
+
internal Collection RemovedItems { get; set; }
internal DateTime? LastSelectedDateInternal { get; set; }
internal DateTime? LastSelectedDate
@@ -914,7 +911,7 @@ namespace Avalonia.Controls
o => o.DisplayDateEnd,
(o, v) => o.DisplayDateEnd = v,
defaultBindingMode: BindingMode.TwoWay);
-
+
///
/// Gets or sets the last date to be displayed.
///
@@ -1242,7 +1239,7 @@ namespace Avalonia.Controls
{
b.IsSelected = false;
}
- }
+ }
}
}
}
@@ -1278,7 +1275,7 @@ namespace Avalonia.Controls
internal void OnPreviousClick()
{
- if (DisplayMode == CalendarMode.Month && DisplayDate != null)
+ if (DisplayMode == CalendarMode.Month)
{
DateTime? d = DateTimeHelper.AddMonths(DateTimeHelper.DiscardDayTime(DisplayDate), -1);
if (d.HasValue)
@@ -1326,7 +1323,7 @@ namespace Avalonia.Controls
}
internal void OnNextClick()
{
- if (DisplayMode == CalendarMode.Month && DisplayDate != null)
+ if (DisplayMode == CalendarMode.Month)
{
DateTime? d = DateTimeHelper.AddMonths(DateTimeHelper.DiscardDayTime(DisplayDate), 1);
if (d.HasValue)
@@ -1645,7 +1642,7 @@ namespace Avalonia.Controls
{
if (DisplayMode == CalendarMode.Month)
{
- if (LastSelectedDate.HasValue && DisplayDateInternal != null)
+ if (LastSelectedDate.HasValue)
{
// If a blackout day is inactive, when clicked on it, the
// previous inactive day which is not a blackout day can get
@@ -1897,24 +1894,21 @@ namespace Avalonia.Controls
{
case CalendarMode.Month:
{
- if (DisplayDate != null)
- {
- DateTime? selectedDate = new DateTime(DisplayDateInternal.Year, DisplayDateInternal.Month, 1);
+ DateTime? selectedDate = new DateTime(DisplayDateInternal.Year, DisplayDateInternal.Month, 1);
- if (DateTimeHelper.CompareYearMonth(DateTime.MaxValue, selectedDate.Value) > 0)
- {
- // since DisplayDate is not equal to
- // DateTime.MaxValue we are sure selectedDate is\
- // not null
- selectedDate = DateTimeHelper.AddMonths(selectedDate.Value, 1)!.Value;
- selectedDate = DateTimeHelper.AddDays(selectedDate.Value, -1)!.Value;
- }
- else
- {
- selectedDate = DateTime.MaxValue;
- }
- ProcessSelection(shift, selectedDate, null);
+ if (DateTimeHelper.CompareYearMonth(DateTime.MaxValue, selectedDate.Value) > 0)
+ {
+ // since DisplayDate is not equal to
+ // DateTime.MaxValue we are sure selectedDate is\
+ // not null
+ selectedDate = DateTimeHelper.AddMonths(selectedDate.Value, 1)!.Value;
+ selectedDate = DateTimeHelper.AddDays(selectedDate.Value, -1)!.Value;
}
+ else
+ {
+ selectedDate = DateTime.MaxValue;
+ }
+ ProcessSelection(shift, selectedDate, null);
break;
}
case CalendarMode.Year:
@@ -2026,7 +2020,6 @@ namespace Avalonia.Controls
focusDate = DisplayDate;
LastSelectedDate = DisplayDate;
}
- Debug.Assert(focusDate != null, "focusDate should not be null!");
FocusButton = FindDayButtonFromDay(focusDate);
if (FocusButton != null)
@@ -2091,17 +2084,17 @@ namespace Avalonia.Controls
static Calendar()
{
- IsEnabledProperty.Changed.AddClassHandler((x,e) => x.OnIsEnabledChanged(e));
- FirstDayOfWeekProperty.Changed.AddClassHandler((x,e) => x.OnFirstDayOfWeekChanged(e));
- IsTodayHighlightedProperty.Changed.AddClassHandler((x,e) => x.OnIsTodayHighlightedChanged(e));
- DisplayModeProperty.Changed.AddClassHandler((x,e) => x.OnDisplayModePropertyChanged(e));
- SelectionModeProperty.Changed.AddClassHandler((x,e) => x.OnSelectionModeChanged(e));
- SelectedDateProperty.Changed.AddClassHandler((x,e) => x.OnSelectedDateChanged(e));
- DisplayDateProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateChanged(e));
- DisplayDateStartProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateStartChanged(e));
- DisplayDateEndProperty.Changed.AddClassHandler((x,e) => x.OnDisplayDateEndChanged(e));
- KeyDownEvent.AddClassHandler((x,e) => x.Calendar_KeyDown(e));
- KeyUpEvent.AddClassHandler((x,e) => x.Calendar_KeyUp(e));
+ IsEnabledProperty.Changed.AddClassHandler((x, e) => x.OnIsEnabledChanged(e));
+ FirstDayOfWeekProperty.Changed.AddClassHandler((x, e) => x.OnFirstDayOfWeekChanged(e));
+ IsTodayHighlightedProperty.Changed.AddClassHandler((x, e) => x.OnIsTodayHighlightedChanged(e));
+ DisplayModeProperty.Changed.AddClassHandler((x, e) => x.OnDisplayModePropertyChanged(e));
+ SelectionModeProperty.Changed.AddClassHandler((x, e) => x.OnSelectionModeChanged(e));
+ SelectedDateProperty.Changed.AddClassHandler((x, e) => x.OnSelectedDateChanged(e));
+ DisplayDateProperty.Changed.AddClassHandler((x, e) => x.OnDisplayDateChanged(e));
+ DisplayDateStartProperty.Changed.AddClassHandler((x, e) => x.OnDisplayDateStartChanged(e));
+ DisplayDateEndProperty.Changed.AddClassHandler((x, e) => x.OnDisplayDateEndChanged(e));
+ KeyDownEvent.AddClassHandler((x, e) => x.Calendar_KeyDown(e));
+ KeyUpEvent.AddClassHandler((x, e) => x.Calendar_KeyUp(e));
}
///
@@ -2116,8 +2109,8 @@ namespace Avalonia.Controls
RemovedItems = new Collection();
}
- private const string PART_ElementRoot = "Root";
- private const string PART_ElementMonth = "CalendarItem";
+ private const string PART_ElementRoot = "PART_Root";
+ private const string PART_ElementMonth = "PART_CalendarItem";
///
/// Builds the visual tree for the
diff --git a/src/Avalonia.Controls/Calendar/CalendarItem.cs b/src/Avalonia.Controls/Calendar/CalendarItem.cs
index 4c958c83b7..e2eabb5f28 100644
--- a/src/Avalonia.Controls/Calendar/CalendarItem.cs
+++ b/src/Avalonia.Controls/Calendar/CalendarItem.cs
@@ -4,7 +4,6 @@
// All other rights reserved.
using System;
-using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using Avalonia.Collections.Pooled;
@@ -33,11 +32,11 @@ namespace Avalonia.Controls.Primitives
///
private const int NumberOfDaysPerWeek = 7;
- private const string PART_ElementHeaderButton = "HeaderButton";
- private const string PART_ElementPreviousButton = "PreviousButton";
- private const string PART_ElementNextButton = "NextButton";
- private const string PART_ElementMonthView = "MonthView";
- private const string PART_ElementYearView = "YearView";
+ private const string PART_ElementHeaderButton = "PART_HeaderButton";
+ private const string PART_ElementPreviousButton = "PART_PreviousButton";
+ private const string PART_ElementNextButton = "PART_NextButton";
+ private const string PART_ElementMonthView = "PART_MonthView";
+ private const string PART_ElementYearView = "PART_YearView";
private Button? _headerButton;
private Button? _nextButton;
@@ -353,7 +352,6 @@ namespace Avalonia.Controls.Primitives
{
if (Owner != null)
{
- Debug.Assert(Owner.DisplayDate != null, "The Owner Calendar's DisplayDate should not be null!");
_currentMonth = Owner.DisplayDateInternal;
}
else
@@ -361,17 +359,14 @@ namespace Avalonia.Controls.Primitives
_currentMonth = DateTime.Today;
}
- if (_currentMonth != null)
- {
- SetMonthModeHeaderButton();
- SetMonthModePreviousButton(_currentMonth);
- SetMonthModeNextButton(_currentMonth);
+ SetMonthModeHeaderButton();
+ SetMonthModePreviousButton(_currentMonth);
+ SetMonthModeNextButton(_currentMonth);
- if (MonthView != null)
- {
- SetDayTitles();
- SetCalendarDayButtons(_currentMonth);
- }
+ if (MonthView != null)
+ {
+ SetDayTitles();
+ SetCalendarDayButtons(_currentMonth);
}
}
private void SetMonthModeHeaderButton()
@@ -592,7 +587,6 @@ namespace Avalonia.Controls.Primitives
{
if (Owner != null)
{
- Debug.Assert(Owner.SelectedMonth != null, "The Owner Calendar's SelectedMonth should not be null!");
_currentMonth = (DateTime)Owner.SelectedMonth;
}
else
@@ -600,16 +594,13 @@ namespace Avalonia.Controls.Primitives
_currentMonth = DateTime.Today;
}
- if (_currentMonth != null)
- {
- SetYearModeHeaderButton();
- SetYearModePreviousButton();
- SetYearModeNextButton();
+ SetYearModeHeaderButton();
+ SetYearModePreviousButton();
+ SetYearModeNextButton();
- if (YearView != null)
- {
- SetMonthButtonsForYearMode();
- }
+ if (YearView != null)
+ {
+ SetMonthButtonsForYearMode();
}
}
private void SetYearModeHeaderButton()
@@ -660,7 +651,6 @@ namespace Avalonia.Controls.Primitives
childButton.IsCalendarButtonFocused = false;
}
- Debug.Assert(Owner.DisplayDateInternal != null, "The Owner Calendar's DisplayDateInternal should not be null!");
childButton.IsSelected = (DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateInternal) == 0);
if (DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateRangeStart) < 0 || DateTimeHelper.CompareYearMonth(day, Owner.DisplayDateRangeEnd) > 0)
@@ -685,7 +675,6 @@ namespace Avalonia.Controls.Primitives
if (Owner != null)
{
- Debug.Assert(Owner.SelectedYear != null, "The owning Calendar's selected year should not be null!");
selectedYear = Owner.SelectedYear;
_currentMonth = (DateTime)Owner.SelectedMonth;
}
@@ -695,19 +684,16 @@ namespace Avalonia.Controls.Primitives
selectedYear = DateTime.Today;
}
- if (_currentMonth != null)
- {
- int decade = DateTimeHelper.DecadeOfDate(selectedYear);
- int decadeEnd = DateTimeHelper.EndOfDecade(selectedYear);
+ int decade = DateTimeHelper.DecadeOfDate(selectedYear);
+ int decadeEnd = DateTimeHelper.EndOfDecade(selectedYear);
- SetDecadeModeHeaderButton(decade, decadeEnd);
- SetDecadeModePreviousButton(decade);
- SetDecadeModeNextButton(decadeEnd);
+ SetDecadeModeHeaderButton(decade, decadeEnd);
+ SetDecadeModePreviousButton(decade);
+ SetDecadeModeNextButton(decadeEnd);
- if (YearView != null)
- {
- SetYearButtons(decade, decadeEnd);
- }
+ if (YearView != null)
+ {
+ SetYearButtons(decade, decadeEnd);
}
}
internal void UpdateYearViewSelection(CalendarButton calendarButton)
@@ -822,22 +808,15 @@ namespace Avalonia.Controls.Primitives
{
if (Owner.DisplayMode == CalendarMode.Month)
{
- if (Owner.DisplayDate != null)
- {
- d = Owner.DisplayDateInternal;
- Owner.SelectedMonth = new DateTime(d.Year, d.Month, 1);
- }
+ d = Owner.DisplayDateInternal;
+ Owner.SelectedMonth = new DateTime(d.Year, d.Month, 1);
Owner.DisplayMode = CalendarMode.Year;
}
else
{
Debug.Assert(Owner.DisplayMode == CalendarMode.Year, "The Owner Calendar's DisplayMode should be Year!");
-
- if (Owner.SelectedMonth != null)
- {
- d = Owner.SelectedMonth;
- Owner.SelectedYear = new DateTime(d.Year, d.Month, 1);
- }
+ d = Owner.SelectedMonth;
+ Owner.SelectedYear = new DateTime(d.Year, d.Month, 1);
Owner.DisplayMode = CalendarMode.Decade;
}
}
diff --git a/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs b/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs
index c7c35718e0..f4bc2528ba 100644
--- a/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs
+++ b/src/Avalonia.Controls/Calendar/SelectedDatesCollection.cs
@@ -297,7 +297,7 @@ namespace Avalonia.Controls.Primitives
}
else
{
- if (item != null && DateTime.Compare(this[index], item) != 0 && Calendar.IsValidDateSelection(_owner, item))
+ if (DateTime.Compare(this[index], item) != 0 && Calendar.IsValidDateSelection(_owner, item))
{
removedItems.Add(this[index]);
base.SetItem(index, item);
diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs
index 083182a370..524362fcf9 100644
--- a/src/Avalonia.Controls/Control.cs
+++ b/src/Avalonia.Controls/Control.cs
@@ -8,6 +8,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Interactivity;
+using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.Styling;
@@ -104,7 +105,6 @@ namespace Avalonia.Controls
private static readonly HashSet _loadedQueue = new HashSet();
private static readonly HashSet _loadedProcessingQueue = new HashSet();
- private bool _isAttachedToVisualTree = false;
private bool _isLoaded = false;
private DataTemplates? _dataTemplates;
private IControl? _focusAdorner;
@@ -347,7 +347,7 @@ namespace Avalonia.Controls
internal void OnLoadedCore()
{
if (_isLoaded == false &&
- _isAttachedToVisualTree)
+ ((ILogical)this).IsAttachedToLogicalTree)
{
_isLoaded = true;
OnLoaded();
@@ -395,7 +395,6 @@ namespace Avalonia.Controls
protected sealed override void OnAttachedToVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTreeCore(e);
- _isAttachedToVisualTree = true;
InitializeIfNeeded();
@@ -406,7 +405,6 @@ namespace Avalonia.Controls
protected sealed override void OnDetachedFromVisualTreeCore(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTreeCore(e);
- _isAttachedToVisualTree = false;
OnUnloadedCore();
}
diff --git a/src/Avalonia.Controls/Converters/StringFormatConverter.cs b/src/Avalonia.Controls/Converters/StringFormatConverter.cs
index ae920dac7e..82e75d9c34 100644
--- a/src/Avalonia.Controls/Converters/StringFormatConverter.cs
+++ b/src/Avalonia.Controls/Converters/StringFormatConverter.cs
@@ -2,7 +2,6 @@
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
-using Avalonia.Data;
using Avalonia.Data.Converters;
namespace Avalonia.Controls.Converters;
@@ -15,13 +14,17 @@ public class StringFormatConverter : IMultiValueConverter
{
public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture)
{
- try
+ if (values[0] is string format)
{
- return string.Format((string)values[0]!, values.Skip(1).ToArray());
- }
- catch (Exception e)
- {
- return new BindingNotification(e, BindingErrorType.Error);
+ try
+ {
+ return string.Format(format, values.Skip(1).ToArray());
+ }
+ catch
+ {
+ return AvaloniaProperty.UnsetValue;
+ }
}
+ return AvaloniaProperty.UnsetValue;
}
}
diff --git a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
index f2b808fe0d..fb27feb521 100644
--- a/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/DatePicker.cs
@@ -14,15 +14,15 @@ namespace Avalonia.Controls
///
/// A control to allow the user to select a date
///
- [TemplatePart("ButtonContentGrid", typeof(Grid))]
- [TemplatePart("DayText", typeof(TextBlock))]
- [TemplatePart("FirstSpacer", typeof(Rectangle))]
- [TemplatePart("FlyoutButton", typeof(Button))]
- [TemplatePart("MonthText", typeof(TextBlock))]
- [TemplatePart("PickerPresenter", typeof(DatePickerPresenter))]
- [TemplatePart("Popup", typeof(Popup))]
- [TemplatePart("SecondSpacer", typeof(Rectangle))]
- [TemplatePart("YearText", typeof(TextBlock))]
+ [TemplatePart("PART_ButtonContentGrid", typeof(Grid))]
+ [TemplatePart("PART_DayTextBlock", typeof(TextBlock))]
+ [TemplatePart("PART_FirstSpacer", typeof(Rectangle))]
+ [TemplatePart("PART_FlyoutButton", typeof(Button))]
+ [TemplatePart("PART_MonthTextBlock", typeof(TextBlock))]
+ [TemplatePart("PART_PickerPresenter", typeof(DatePickerPresenter))]
+ [TemplatePart("PART_Popup", typeof(Popup))]
+ [TemplatePart("PART_SecondSpacer", typeof(Rectangle))]
+ [TemplatePart("PART_YearTextBlock", typeof(TextBlock))]
[PseudoClasses(":hasnodate")]
public class DatePicker : TemplatedControl
{
@@ -280,15 +280,15 @@ namespace Avalonia.Controls
}
base.OnApplyTemplate(e);
- _flyoutButton = e.NameScope.Find
- [TemplatePart("AcceptButton", typeof(Button))]
- [TemplatePart("DayDownButton", typeof(RepeatButton))]
- [TemplatePart("DayHost", typeof(Panel))]
- [TemplatePart("DaySelector", typeof(DateTimePickerPanel))]
- [TemplatePart("DayUpButton", typeof(RepeatButton))]
- [TemplatePart("DismissButton", typeof(Button))]
- [TemplatePart("FirstSpacer", typeof(Rectangle))]
- [TemplatePart("MonthDownButton", typeof(RepeatButton))]
- [TemplatePart("MonthHost", typeof(Panel))]
- [TemplatePart("MonthSelector", typeof(DateTimePickerPanel))]
- [TemplatePart("MonthUpButton", typeof(RepeatButton))]
- [TemplatePart("PickerContainer", typeof(Grid))]
- [TemplatePart("SecondSpacer", typeof(Rectangle))]
- [TemplatePart("YearDownButton", typeof(RepeatButton))]
- [TemplatePart("YearHost", typeof(Panel))]
- [TemplatePart("YearSelector", typeof(DateTimePickerPanel))]
- [TemplatePart("YearUpButton", typeof(RepeatButton))]
+ [TemplatePart("PART_AcceptButton", typeof(Button))]
+ [TemplatePart("PART_DayDownButton", typeof(RepeatButton))]
+ [TemplatePart("PART_DayHost", typeof(Panel))]
+ [TemplatePart("PART_DaySelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("PART_DayUpButton", typeof(RepeatButton))]
+ [TemplatePart("PART_DismissButton", typeof(Button))]
+ [TemplatePart("PART_FirstSpacer", typeof(Rectangle))]
+ [TemplatePart("PART_MonthDownButton", typeof(RepeatButton))]
+ [TemplatePart("PART_MonthHost", typeof(Panel))]
+ [TemplatePart("PART_MonthSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("PART_MonthUpButton", typeof(RepeatButton))]
+ [TemplatePart("PART_PickerContainer", typeof(Grid))]
+ [TemplatePart("PART_SecondSpacer", typeof(Rectangle))]
+ [TemplatePart("PART_YearDownButton", typeof(RepeatButton))]
+ [TemplatePart("PART_YearHost", typeof(Panel))]
+ [TemplatePart("PART_YearSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("PART_YearUpButton", typeof(RepeatButton))]
public class DatePickerPresenter : PickerPresenterBase
{
///
@@ -253,58 +253,58 @@ namespace Avalonia.Controls
{
base.OnApplyTemplate(e);
// These are requirements, so throw if not found
- _pickerContainer = e.NameScope.Get("PickerContainer");
- _monthHost = e.NameScope.Get("MonthHost");
- _dayHost = e.NameScope.Get("DayHost");
- _yearHost = e.NameScope.Get("YearHost");
+ _pickerContainer = e.NameScope.Get("PART_PickerContainer");
+ _monthHost = e.NameScope.Get("PART_MonthHost");
+ _dayHost = e.NameScope.Get("PART_DayHost");
+ _yearHost = e.NameScope.Get("PART_YearHost");
- _monthSelector = e.NameScope.Get("MonthSelector");
+ _monthSelector = e.NameScope.Get("PART_MonthSelector");
_monthSelector.SelectionChanged += OnMonthChanged;
- _daySelector = e.NameScope.Get("DaySelector");
+ _daySelector = e.NameScope.Get("PART_DaySelector");
_daySelector.SelectionChanged += OnDayChanged;
- _yearSelector = e.NameScope.Get("YearSelector");
+ _yearSelector = e.NameScope.Get("PART_YearSelector");
_yearSelector.SelectionChanged += OnYearChanged;
- _acceptButton = e.NameScope.Get("AcceptButton");
+ _acceptButton = e.NameScope.Get("PART_AcceptButton");
- _monthUpButton = e.NameScope.Find("MonthUpButton");
+ _monthUpButton = e.NameScope.Find("PART_MonthUpButton");
if (_monthUpButton != null)
{
_monthUpButton.Click += OnSelectorButtonClick;
}
- _monthDownButton = e.NameScope.Find("MonthDownButton");
+ _monthDownButton = e.NameScope.Find("PART_MonthDownButton");
if (_monthDownButton != null)
{
_monthDownButton.Click += OnSelectorButtonClick;
}
- _dayUpButton = e.NameScope.Find("DayUpButton");
+ _dayUpButton = e.NameScope.Find("PART_DayUpButton");
if (_dayUpButton != null)
{
_dayUpButton.Click += OnSelectorButtonClick;
}
- _dayDownButton = e.NameScope.Find("DayDownButton");
+ _dayDownButton = e.NameScope.Find("PART_DayDownButton");
if (_dayDownButton != null)
{
_dayDownButton.Click += OnSelectorButtonClick;
}
- _yearUpButton = e.NameScope.Find("YearUpButton");
+ _yearUpButton = e.NameScope.Find("PART_YearUpButton");
if (_yearUpButton != null)
{
_yearUpButton.Click += OnSelectorButtonClick;
}
- _yearDownButton = e.NameScope.Find("YearDownButton");
+ _yearDownButton = e.NameScope.Find("PART_YearDownButton");
if (_yearDownButton != null)
{
_yearDownButton.Click += OnSelectorButtonClick;
}
- _dismissButton = e.NameScope.Find("DismissButton");
- _spacer1 = e.NameScope.Find("FirstSpacer");
- _spacer2 = e.NameScope.Find("SecondSpacer");
+ _dismissButton = e.NameScope.Find("PART_DismissButton");
+ _spacer1 = e.NameScope.Find("PART_FirstSpacer");
+ _spacer2 = e.NameScope.Find("PART_SecondSpacer");
if (_acceptButton != null)
{
diff --git a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
index 047667567d..a709e4fb49 100644
--- a/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
+++ b/src/Avalonia.Controls/DateTimePickers/TimePicker.cs
@@ -12,18 +12,18 @@ namespace Avalonia.Controls
///
/// A control to allow the user to select a time.
///
- [TemplatePart("FirstColumnDivider", typeof(Rectangle))]
- [TemplatePart("FirstPickerHost", typeof(Border))]
- [TemplatePart("FlyoutButton", typeof(Button))]
- [TemplatePart("FlyoutButtonContentGrid", typeof(Grid))]
- [TemplatePart("HourTextBlock", typeof(TextBlock))]
- [TemplatePart("MinuteTextBlock", typeof(TextBlock))]
- [TemplatePart("PeriodTextBlock", typeof(TextBlock))]
- [TemplatePart("PickerPresenter", typeof(TimePickerPresenter))]
- [TemplatePart("Popup", typeof(Popup))]
- [TemplatePart("SecondColumnDivider", typeof(Rectangle))]
- [TemplatePart("SecondPickerHost", typeof(Border))]
- [TemplatePart("ThirdPickerHost", typeof(Border))]
+ [TemplatePart("PART_FirstColumnDivider", typeof(Rectangle))]
+ [TemplatePart("PART_FirstPickerHost", typeof(Border))]
+ [TemplatePart("PART_FlyoutButton", typeof(Button))]
+ [TemplatePart("PART_FlyoutButtonContentGrid", typeof(Grid))]
+ [TemplatePart("PART_HourTextBlock", typeof(TextBlock))]
+ [TemplatePart("PART_MinuteTextBlock", typeof(TextBlock))]
+ [TemplatePart("PART_PeriodTextBlock", typeof(TextBlock))]
+ [TemplatePart("PART_PickerPresenter", typeof(TimePickerPresenter))]
+ [TemplatePart("PART_Popup", typeof(Popup))]
+ [TemplatePart("PART_SecondColumnDivider", typeof(Rectangle))]
+ [TemplatePart("PART_SecondPickerHost", typeof(Border))]
+ [TemplatePart("PART_ThirdPickerHost", typeof(Border))]
[PseudoClasses(":hasnotime")]
public class TimePicker : TemplatedControl
{
@@ -169,23 +169,23 @@ namespace Avalonia.Controls
}
base.OnApplyTemplate(e);
- _flyoutButton = e.NameScope.Find("FlyoutButton");
+ _flyoutButton = e.NameScope.Find("PART_FlyoutButton");
- _firstPickerHost = e.NameScope.Find("FirstPickerHost");
- _secondPickerHost = e.NameScope.Find("SecondPickerHost");
- _thirdPickerHost = e.NameScope.Find("ThirdPickerHost");
+ _firstPickerHost = e.NameScope.Find("PART_FirstPickerHost");
+ _secondPickerHost = e.NameScope.Find("PART_SecondPickerHost");
+ _thirdPickerHost = e.NameScope.Find("PART_ThirdPickerHost");
- _hourText = e.NameScope.Find("HourTextBlock");
- _minuteText = e.NameScope.Find("MinuteTextBlock");
- _periodText = e.NameScope.Find("PeriodTextBlock");
+ _hourText = e.NameScope.Find("PART_HourTextBlock");
+ _minuteText = e.NameScope.Find("PART_MinuteTextBlock");
+ _periodText = e.NameScope.Find("PART_PeriodTextBlock");
- _firstSplitter = e.NameScope.Find("FirstColumnDivider");
- _secondSplitter = e.NameScope.Find("SecondColumnDivider");
+ _firstSplitter = e.NameScope.Find("PART_FirstColumnDivider");
+ _secondSplitter = e.NameScope.Find("PART_SecondColumnDivider");
- _contentGrid = e.NameScope.Find("FlyoutButtonContentGrid");
+ _contentGrid = e.NameScope.Find("PART_FlyoutButtonContentGrid");
- _popup = e.NameScope.Find("Popup");
- _presenter = e.NameScope.Find("PickerPresenter");
+ _popup = e.NameScope.Find("PART_Popup");
+ _presenter = e.NameScope.Find("PART_PickerPresenter");
if (_flyoutButton != null)
_flyoutButton.Click += OnFlyoutButtonClicked;
diff --git a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
index 46c5eaeaaa..d6599c9f18 100644
--- a/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
+++ b/src/Avalonia.Controls/DateTimePickers/TimePickerPresenter.cs
@@ -11,20 +11,20 @@ namespace Avalonia.Controls
/// Defines the presenter used for selecting a time. Intended for use with
/// but can be used independently
///
- [TemplatePart("AcceptButton", typeof(Button))]
- [TemplatePart("DismissButton", typeof(Button))]
- [TemplatePart("HourDownButton", typeof(RepeatButton))]
- [TemplatePart("HourSelector", typeof(DateTimePickerPanel))]
- [TemplatePart("HourUpButton", typeof(RepeatButton))]
- [TemplatePart("MinuteDownButton", typeof(RepeatButton))]
- [TemplatePart("MinuteSelector", typeof(DateTimePickerPanel))]
- [TemplatePart("MinuteUpButton", typeof(RepeatButton))]
- [TemplatePart("PeriodDownButton", typeof(RepeatButton))]
- [TemplatePart("PeriodHost", typeof(Panel))]
- [TemplatePart("PeriodSelector", typeof(DateTimePickerPanel))]
- [TemplatePart("PeriodUpButton", typeof(RepeatButton))]
- [TemplatePart("PickerContainer", typeof(Grid))]
- [TemplatePart("SecondSpacer", typeof(Rectangle))]
+ [TemplatePart("PART_AcceptButton", typeof(Button))]
+ [TemplatePart("PART_DismissButton", typeof(Button))]
+ [TemplatePart("PART_HourDownButton", typeof(RepeatButton))]
+ [TemplatePart("PART_HourSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("PART_HourUpButton", typeof(RepeatButton))]
+ [TemplatePart("PART_MinuteDownButton", typeof(RepeatButton))]
+ [TemplatePart("PART_MinuteSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("PART_MinuteUpButton", typeof(RepeatButton))]
+ [TemplatePart("PART_PeriodDownButton", typeof(RepeatButton))]
+ [TemplatePart("PART_PeriodHost", typeof(Panel))]
+ [TemplatePart("PART_PeriodSelector", typeof(DateTimePickerPanel))]
+ [TemplatePart("PART_PeriodUpButton", typeof(RepeatButton))]
+ [TemplatePart("PART_PickerContainer", typeof(Grid))]
+ [TemplatePart("PART_SecondSpacer", typeof(Rectangle))]
public class TimePickerPresenter : PickerPresenterBase
{
///
@@ -122,40 +122,40 @@ namespace Avalonia.Controls
{
base.OnApplyTemplate(e);
- _pickerContainer = e.NameScope.Get("PickerContainer");
- _periodHost = e.NameScope.Get("PeriodHost");
+ _pickerContainer = e.NameScope.Get("PART_PickerContainer");
+ _periodHost = e.NameScope.Get("PART_PeriodHost");
- _hourSelector = e.NameScope.Get("HourSelector");
- _minuteSelector = e.NameScope.Get("MinuteSelector");
- _periodSelector = e.NameScope.Get("PeriodSelector");
+ _hourSelector = e.NameScope.Get("PART_HourSelector");
+ _minuteSelector = e.NameScope.Get("PART_MinuteSelector");
+ _periodSelector = e.NameScope.Get("PART_PeriodSelector");
- _spacer2 = e.NameScope.Get("SecondSpacer");
+ _spacer2 = e.NameScope.Get("PART_SecondSpacer");
- _acceptButton = e.NameScope.Get("AcceptButton");
+ _acceptButton = e.NameScope.Get("PART_AcceptButton");
_acceptButton.Click += OnAcceptButtonClicked;
- _hourUpButton = e.NameScope.Find("HourUpButton");
+ _hourUpButton = e.NameScope.Find("PART_HourUpButton");
if (_hourUpButton != null)
_hourUpButton.Click += OnSelectorButtonClick;
- _hourDownButton = e.NameScope.Find("HourDownButton");
+ _hourDownButton = e.NameScope.Find("PART_HourDownButton");
if (_hourDownButton != null)
_hourDownButton.Click += OnSelectorButtonClick;
- _minuteUpButton = e.NameScope.Find("MinuteUpButton");
+ _minuteUpButton = e.NameScope.Find("PART_MinuteUpButton");
if (_minuteUpButton != null)
_minuteUpButton.Click += OnSelectorButtonClick;
- _minuteDownButton = e.NameScope.Find("MinuteDownButton");
+ _minuteDownButton = e.NameScope.Find("PART_MinuteDownButton");
if (_minuteDownButton != null)
_minuteDownButton.Click += OnSelectorButtonClick;
- _periodUpButton = e.NameScope.Find("PeriodUpButton");
+ _periodUpButton = e.NameScope.Find("PART_PeriodUpButton");
if (_periodUpButton != null)
_periodUpButton.Click += OnSelectorButtonClick;
- _periodDownButton = e.NameScope.Find("PeriodDownButton");
+ _periodDownButton = e.NameScope.Find("PART_PeriodDownButton");
if (_periodDownButton != null)
_periodDownButton.Click += OnSelectorButtonClick;
- _dismissButton = e.NameScope.Find("DismissButton");
+ _dismissButton = e.NameScope.Find("PART_DismissButton");
if (_dismissButton != null)
_dismissButton.Click += OnDismissButtonClicked;
diff --git a/src/Avalonia.Controls/DesktopApplicationExtensions.cs b/src/Avalonia.Controls/DesktopApplicationExtensions.cs
index ddd4e57a40..ddaed6bb0d 100644
--- a/src/Avalonia.Controls/DesktopApplicationExtensions.cs
+++ b/src/Avalonia.Controls/DesktopApplicationExtensions.cs
@@ -8,8 +8,6 @@ namespace Avalonia.Controls
{
public static class DesktopApplicationExtensions
{
- [Obsolete("Running application without a cancellation token and a lifetime is no longer supported, see https://github.com/AvaloniaUI/Avalonia/wiki/Application-lifetimes for details", true)]
- public static void Run(this Application app) => throw new NotSupportedException();
///
/// On desktop-style platforms runs the application's main loop until closable is closed
diff --git a/src/Avalonia.Controls/Documents/InlineUIContainer.cs b/src/Avalonia.Controls/Documents/InlineUIContainer.cs
index 5f08c23099..d632e5fea7 100644
--- a/src/Avalonia.Controls/Documents/InlineUIContainer.cs
+++ b/src/Avalonia.Controls/Documents/InlineUIContainer.cs
@@ -87,18 +87,7 @@ namespace Avalonia.Controls.Documents
public override TextRunProperties? Properties { get; }
- public override Size Size
- {
- get
- {
- if (!Control.IsMeasureValid)
- {
- Control.Measure(Size.Infinity);
- }
-
- return Control.DesiredSize;
- }
- }
+ public override Size Size => Control.DesiredSize;
public override double Baseline
{
@@ -118,7 +107,7 @@ namespace Avalonia.Controls.Documents
public override void Draw(DrawingContext drawingContext, Point origin)
{
- Control.Arrange(new Rect(origin, Size));
+ //noop
}
}
}
diff --git a/src/Avalonia.Controls/DrawingPresenter.cs b/src/Avalonia.Controls/DrawingPresenter.cs
deleted file mode 100644
index ee27aa7ec1..0000000000
--- a/src/Avalonia.Controls/DrawingPresenter.cs
+++ /dev/null
@@ -1,61 +0,0 @@
-using System;
-using Avalonia.Controls.Shapes;
-using Avalonia.Media;
-using Avalonia.Metadata;
-
-namespace Avalonia.Controls
-{
- [Obsolete("Use Image control with DrawingImage source")]
- public class DrawingPresenter : Control
- {
- static DrawingPresenter()
- {
- AffectsMeasure(DrawingProperty);
- AffectsRender(DrawingProperty);
- }
-
- public static readonly StyledProperty DrawingProperty =
- AvaloniaProperty.Register(nameof(Drawing));
-
- public static readonly StyledProperty StretchProperty =
- AvaloniaProperty.Register(nameof(Stretch), Stretch.Uniform);
-
- [Content]
- public Drawing Drawing
- {
- get => GetValue(DrawingProperty);
- set => SetValue(DrawingProperty, value);
- }
-
- public Stretch Stretch
- {
- get => GetValue(StretchProperty);
- set => SetValue(StretchProperty, value);
- }
-
- private Matrix _transform = Matrix.Identity;
-
- protected override Size MeasureOverride(Size availableSize)
- {
- if (Drawing == null) return new Size();
-
- var (size, transform) = Shape.CalculateSizeAndTransform(availableSize, Drawing.GetBounds(), Stretch);
-
- _transform = transform;
-
- return size;
- }
-
- public override void Render(DrawingContext context)
- {
- if (Drawing != null)
- {
- using (context.PushPreTransform(_transform))
- using (context.PushClip(new Rect(Bounds.Size)))
- {
- Drawing.Draw(context);
- }
- }
- }
- }
-}
diff --git a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
index 1504d2b25f..00ebcab70e 100644
--- a/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
+++ b/src/Avalonia.Controls/Flyouts/FlyoutBase.cs
@@ -12,17 +12,12 @@ namespace Avalonia.Controls.Primitives
{
public abstract class FlyoutBase : AvaloniaObject, IPopupHostProvider
{
- static FlyoutBase()
- {
- Control.ContextFlyoutProperty.Changed.Subscribe(OnContextFlyoutPropertyChanged);
- }
-
///
/// Defines the property
///
public static readonly DirectProperty IsOpenProperty =
- AvaloniaProperty.RegisterDirect(nameof(IsOpen),
- x => x.IsOpen);
+ AvaloniaProperty.RegisterDirect(nameof(IsOpen),
+ x => x.IsOpen);
///
/// Defines the property
@@ -43,6 +38,14 @@ namespace Avalonia.Controls.Primitives
AvaloniaProperty.RegisterDirect(nameof(ShowMode),
x => x.ShowMode, (x, v) => x.ShowMode = v);
+ ///
+ /// Defines the property
+ ///
+ public static readonly DirectProperty OverlayInputPassThroughElementProperty =
+ Popup.OverlayInputPassThroughElementProperty.AddOwner(
+ o => o._overlayInputPassThroughElement,
+ (o, v) => o._overlayInputPassThroughElement = v);
+
///
/// Defines the AttachedFlyout property
///
@@ -57,6 +60,12 @@ namespace Avalonia.Controls.Primitives
private PixelRect? _enlargePopupRectScreenPixelRect;
private IDisposable? _transientDisposable;
private Action? _popupHostChangedHandler;
+ private IInputElement? _overlayInputPassThroughElement;
+
+ static FlyoutBase()
+ {
+ Control.ContextFlyoutProperty.Changed.Subscribe(OnContextFlyoutPropertyChanged);
+ }
public FlyoutBase()
{
@@ -101,11 +110,21 @@ namespace Avalonia.Controls.Primitives
private set => SetAndRaise(TargetProperty, ref _target, value);
}
+ ///
+ /// Gets or sets an element that should receive pointer input events even when underneath
+ /// the flyout's overlay.
+ ///
+ public IInputElement? OverlayInputPassThroughElement
+ {
+ get => _overlayInputPassThroughElement;
+ set => SetAndRaise(OverlayInputPassThroughElementProperty, ref _overlayInputPassThroughElement, value);
+ }
+
IPopupHost? IPopupHostProvider.PopupHost => Popup?.Host;
- event Action? IPopupHostProvider.PopupHostChanged
- {
- add => _popupHostChangedHandler += value;
+ event Action? IPopupHostProvider.PopupHostChanged
+ {
+ add => _popupHostChangedHandler += value;
remove => _popupHostChangedHandler -= value;
}
@@ -175,8 +194,9 @@ namespace Avalonia.Controls.Primitives
IsOpen = false;
Popup.IsOpen = false;
+
((ISetLogicalParent)Popup).SetParent(null);
-
+
// Ensure this isn't active
_transientDisposable?.Dispose();
_transientDisposable = null;
@@ -231,6 +251,8 @@ namespace Avalonia.Controls.Primitives
Popup.Child = CreatePresenter();
}
+ Popup.OverlayInputPassThroughElement = OverlayInputPassThroughElement;
+
if (CancelOpening())
{
return false;
@@ -356,10 +378,13 @@ namespace Avalonia.Controls.Primitives
private Popup CreatePopup()
{
- var popup = new Popup();
- popup.WindowManagerAddShadowHint = false;
- popup.IsLightDismissEnabled = true;
- popup.OverlayDismissEventPassThrough = true;
+ var popup = new Popup
+ {
+ WindowManagerAddShadowHint = false,
+ IsLightDismissEnabled = true,
+ //Note: This is required to prevent Button.Flyout from opening the flyout again after dismiss.
+ OverlayDismissEventPassThrough = false
+ };
popup.Opened += OnPopupOpened;
popup.Closed += OnPopupClosed;
@@ -372,7 +397,7 @@ namespace Avalonia.Controls.Primitives
{
IsOpen = true;
- _popupHostChangedHandler?.Invoke(Popup!.Host);
+ _popupHostChangedHandler?.Invoke(Popup.Host);
}
private void OnPopupClosing(object? sender, CancelEventArgs e)
diff --git a/src/Avalonia.Controls/GridLength.cs b/src/Avalonia.Controls/GridLength.cs
index d7022b80ce..0e2f8e7d0c 100644
--- a/src/Avalonia.Controls/GridLength.cs
+++ b/src/Avalonia.Controls/GridLength.cs
@@ -180,7 +180,7 @@ namespace Avalonia.Controls
return "Auto";
}
- string s = _value.ToString();
+ string s = _value.ToString(CultureInfo.InvariantCulture);
return IsStar ? s + "*" : s;
}
diff --git a/src/Avalonia.Controls/ListBox.cs b/src/Avalonia.Controls/ListBox.cs
index 79285bb86b..80b5259a53 100644
--- a/src/Avalonia.Controls/ListBox.cs
+++ b/src/Avalonia.Controls/ListBox.cs
@@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Selection;
using Avalonia.Controls.Templates;
using Avalonia.Input;
+using Avalonia.Input.Platform;
using Avalonia.VisualTree;
namespace Avalonia.Controls
@@ -157,7 +158,7 @@ namespace Avalonia.Controls
e.Source,
true,
e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
- e.KeyModifiers.HasAllFlags(KeyModifiers.Control),
+ e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService().CommandModifiers),
point.Properties.IsRightButtonPressed);
}
}
diff --git a/src/Avalonia.Controls/LoggingExtensions.cs b/src/Avalonia.Controls/LoggingExtensions.cs
index 9eb3b140f6..ef14d0b477 100644
--- a/src/Avalonia.Controls/LoggingExtensions.cs
+++ b/src/Avalonia.Controls/LoggingExtensions.cs
@@ -1,21 +1,10 @@
-using System;
-using Avalonia.Controls;
+using Avalonia.Controls;
using Avalonia.Logging;
namespace Avalonia
{
public static class LoggingExtensions
{
- [Obsolete("Use LogToTrace")]
- public static T LogToDebug(
- this T builder,
- LogEventLevel level = LogEventLevel.Warning,
- params string[] areas)
- where T : AppBuilderBase, new()
- {
- return LogToTrace(builder, level, areas);
- }
-
///
/// Logs Avalonia events to the sink.
///
diff --git a/src/Avalonia.Controls/NativeMenuItem.cs b/src/Avalonia.Controls/NativeMenuItem.cs
index 265e7119eb..d22fdb2f84 100644
--- a/src/Avalonia.Controls/NativeMenuItem.cs
+++ b/src/Avalonia.Controls/NativeMenuItem.cs
@@ -186,13 +186,6 @@ namespace Avalonia.Controls
///
public event EventHandler? Click;
- [Obsolete("Use Click event.")]
- public event EventHandler Clicked
- {
- add => Click += value;
- remove => Click -= value;
- }
-
void INativeMenuItemExporterEventsImplBridge.RaiseClicked()
{
Click?.Invoke(this, new EventArgs());
diff --git a/src/Avalonia.Controls/NativeMenuItemSeparator.cs b/src/Avalonia.Controls/NativeMenuItemSeparator.cs
index 49b36e714d..97278130b1 100644
--- a/src/Avalonia.Controls/NativeMenuItemSeparator.cs
+++ b/src/Avalonia.Controls/NativeMenuItemSeparator.cs
@@ -1,16 +1,7 @@
-using System;
-
-namespace Avalonia.Controls
+namespace Avalonia.Controls
{
-
- [Obsolete("This class exists to maintain backwards compatibility with existing code. Use NativeMenuItemSeparator instead")]
- public class NativeMenuItemSeperator : NativeMenuItemSeparator
- {
- }
-
public class NativeMenuItemSeparator : NativeMenuItemBase
{
- [Obsolete("This is a temporary hack to make our MenuItem recognize this as a separator, don't use", true)]
- public string Header => "-";
+
}
}
diff --git a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
index 2449f4c15c..630276767a 100644
--- a/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
+++ b/src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
@@ -102,15 +102,12 @@ namespace Avalonia.Controls.Notifications
Content = content
};
- if (notification != null)
+ notificationControl.NotificationClosed += (sender, args) =>
{
- notificationControl.NotificationClosed += (sender, args) =>
- {
- notification.OnClose?.Invoke();
+ notification?.OnClose?.Invoke();
- _items?.Remove(sender);
- };
- }
+ _items?.Remove(sender);
+ };
notificationControl.PointerPressed += (sender, args) =>
{
diff --git a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
index 705e68e3ea..b607ced10a 100644
--- a/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
+++ b/src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
@@ -5,6 +5,7 @@ using System.Linq;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Data;
+using Avalonia.Data.Converters;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
@@ -45,14 +46,6 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect(nameof(ClipValueToMinMax),
updown => updown.ClipValueToMinMax, (updown, b) => updown.ClipValueToMinMax = b);
- ///
- /// Defines the property.
- ///
- [Obsolete]
- public static readonly DirectProperty CultureInfoProperty =
- AvaloniaProperty.RegisterDirect(nameof(CultureInfo), o => o.CultureInfo,
- (o, v) => o.CultureInfo = v, CultureInfo.CurrentCulture);
-
///
/// Defines the property.
///
@@ -104,6 +97,13 @@ namespace Avalonia.Controls
AvaloniaProperty.RegisterDirect(nameof(Text), o => o.Text, (o, v) => o.Text = v,
defaultBindingMode: BindingMode.TwoWay, enableDataValidation: true);
+ ///
+ /// Defines the property.
+ ///
+ public static readonly DirectProperty TextConverterProperty =
+ AvaloniaProperty.RegisterDirect(nameof(TextConverter),
+ updown => updown.TextConverter, (o, v) => o.TextConverter = v, null, BindingMode.OneWay, false);
+
///
/// Defines the property.
///
@@ -133,11 +133,11 @@ namespace Avalonia.Controls
private decimal? _value;
private string? _text;
+ private IValueConverter? _textConverter;
private bool _internalValueSet;
private bool _clipValueToMinMax;
private bool _isSyncingTextAndValueProperties;
private bool _isTextChangedFromUI;
- private CultureInfo? _cultureInfo;
private NumberStyles _parsingNumberStyle = NumberStyles.Any;
private NumberFormatInfo? _numberFormat;
@@ -187,21 +187,6 @@ namespace Avalonia.Controls
set { SetAndRaise(ClipValueToMinMaxProperty, ref _clipValueToMinMax, value); }
}
- ///
- /// Gets or sets the current CultureInfo.
- ///
- [Obsolete("CultureInfo is obsolete, please use NumberFormat instead.")]
- public CultureInfo? CultureInfo
- {
- get { return _cultureInfo; }
- set
- {
- SetAndRaise(CultureInfoProperty, ref _cultureInfo, value);
- //Set and Raise the NumberFormatProperty when CultureInfo is changed.
- SetAndRaise(NumberFormatProperty, ref _numberFormat, value?.NumberFormat);
- }
- }
-
///
/// Gets or sets the current NumberFormatInfo
///
@@ -258,6 +243,8 @@ namespace Avalonia.Controls
///
/// Gets or sets the parsing style (AllowLeadingWhite, Float, AllowHexSpecifier, ...). By default, Any.
+ /// Note that Hex style does not work with decimal.
+ /// For hexadecimal display, use .
///
public NumberStyles ParsingNumberStyle
{
@@ -274,6 +261,17 @@ namespace Avalonia.Controls
set { SetAndRaise(TextProperty, ref _text, value); }
}
+ ///
+ /// Gets or sets the custom bidirectional Text-Value converter.
+ /// Non-null converter overrides , providing finer control over
+ /// string representation of the underlying value.
+ ///
+ public IValueConverter? TextConverter
+ {
+ get { return _textConverter; }
+ set { SetAndRaise(TextConverterProperty, ref _textConverter, value); }
+ }
+
///
/// Gets or sets the value.
///
@@ -335,9 +333,6 @@ namespace Avalonia.Controls
///
static NumericUpDown()
{
-#pragma warning disable CS0612 // Type or member is obsolete
- CultureInfoProperty.Changed.Subscribe(OnCultureInfoChanged);
-#pragma warning restore CS0612 // Type or member is obsolete
NumberFormatProperty.Changed.Subscribe(OnNumberFormatChanged);
FormatStringProperty.Changed.Subscribe(FormatStringChanged);
IncrementProperty.Changed.Subscribe(IncrementChanged);
@@ -345,6 +340,7 @@ namespace Avalonia.Controls
MaximumProperty.Changed.Subscribe(OnMaximumChanged);
MinimumProperty.Changed.Subscribe(OnMinimumChanged);
TextProperty.Changed.Subscribe(OnTextChanged);
+ TextConverterProperty.Changed.Subscribe(OnTextConverterChanged);
ValueProperty.Changed.Subscribe(OnValueChanged);
}
@@ -416,19 +412,6 @@ namespace Avalonia.Controls
}
}
- ///
- /// Called when the property value changed.
- ///
- /// The old value.
- /// The new value.
- protected virtual void OnCultureInfoChanged(CultureInfo? oldValue, CultureInfo? newValue)
- {
- if (IsInitialized)
- {
- SyncTextAndValueProperties(false, null);
- }
- }
-
///
/// Called when the property value changed.
///
@@ -524,6 +507,19 @@ namespace Avalonia.Controls
SyncTextAndValueProperties(true, Text);
}
}
+
+ ///
+ /// Called when the property value changed.
+ ///
+ /// The old value.
+ /// The new value.
+ protected virtual void OnTextConverterChanged(IValueConverter? oldValue, IValueConverter? newValue)
+ {
+ if (IsInitialized)
+ {
+ SyncTextAndValueProperties(false, null);
+ }
+ }
///
/// Called when the property value changed.
@@ -651,6 +647,10 @@ namespace Avalonia.Controls
///
private string? ConvertValueToText()
{
+ if (TextConverter != null)
+ {
+ return TextConverter.ConvertBack(Value, typeof(string), null, CultureInfo.CurrentCulture)?.ToString();
+ }
//Manage FormatString of type "{}{0:N2} °" (in xaml) or "{0:N2} °" in code-behind.
if (FormatString.Contains("{0"))
{
@@ -729,20 +729,6 @@ namespace Avalonia.Controls
}
}
- ///
- /// Called when the property value changed.
- ///
- /// The event args.
- private static void OnCultureInfoChanged(AvaloniaPropertyChangedEventArgs e)
- {
- if (e.Sender is NumericUpDown upDown)
- {
- var oldValue = (CultureInfo?)e.OldValue;
- var newValue = (CultureInfo?)e.NewValue;
- upDown.OnCultureInfoChanged(oldValue, newValue);
- }
- }
-
///
/// Called when the property value changed.
///
@@ -841,6 +827,21 @@ namespace Avalonia.Controls
}
}
+ ///
+ /// Called when the property value changed.
+ ///
+ /// The event args.
+ private static void OnTextConverterChanged(AvaloniaPropertyChangedEventArgs e)
+ {
+ if (e.Sender is NumericUpDown upDown)
+ {
+ var oldValue = (IValueConverter?)e.OldValue;
+ var newValue = (IValueConverter?)e.NewValue;
+ upDown.OnTextConverterChanged(oldValue, newValue);
+ }
+ }
+
+
///
/// Called when the property value changed.
///
@@ -1065,6 +1066,12 @@ namespace Avalonia.Controls
return null;
}
+ if (TextConverter != null)
+ {
+ var valueFromText = TextConverter.Convert(text, typeof(decimal?), null, CultureInfo.CurrentCulture);
+ return (decimal?)valueFromText;
+ }
+
if (IsPercent(FormatString))
{
result = ParsePercent(text, NumberFormat);
diff --git a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
index 630d2d8efb..e1f6db9c60 100644
--- a/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
+++ b/src/Avalonia.Controls/Platform/InternalPlatformThreadingInterface.cs
@@ -43,7 +43,7 @@ namespace Avalonia.Controls.Platform
_priority = priority;
_interval = interval;
_tick = tick;
- _timer = new Timer(OnTimer, null, interval, TimeSpan.FromMilliseconds(-1));
+ _timer = new Timer(OnTimer, null, interval, Timeout.InfiniteTimeSpan);
_handle = GCHandle.Alloc(_timer);
}
@@ -57,7 +57,7 @@ namespace Avalonia.Controls.Platform
if (_timer == null)
return;
_tick();
- _timer?.Change(_interval, TimeSpan.FromMilliseconds(-1));
+ _timer?.Change(_interval, Timeout.InfiniteTimeSpan);
});
}
diff --git a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
index 39a512a773..4db2ec6d50 100644
--- a/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
+++ b/src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
@@ -187,7 +187,7 @@ namespace Avalonia.Controls.Presenters
break;
case NotifyCollectionChangedAction.Remove:
- if ((e.OldStartingIndex >= FirstIndex && e.OldStartingIndex < NextIndex) ||
+ if (e.OldStartingIndex < NextIndex ||
panel.Children.Count > ItemCount)
{
RecycleContainersOnRemove();
diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs
index e463bc5731..e540f58195 100644
--- a/src/Avalonia.Controls/Presenters/TextPresenter.cs
+++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs
@@ -9,6 +9,7 @@ using Avalonia.VisualTree;
using Avalonia.Layout;
using Avalonia.Media.Immutable;
using Avalonia.Controls.Documents;
+using Avalonia.Media.TextFormatting.Unicode;
namespace Avalonia.Controls.Presenters
{
@@ -496,14 +497,14 @@ namespace Avalonia.Controls.Presenters
var length = Math.Max(selectionStart, selectionEnd) - start;
IReadOnlyList>? textStyleOverrides = null;
-
- if (length > 0)
+
+ if (length > 0 && SelectionForegroundBrush != null)
{
textStyleOverrides = new[]
{
new ValueSpan(start, length,
new GenericTextRunProperties(typeface, FontSize,
- foregroundBrush: SelectionForegroundBrush ?? Brushes.White))
+ foregroundBrush: SelectionForegroundBrush))
};
}
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 1501d97470..ccb81ba276 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -22,9 +22,7 @@ namespace Avalonia.Controls.Primitives
///
/// Displays a popup window.
///
-#pragma warning disable CS0612 // Type or member is obsolete
- public class Popup : Control, IVisualTreeHost, IPopupHostProvider
-#pragma warning restore CS0612 // Type or member is obsolete
+ public class Popup : Control, IPopupHostProvider
{
public static readonly StyledProperty WindowManagerAddShadowHintProperty =
AvaloniaProperty.Register(nameof(WindowManagerAddShadowHint), false);
@@ -90,14 +88,6 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty PlacementTargetProperty =
AvaloniaProperty.Register(nameof(PlacementTarget));
-#pragma warning disable 618
- ///
- /// Defines the property.
- ///
- public static readonly StyledProperty ObeyScreenEdgesProperty =
- AvaloniaProperty.Register(nameof(ObeyScreenEdges), true);
-#pragma warning restore 618
-
public static readonly StyledProperty OverlayDismissEventPassThroughProperty =
AvaloniaProperty.Register(nameof(OverlayDismissEventPassThrough));
@@ -125,17 +115,6 @@ namespace Avalonia.Controls.Primitives
public static readonly StyledProperty VerticalOffsetProperty =
AvaloniaProperty.Register(nameof(VerticalOffset));
- ///
- /// Defines the property.
- ///
- [Obsolete("Use IsLightDismissEnabledProperty")]
- public static readonly DirectProperty StaysOpenProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(StaysOpen),
- o => o.StaysOpen,
- (o, v) => o.StaysOpen = v,
- true);
-
///
/// Defines the property.
///
@@ -301,13 +280,6 @@ namespace Avalonia.Controls.Primitives
set { SetValue(PlacementTargetProperty, value); }
}
- [Obsolete("This property has no effect")]
- public bool ObeyScreenEdges
- {
- get => GetValue(ObeyScreenEdgesProperty);
- set => SetValue(ObeyScreenEdgesProperty, value);
- }
-
///
/// Gets or sets a value indicating whether the event that closes the popup is passed
/// through to the parent window.
@@ -352,17 +324,6 @@ namespace Avalonia.Controls.Primitives
set { SetValue(VerticalOffsetProperty, value); }
}
- ///
- /// Gets or sets a value indicating whether the popup should stay open when the popup is
- /// pressed or loses focus.
- ///
- [Obsolete("Use IsLightDismissEnabled")]
- public bool StaysOpen
- {
- get => !IsLightDismissEnabled;
- set => IsLightDismissEnabled = !value;
- }
-
///
/// Gets or sets whether this popup appears on top of all other windows
///
@@ -372,11 +333,6 @@ namespace Avalonia.Controls.Primitives
set { SetValue(TopmostProperty, value); }
}
- ///
- /// Gets the root of the popup window.
- ///
- IVisual? IVisualTreeHost.Root => _openState?.PopupHost.HostedVisualTreeRoot;
-
IPopupHost? IPopupHostProvider.PopupHost => Host;
event Action? IPopupHostProvider.PopupHostChanged
@@ -501,7 +457,7 @@ namespace Avalonia.Controls.Primitives
if (dismissLayer != null)
{
dismissLayer.IsVisible = true;
- dismissLayer.InputPassThroughElement = _overlayInputPassThroughElement;
+ dismissLayer.InputPassThroughElement = OverlayInputPassThroughElement;
Disposable.Create(() =>
{
@@ -568,6 +524,7 @@ namespace Avalonia.Controls.Primitives
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
{
+ base.OnPropertyChanged(change);
if (_openState is not null)
{
if (change.Property == WidthProperty ||
diff --git a/src/Avalonia.Controls/Primitives/TemplatedControl.cs b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
index dc52cc3ae2..2441da8920 100644
--- a/src/Avalonia.Controls/Primitives/TemplatedControl.cs
+++ b/src/Avalonia.Controls/Primitives/TemplatedControl.cs
@@ -297,9 +297,6 @@ namespace Avalonia.Controls.Primitives
var e = new TemplateAppliedEventArgs(nameScope);
OnApplyTemplate(e);
-#pragma warning disable CS0618 // Type or member is obsolete
- OnTemplateApplied(e);
-#pragma warning restore CS0618 // Type or member is obsolete
RaiseEvent(e);
}
@@ -378,15 +375,6 @@ namespace Avalonia.Controls.Primitives
}
}
- ///
- /// Called when the control's template is applied.
- ///
- /// The event args.
- [Obsolete("Use OnApplyTemplate")]
- protected virtual void OnTemplateApplied(TemplateAppliedEventArgs e)
- {
- }
-
///
/// Called when the property changes.
///
diff --git a/src/Avalonia.Controls/ProgressBar.cs b/src/Avalonia.Controls/ProgressBar.cs
index 56b9caf085..1e406157d7 100644
--- a/src/Avalonia.Controls/ProgressBar.cs
+++ b/src/Avalonia.Controls/ProgressBar.cs
@@ -119,14 +119,12 @@ namespace Avalonia.Controls
nameof(Percentage),
o => o.Percentage);
- [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
public static readonly DirectProperty IndeterminateStartingOffsetProperty =
AvaloniaProperty.RegisterDirect(
nameof(IndeterminateStartingOffset),
p => p.IndeterminateStartingOffset,
(p, o) => p.IndeterminateStartingOffset = o);
- [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
public static readonly DirectProperty IndeterminateEndingOffsetProperty =
AvaloniaProperty.RegisterDirect(
nameof(IndeterminateEndingOffset),
@@ -139,14 +137,12 @@ namespace Avalonia.Controls
private set { SetAndRaise(PercentageProperty, ref _percentage, value); }
}
- [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
public double IndeterminateStartingOffset
{
get => _indeterminateStartingOffset;
set => SetAndRaise(IndeterminateStartingOffsetProperty, ref _indeterminateStartingOffset, value);
}
- [Obsolete("To be removed when Avalonia.Themes.Default is discontinued.")]
public double IndeterminateEndingOffset
{
get => _indeterminateEndingOffset;
@@ -255,11 +251,9 @@ namespace Avalonia.Controls
TemplateProperties.Container2AnimationEndPosition = barIndicatorWidth2 * 1.66; // Position at 166%
-#pragma warning disable CS0618 // Type or member is obsolete
// Remove these properties when we switch to fluent as default and removed the old one.
IndeterminateStartingOffset = -dim;
IndeterminateEndingOffset = dim;
-#pragma warning restore CS0618 // Type or member is obsolete
var padding = Padding;
var rectangle = new RectangleGeometry(
diff --git a/src/Avalonia.Controls/RelativePanel.cs b/src/Avalonia.Controls/RelativePanel.cs
index 1125f0eb0e..ab8e064028 100644
--- a/src/Avalonia.Controls/RelativePanel.cs
+++ b/src/Avalonia.Controls/RelativePanel.cs
@@ -36,7 +36,10 @@ namespace Avalonia.Controls
foreach (Layoutable child in Children)
{
if (child == null)
+ {
continue;
+ }
+
var node = _childGraph.AddNode(child);
node.AlignLeftWithNode = _childGraph.AddLink(node, GetDependencyElement(AlignLeftWithProperty, child));
@@ -51,17 +54,18 @@ namespace Avalonia.Controls
node.AlignHorizontalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignHorizontalCenterWithProperty, child));
node.AlignVerticalCenterWith = _childGraph.AddLink(node, GetDependencyElement(AlignVerticalCenterWithProperty, child));
-
}
+
_childGraph.Measure(availableSize);
_childGraph.Reset(false);
- var calcWidth = Width.IsNaN() && HorizontalAlignment != HorizontalAlignment.Stretch;
- var calcHeight = Height.IsNaN() && VerticalAlignment != VerticalAlignment.Stretch;
+ var calcWidth = Width.IsNaN() && (HorizontalAlignment != HorizontalAlignment.Stretch);
+ var calcHeight = Height.IsNaN() && (VerticalAlignment != VerticalAlignment.Stretch);
var boundingSize = _childGraph.GetBoundingSize(calcWidth, calcHeight);
_childGraph.Reset();
_childGraph.Measure(boundingSize);
+
return boundingSize;
}
@@ -171,6 +175,7 @@ namespace Avalonia.Controls
prevSize = prevSize.WithWidth(prevSize.Width + prevNode.OriginDesiredSize.Width);
prevNode.HorizontalOffsetFlag = true;
}
+
if (node.VerticalOffsetFlag)
{
prevNode.VerticalOffsetFlag = true;
@@ -186,6 +191,7 @@ namespace Avalonia.Controls
prevSize = prevSize.WithHeight(prevSize.Height + node.OriginDesiredSize.Height);
prevNode.VerticalOffsetFlag = true;
}
+
if (node.HorizontalOffsetFlag)
{
prevNode.HorizontalOffsetFlag = true;
@@ -269,16 +275,16 @@ namespace Avalonia.Controls
MeasureChild(node);
continue;
}
-
+
if (node.OutgoingNodes.All(item => item.Measured))
{
MeasureChild(node);
continue;
}
-
+
if (!set.Add(node.Element))
throw new Exception("RelativePanel error: Circular dependency detected. Layout could not complete.");
-
+
Measure(node.OutgoingNodes, set);
if (!node.Measured)
@@ -507,8 +513,11 @@ namespace Avalonia.Controls
boundingSize = boundingSize.WithHeight(Math.Max(boundingSize.Height, size.Height));
}
- boundingSize = boundingSize.WithWidth(calcWidth ? boundingSize.Width : AvailableSize.Width);
- boundingSize = boundingSize.WithHeight(calcHeight ? boundingSize.Height : AvailableSize.Height);
+ var availableWidth = double.IsInfinity(AvailableSize.Width) ? boundingSize.Width : AvailableSize.Width;
+ var availableHeight = double.IsInfinity(AvailableSize.Height) ? boundingSize.Height : AvailableSize.Height;
+
+ boundingSize = boundingSize.WithWidth(calcWidth ? boundingSize.Width : availableWidth);
+ boundingSize = boundingSize.WithHeight(calcHeight ? boundingSize.Height : availableHeight);
return boundingSize;
}
}
diff --git a/src/Avalonia.Controls/Remote/RemoteWidget.cs b/src/Avalonia.Controls/Remote/RemoteWidget.cs
index a88e0fd3d8..27578ddc78 100644
--- a/src/Avalonia.Controls/Remote/RemoteWidget.cs
+++ b/src/Avalonia.Controls/Remote/RemoteWidget.cs
@@ -77,10 +77,8 @@ namespace Avalonia.Controls.Remote
_bitmap.PixelSize.Height != _lastFrame.Height)
{
_bitmap?.Dispose();
-#pragma warning disable CS0618 // Type or member is obsolete
_bitmap = new WriteableBitmap(new PixelSize(_lastFrame.Width, _lastFrame.Height),
new Vector(96, 96), fmt);
-#pragma warning restore CS0618 // Type or member is obsolete
}
using (var l = _bitmap.Lock())
{
diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/RichTextBlock.cs
index 0c8b1d125d..906a038ec3 100644
--- a/src/Avalonia.Controls/RichTextBlock.cs
+++ b/src/Avalonia.Controls/RichTextBlock.cs
@@ -44,8 +44,8 @@ namespace Avalonia.Controls
///
/// Defines the property.
///
- public static readonly StyledProperty InlinesProperty =
- AvaloniaProperty.Register(
+ public static readonly StyledProperty InlinesProperty =
+ AvaloniaProperty.Register(
nameof(Inlines));
public static readonly DirectProperty CanCopyProperty =
@@ -138,7 +138,7 @@ namespace Avalonia.Controls
/// Gets or sets the inlines.
///
[Content]
- public InlineCollection Inlines
+ public InlineCollection? Inlines
{
get => GetValue(InlinesProperty);
set => SetValue(InlinesProperty, value);
@@ -159,7 +159,7 @@ namespace Avalonia.Controls
remove => RemoveHandler(CopyingToClipboardEvent, value);
}
- internal bool HasComplexContent => Inlines.Count > 0;
+ internal bool HasComplexContent => Inlines != null && Inlines.Count > 0;
///
/// Copies the current selection to the Clipboard.
@@ -260,23 +260,23 @@ namespace Avalonia.Controls
{
if (!string.IsNullOrEmpty(_text))
{
- Inlines.Add(_text);
+ Inlines?.Add(_text);
_text = null;
}
- Inlines.Add(text);
+ Inlines?.Add(text);
}
}
protected override string? GetText()
{
- return _text ?? Inlines.Text;
+ return _text ?? Inlines?.Text;
}
protected override void SetText(string? text)
{
- var oldValue = _text ?? Inlines?.Text;
+ var oldValue = GetText();
AddText(text);
@@ -301,10 +301,10 @@ namespace Avalonia.Controls
ITextSource textSource;
- var inlines = Inlines;
-
if (HasComplexContent)
{
+ var inlines = Inlines!;
+
var textRuns = new List();
foreach (var inline in inlines)
@@ -381,9 +381,7 @@ namespace Avalonia.Controls
var hit = TextLayout.HitTestPoint(point);
var index = hit.TextPosition;
-#pragma warning disable CS0618 // Type or member is obsolete
switch (e.ClickCount)
-#pragma warning restore CS0618 // Type or member is obsolete
{
case 1:
if (clickToSelect)
@@ -537,7 +535,7 @@ namespace Avalonia.Controls
switch (change.Property.Name)
{
- case nameof(InlinesProperty):
+ case nameof(Inlines):
{
OnInlinesChanged(change.OldValue as InlineCollection, change.NewValue as InlineCollection);
InvalidateTextLayout();
@@ -546,6 +544,32 @@ namespace Avalonia.Controls
}
}
+ protected override Size MeasureOverride(Size availableSize)
+ {
+ foreach (var child in VisualChildren)
+ {
+ if (child is Control control)
+ {
+ control.Measure(Size.Infinity);
+ }
+ }
+
+ return base.MeasureOverride(availableSize);
+ }
+
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ foreach (var child in VisualChildren)
+ {
+ if (child is Control control)
+ {
+ control.Arrange(new Rect(control.DesiredSize));
+ }
+ }
+
+ return base.ArrangeOverride(finalSize);
+ }
+
private string GetSelection()
{
if (!IsTextSelectionEnabled)
@@ -553,7 +577,7 @@ namespace Avalonia.Controls
return "";
}
- var text = Inlines.Text ?? Text;
+ var text = GetText();
if (string.IsNullOrEmpty(text))
{
diff --git a/src/Avalonia.Controls/Shapes/Arc.cs b/src/Avalonia.Controls/Shapes/Arc.cs
index 5ebb321f9b..de3814f215 100644
--- a/src/Avalonia.Controls/Shapes/Arc.cs
+++ b/src/Avalonia.Controls/Shapes/Arc.cs
@@ -1,8 +1,12 @@
using System;
using Avalonia.Media;
+using Avalonia.Utilities;
namespace Avalonia.Controls.Shapes
{
+ ///
+ /// Represents a circular or elliptical arc (a segment of a curve).
+ ///
public class Arc : Shape
{
///
@@ -19,8 +23,12 @@ namespace Avalonia.Controls.Shapes
static Arc()
{
- StrokeThicknessProperty.OverrideDefaultValue(1);
- AffectsGeometry(BoundsProperty, StrokeThicknessProperty, StartAngleProperty, SweepAngleProperty);
+ StrokeThicknessProperty.OverrideDefaultValue(1.0d);
+ AffectsGeometry(
+ BoundsProperty,
+ StrokeThicknessProperty,
+ StartAngleProperty,
+ SweepAngleProperty);
}
///
@@ -42,10 +50,11 @@ namespace Avalonia.Controls.Shapes
set => SetValue(SweepAngleProperty, value);
}
+ ///
protected override Geometry CreateDefiningGeometry()
{
- var angle1 = DegreesToRad(StartAngle);
- var angle2 = angle1 + DegreesToRad(SweepAngle);
+ var angle1 = MathUtilities.Deg2Rad(StartAngle);
+ var angle2 = angle1 + MathUtilities.Deg2Rad(SweepAngle);
var startAngle = Math.Min(angle1, angle2);
var sweepAngle = Math.Max(angle1, angle2);
@@ -80,24 +89,25 @@ namespace Avalonia.Controls.Shapes
var arcGeometry = new StreamGeometry();
- using (var ctx = arcGeometry.Open())
+ using (StreamGeometryContext context = arcGeometry.Open())
{
- ctx.BeginFigure(startPoint, false);
- ctx.ArcTo(endPoint, new Size(radiusX, radiusY), angleGap, angleGap >= Math.PI,
+ context.BeginFigure(startPoint, false);
+ context.ArcTo(
+ endPoint,
+ new Size(radiusX, radiusY),
+ rotationAngle: angleGap,
+ isLargeArc: angleGap >= Math.PI,
SweepDirection.Clockwise);
- ctx.EndFigure(false);
+ context.EndFigure(false);
}
return arcGeometry;
}
}
- static double DegreesToRad(double inAngle) =>
- inAngle * Math.PI / 180;
+ private static double RadToNormRad(double inAngle) => ((inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2);
- static double RadToNormRad(double inAngle) => ((inAngle % (Math.PI * 2)) + (Math.PI * 2)) % (Math.PI * 2);
-
- static Point GetRingPoint(double radiusX, double radiusY, double centerX, double centerY, double angle) =>
+ private static Point GetRingPoint(double radiusX, double radiusY, double centerX, double centerY, double angle) =>
new Point((radiusX * Math.Cos(angle)) + centerX, (radiusY * Math.Sin(angle)) + centerY);
}
}
diff --git a/src/Avalonia.Controls/Shapes/Sector.cs b/src/Avalonia.Controls/Shapes/Sector.cs
new file mode 100644
index 0000000000..a9b7e8939b
--- /dev/null
+++ b/src/Avalonia.Controls/Shapes/Sector.cs
@@ -0,0 +1,97 @@
+using System;
+using Avalonia.Media;
+using Avalonia.Utilities;
+
+namespace Avalonia.Controls.Shapes
+{
+ ///
+ /// Represents a circular or elliptical sector (a pie-shaped closed region of a circle or ellipse).
+ ///
+ public class Sector : Shape
+ {
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty StartAngleProperty =
+ AvaloniaProperty.Register(nameof(StartAngle), 0.0d);
+
+ ///
+ /// Defines the property.
+ ///
+ public static readonly StyledProperty SweepAngleProperty =
+ AvaloniaProperty.Register(nameof(SweepAngle), 0.0d);
+
+ ///
+ /// Gets or sets the angle at which the sector's arc starts, in degrees.
+ ///
+ public double StartAngle
+ {
+ get => GetValue(StartAngleProperty);
+ set => SetValue(StartAngleProperty, value);
+ }
+
+ ///
+ /// Gets or sets the angle, in degrees, added to the defining where the sector's arc ends.
+ /// A positive value is clockwise, negative is counter-clockwise.
+ ///
+ public double SweepAngle
+ {
+ get => GetValue(SweepAngleProperty);
+ set => SetValue(SweepAngleProperty, value);
+ }
+
+ static Sector()
+ {
+ StrokeThicknessProperty.OverrideDefaultValue(1.0d);
+ AffectsGeometry(
+ BoundsProperty,
+ StrokeThicknessProperty,
+ StartAngleProperty,
+ SweepAngleProperty);
+ }
+
+ ///
+ protected override Geometry? CreateDefiningGeometry()
+ {
+ Rect rect = new Rect(Bounds.Size);
+ Rect deflatedRect = rect.Deflate(StrokeThickness * 0.5d);
+
+ if (SweepAngle >= 360.0d || SweepAngle <= -360.0d)
+ {
+ return new EllipseGeometry(deflatedRect);
+ }
+
+ if (SweepAngle == 0.0d)
+ {
+ return new StreamGeometry();
+ }
+
+ (double startAngle, double endAngle) = MathUtilities.GetMinMaxFromDelta(
+ MathUtilities.Deg2Rad(StartAngle),
+ MathUtilities.Deg2Rad(SweepAngle));
+
+ Point centre = new Point(rect.Width * 0.5d, rect.Height * 0.5d);
+ double radiusX = deflatedRect.Width * 0.5d;
+ double radiusY = deflatedRect.Height * 0.5d;
+ Point startCurvePoint = MathUtilities.GetEllipsePoint(centre, radiusX, radiusY, startAngle);
+ Point endCurvePoint = MathUtilities.GetEllipsePoint(centre, radiusX, radiusY, endAngle);
+ Size size = new Size(radiusX, radiusY);
+
+ var streamGeometry = new StreamGeometry();
+ using (StreamGeometryContext context = streamGeometry.Open())
+ {
+ context.BeginFigure(startCurvePoint, false);
+ context.ArcTo(
+ endCurvePoint,
+ size,
+ rotationAngle: 0.0d,
+ isLargeArc: Math.Abs(SweepAngle) > 180.0d,
+ SweepDirection.Clockwise);
+ context.LineTo(centre);
+ context.EndFigure(true);
+ }
+
+ return streamGeometry;
+ }
+ }
+}
diff --git a/src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs b/src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs
index cd34f8060a..83bc9851c1 100644
--- a/src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs
+++ b/src/Avalonia.Controls/SplitButton/ToggleSplitButton.cs
@@ -92,6 +92,7 @@ namespace Avalonia.Controls
///
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
+ base.OnPropertyChanged(e);
if (e.Property == IsCheckedProperty)
{
OnIsCheckedChanged();
diff --git a/src/Avalonia.Controls/SystemDialog.cs b/src/Avalonia.Controls/SystemDialog.cs
index f3fb4d9a6d..ee41b94b5a 100644
--- a/src/Avalonia.Controls/SystemDialog.cs
+++ b/src/Avalonia.Controls/SystemDialog.cs
@@ -32,13 +32,6 @@ namespace Avalonia.Controls
[Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")]
public abstract class FileSystemDialog : SystemDialog
{
- [Obsolete("Use Directory")]
- public string? InitialDirectory
- {
- get => Directory;
- set => Directory = value;
- }
-
///
/// Gets or sets the initial directory that will be displayed when the file system dialog
/// is opened.
@@ -87,7 +80,7 @@ namespace Avalonia.Controls
DefaultExtension = DefaultExtension,
FileTypeChoices = Filters?.Select(f => new FilePickerFileType(f.Name!) { Patterns = f.Extensions.Select(e => $"*.{e}").ToArray() }).ToArray(),
Title = Title,
- SuggestedStartLocation = InitialDirectory is { } directory
+ SuggestedStartLocation = Directory is { } directory
? new BclStorageFolder(new System.IO.DirectoryInfo(directory))
: null,
ShowOverwritePrompt = ShowOverwritePrompt
@@ -129,7 +122,7 @@ namespace Avalonia.Controls
AllowMultiple = AllowMultiple,
FileTypeFilter = Filters?.Select(f => new FilePickerFileType(f.Name!) { Patterns = f.Extensions.Select(e => $"*.{e}").ToArray() }).ToArray(),
Title = Title,
- SuggestedStartLocation = InitialDirectory is { } directory
+ SuggestedStartLocation = Directory is { } directory
? new BclStorageFolder(new System.IO.DirectoryInfo(directory))
: null
};
@@ -142,13 +135,6 @@ namespace Avalonia.Controls
[Obsolete("Use Window.StorageProvider API or TopLevel.StorageProvider API")]
public class OpenFolderDialog : FileSystemDialog
{
- [Obsolete("Use Directory")]
- public string? DefaultDirectory
- {
- get => Directory;
- set => Directory = value;
- }
-
///
/// Shows the open folder dialog.
///
@@ -170,7 +156,7 @@ namespace Avalonia.Controls
return new FolderPickerOpenOptions
{
Title = Title,
- SuggestedStartLocation = InitialDirectory is { } directory
+ SuggestedStartLocation = Directory is { } directory
? new BclStorageFolder(new System.IO.DirectoryInfo(directory))
: null
};
diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs
index 1b268db2f7..d53302ef22 100644
--- a/src/Avalonia.Controls/TextBox.cs
+++ b/src/Avalonia.Controls/TextBox.cs
@@ -17,6 +17,7 @@ using Avalonia.Controls.Metadata;
using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Automation.Peers;
+using System.Diagnostics;
namespace Avalonia.Controls
{
@@ -1164,9 +1165,7 @@ namespace Avalonia.Controls
SetAndRaise(CaretIndexProperty, ref _caretIndex, index);
-#pragma warning disable CS0618 // Type or member is obsolete
switch (e.ClickCount)
-#pragma warning restore CS0618 // Type or member is obsolete
{
case 1:
if (clickToSelect)
@@ -1240,9 +1239,10 @@ namespace Avalonia.Controls
MathUtilities.Clamp(point.X, 0, Math.Max(_presenter.Bounds.Width - 1, 0)),
MathUtilities.Clamp(point.Y, 0, Math.Max(_presenter.Bounds.Height - 1, 0)));
- _presenter.MoveCaretToPoint(point);
+ _presenter.MoveCaretToPoint(point);
var caretIndex = _presenter.CaretIndex;
+
var text = Text;
if (text != null && _wordSelectionStart >= 0)
@@ -1266,7 +1266,7 @@ namespace Avalonia.Controls
}
else
{
- SelectionEnd = _presenter.CaretIndex;
+ SelectionEnd = caretIndex;
}
}
}
diff --git a/src/Avalonia.Controls/ToggleSwitch.cs b/src/Avalonia.Controls/ToggleSwitch.cs
index fd6c202c6f..a28e9791f6 100644
--- a/src/Avalonia.Controls/ToggleSwitch.cs
+++ b/src/Avalonia.Controls/ToggleSwitch.cs
@@ -9,8 +9,10 @@ namespace Avalonia.Controls
///
/// A Toggle Switch control.
///
- [TemplatePart("MovingKnobs", typeof(Panel))]
- [TemplatePart("SwitchKnob", typeof(Panel))]
+ [TemplatePart("PART_MovingKnobs", typeof(Panel))]
+ [TemplatePart("PART_OffContentPresenter", typeof(ContentPresenter))]
+ [TemplatePart("PART_OnContentPresenter", typeof(ContentPresenter))]
+ [TemplatePart("PART_SwitchKnob", typeof(Panel))]
[PseudoClasses(":dragging")]
public class ToggleSwitch : ToggleButton
{
@@ -163,8 +165,8 @@ namespace Avalonia.Controls
{
base.OnApplyTemplate(e);
- _switchKnob = e.NameScope.Find("SwitchKnob");
- _knobsPanel = e.NameScope.Get("MovingKnobs");
+ _switchKnob = e.NameScope.Find("PART_SwitchKnob");
+ _knobsPanel = e.NameScope.Get("PART_MovingKnobs");
_knobsPanel.PointerPressed += KnobsPanel_PointerPressed;
_knobsPanel.PointerReleased += KnobsPanel_PointerReleased;
diff --git a/src/Avalonia.Controls/TopLevel.cs b/src/Avalonia.Controls/TopLevel.cs
index 1c1df84431..47fc9d7988 100644
--- a/src/Avalonia.Controls/TopLevel.cs
+++ b/src/Avalonia.Controls/TopLevel.cs
@@ -404,9 +404,6 @@ namespace Avalonia.Controls
LayoutManager?.Dispose();
}
- [Obsolete("Use HandleResized(Size, PlatformResizeReason)")]
- protected virtual void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified);
-
///
/// Handles a resize notification from .
///
diff --git a/src/Avalonia.Controls/TransitioningContentControl.cs b/src/Avalonia.Controls/TransitioningContentControl.cs
index 451e234653..70b21b7248 100644
--- a/src/Avalonia.Controls/TransitioningContentControl.cs
+++ b/src/Avalonia.Controls/TransitioningContentControl.cs
@@ -84,13 +84,19 @@ public class TransitioningContentControl : ContentControl
_lastTransitionCts?.Cancel();
_lastTransitionCts = new CancellationTokenSource();
+ var localToken = _lastTransitionCts.Token;
if (PageTransition != null)
- await PageTransition.Start(this, null, true, _lastTransitionCts.Token);
+ await PageTransition.Start(this, null, true, localToken);
+
+ if (localToken.IsCancellationRequested)
+ {
+ return;
+ }
CurrentContent = content;
if (PageTransition != null)
- await PageTransition.Start(null, this, true, _lastTransitionCts.Token);
+ await PageTransition.Start(null, this, true, localToken);
}
}
diff --git a/src/Avalonia.Controls/TreeView.cs b/src/Avalonia.Controls/TreeView.cs
index b2a188a2ea..d78f9c82ef 100644
--- a/src/Avalonia.Controls/TreeView.cs
+++ b/src/Avalonia.Controls/TreeView.cs
@@ -495,6 +495,7 @@ namespace Avalonia.Controls
break;
case NavigationDirection.Down:
+ case NavigationDirection.Right:
if (from?.IsExpanded == true && intoChildren && from.ItemCount > 0)
{
result = (TreeViewItem)from.ItemContainerGenerator.ContainerFromIndex(0)!;
@@ -529,7 +530,7 @@ namespace Avalonia.Controls
e.Source,
true,
e.KeyModifiers.HasAllFlags(KeyModifiers.Shift),
- e.KeyModifiers.HasAllFlags(KeyModifiers.Control),
+ e.KeyModifiers.HasAllFlags(AvaloniaLocator.Current.GetRequiredService().CommandModifiers),
point.Properties.IsRightButtonPressed);
}
}
diff --git a/src/Avalonia.Controls/TreeViewItem.cs b/src/Avalonia.Controls/TreeViewItem.cs
index 2e3aa037c2..ada081b808 100644
--- a/src/Avalonia.Controls/TreeViewItem.cs
+++ b/src/Avalonia.Controls/TreeViewItem.cs
@@ -157,17 +157,26 @@ namespace Avalonia.Controls
switch (e.Key)
{
case Key.Right:
- if (Items != null && Items.Cast().Any())
+ if (Items != null && Items.Cast().Any() && !IsExpanded)
{
IsExpanded = true;
+ e.Handled = true;
}
-
- e.Handled = true;
break;
case Key.Left:
- IsExpanded = false;
- e.Handled = true;
+ if (Items is not null && Items.Cast().Any() && IsExpanded)
+ {
+ if (IsFocused)
+ {
+ IsExpanded = false;
+ }
+ else
+ {
+ FocusManager.Instance?.Focus(this, NavigationMethod.Directional);
+ }
+ e.Handled = true;
+ }
break;
}
}
diff --git a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
index ecc7cd98bb..eb9f38894d 100644
--- a/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
+++ b/src/Avalonia.Controls/Utils/BorderRenderHelper.cs
@@ -61,7 +61,7 @@ namespace Avalonia.Controls.Utils
_backgroundGeometryCache = null;
}
- if (boundRect.Width != 0 && innerRect.Height != 0)
+ if (boundRect.Width != 0 && boundRect.Height != 0)
{
var borderGeometryKeypoints =
new BorderGeometryKeypoints(boundRect, borderThickness, cornerRadius, false);
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index 9b4bb51aca..03c66aff2b 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -79,16 +79,6 @@ namespace Avalonia.Controls
public static readonly StyledProperty SizeToContentProperty =
AvaloniaProperty.Register(nameof(SizeToContent));
- ///
- /// Enables or disables system window decorations (title bar, buttons, etc)
- ///
- [Obsolete("Use SystemDecorationsProperty instead")]
- public static readonly DirectProperty HasSystemDecorationsProperty =
- AvaloniaProperty.RegisterDirect(
- nameof(HasSystemDecorations),
- o => o.HasSystemDecorations,
- (o, v) => o.HasSystemDecorations = v);
-
///
/// Defines the property.
///
@@ -289,25 +279,6 @@ namespace Avalonia.Controls
set { SetValue(TitleProperty, value); }
}
- ///
- /// Enables or disables system window decorations (title bar, buttons, etc)
- ///
- [Obsolete("Use SystemDecorations instead")]
- public bool HasSystemDecorations
- {
- get => SystemDecorations == SystemDecorations.Full;
- set
- {
- var oldValue = HasSystemDecorations;
-
- if (oldValue != value)
- {
- SystemDecorations = value ? SystemDecorations.Full : SystemDecorations.None;
- RaisePropertyChanged(HasSystemDecorationsProperty, oldValue, value);
- }
- }
- }
-
///
/// Gets or sets if the ClientArea is Extended into the Window Decorations (chrome or border).
///
@@ -407,8 +378,6 @@ namespace Avalonia.Controls
///
/// Enables or disables resizing of the window.
- /// Note that if is set to False then this property
- /// has no effect and should be treated as a recommendation for the user setting HasSystemDecorations.
///
public bool CanResize
{
@@ -985,9 +954,6 @@ namespace Avalonia.Controls
Owner = null;
}
- [Obsolete("Use HandleResized(Size, PlatformResizeReason)")]
- protected sealed override void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified);
-
///
protected sealed override void HandleResized(Size clientSize, PlatformResizeReason reason)
{
@@ -1033,19 +999,9 @@ namespace Avalonia.Controls
base.OnPropertyChanged(change);
if (change.Property == SystemDecorationsProperty)
{
- var (typedOldValue, typedNewValue) = change.GetOldAndNewValue();
+ var (_, typedNewValue) = change.GetOldAndNewValue();
PlatformImpl?.SetSystemDecorations(typedNewValue);
-
- var o = typedOldValue == SystemDecorations.Full;
- var n = typedNewValue == SystemDecorations.Full;
-
- if (o != n)
- {
-#pragma warning disable CS0618 // Type or member is obsolete
- RaisePropertyChanged(HasSystemDecorationsProperty, o, n);
-#pragma warning restore CS0618 // Type or member is obsolete
- }
}
}
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index cb68c1f6e1..89483cd566 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -94,9 +94,6 @@ namespace Avalonia.Controls
public Screens Screens { get; private set; }
- [Obsolete("No longer used. Always returns false.")]
- protected bool AutoSizing => false;
-
///
/// Gets or sets the owner of the window.
///
@@ -169,9 +166,6 @@ namespace Avalonia.Controls
}
}
- [Obsolete("No longer used. Has no effect.")]
- protected IDisposable BeginAutoSizing() => Disposable.Empty;
-
///
/// Ensures that the window is initialized.
///
@@ -226,9 +220,6 @@ namespace Avalonia.Controls
}
}
- [Obsolete("Use HandleResized(Size, PlatformResizeReason)")]
- protected override void HandleResized(Size clientSize) => HandleResized(clientSize, PlatformResizeReason.Unspecified);
-
///
/// Handles a resize notification from .
///
diff --git a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
index be2405efde..ca2c2c7fb2 100644
--- a/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
+++ b/src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
@@ -168,8 +168,12 @@ namespace Avalonia.DesignerSupport.Remote
var entryPoint = asm.EntryPoint;
if (entryPoint == null)
throw Die($"Assembly {args.AppPath} doesn't have an entry point");
- var builderMethod = entryPoint.DeclaringType.GetMethod(BuilderMethodName,
- BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, Array.Empty(), null);
+ var builderMethod = entryPoint.DeclaringType.GetMethod(
+ BuilderMethodName,
+ BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.FlattenHierarchy,
+ null,
+ Array.Empty(),
+ null);
if (builderMethod == null)
throw Die($"{entryPoint.DeclaringType.FullName} doesn't have a method named {BuilderMethodName}");
Design.IsDesignMode = true;
diff --git a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
index adddf3f57b..d719135a7f 100644
--- a/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
+++ b/src/Avalonia.Diagnostics/Avalonia.Diagnostics.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
index fb57058ae9..4153d2d38c 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
@@ -3,6 +3,8 @@ using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
+using Avalonia.Platform.Storage;
+using Avalonia.Platform.Storage.FileIO;
using Lifetimes = Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Diagnostics.Screenshots
@@ -59,24 +61,25 @@ namespace Avalonia.Diagnostics.Screenshots
protected async override Task GetStream(IControl control)
{
Stream? output = default;
- var result = await new SaveFileDialog()
+ var result = await GetWindow(control).StorageProvider.SaveFilePickerAsync(new FilePickerSaveOptions
{
+ SuggestedStartLocation = new BclStorageFolder(new DirectoryInfo(ScreenshotsRoot)),
Title = Title,
- Filters = new() { new FileDialogFilter() { Name = "PNG", Extensions = new() { "png" } } },
- Directory = ScreenshotsRoot,
- }.ShowAsync(GetWindow(control));
- if (!string.IsNullOrWhiteSpace(result))
+ FileTypeChoices = new FilePickerFileType[] { new FilePickerFileType("PNG") { Patterns = new string[] { "*.png" } } }
+ });
+
+ if (result!=null && !string.IsNullOrWhiteSpace(result.Name))
{
- var foldler = Path.GetDirectoryName(result);
+ var folder = Path.GetDirectoryName(result.Name);
// Directory information for path, or null if path denotes a root directory or is
// null. Returns System.String.Empty if path does not contain directory information.
- if (!string.IsNullOrWhiteSpace(foldler))
+ if (!string.IsNullOrWhiteSpace(folder))
{
- if (!Directory.Exists(foldler))
+ if (!Directory.Exists(folder))
{
- Directory.CreateDirectory(foldler);
+ Directory.CreateDirectory(folder);
}
- output = new FileStream(result, FileMode.Create);
+ output = new FileStream(result.Name, FileMode.Create);
}
}
return output;
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
index 004518598c..1270dbaa62 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
@@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:views="clr-namespace:Avalonia.Diagnostics.Views"
xmlns:diag="clr-namespace:Avalonia.Diagnostics"
- xmlns:default="using:Avalonia.Themes.Default"
Title="Avalonia DevTools"
x:Class="Avalonia.Diagnostics.Views.MainWindow"
Theme="{StaticResource {x:Type Window}}">
@@ -11,8 +10,8 @@
-
-
+
+
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
index 96dc929434..c81997f2cb 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml.cs
@@ -11,7 +11,7 @@ using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Markup.Xaml;
using Avalonia.Styling;
-using Avalonia.Themes.Default;
+using Avalonia.Themes.Simple;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.Views
diff --git a/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml b/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml
index a1d06f800f..65df347ee2 100644
--- a/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml
+++ b/src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml
@@ -76,7 +76,11 @@
-
+
+
+
+
+
diff --git a/src/Avalonia.Dialogs/ManagedFileChooser.cs b/src/Avalonia.Dialogs/ManagedFileChooser.cs
index 199a4d6620..12436680b3 100644
--- a/src/Avalonia.Dialogs/ManagedFileChooser.cs
+++ b/src/Avalonia.Dialogs/ManagedFileChooser.cs
@@ -10,8 +10,8 @@ using Avalonia.LogicalTree;
namespace Avalonia.Dialogs
{
- [TemplatePart("QuickLinks", typeof(Control))]
- [TemplatePart("Files", typeof(ListBox))]
+ [TemplatePart("PART_QuickLinks", typeof(Control))]
+ [TemplatePart("PART_Files", typeof(ListBox))]
public class ManagedFileChooser : TemplatedControl
{
private Control _quickLinksRoot;
@@ -36,9 +36,8 @@ namespace Avalonia.Dialogs
if (_quickLinksRoot != null)
{
var isQuickLink = _quickLinksRoot.IsLogicalAncestorOf(e.Source as Control);
-#pragma warning disable CS0618 // Type or member is obsolete
+
if (e.ClickCount == 2 || isQuickLink)
-#pragma warning restore CS0618 // Type or member is obsolete
{
if (model.ItemType == ManagedFileChooserItemType.File)
{
@@ -91,8 +90,8 @@ namespace Avalonia.Dialogs
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
- _quickLinksRoot = e.NameScope.Get("QuickLinks");
- _filesView = e.NameScope.Get("Files");
+ _quickLinksRoot = e.NameScope.Get("PART_QuickLinks");
+ _filesView = e.NameScope.Get("PART_Files");
}
}
}
diff --git a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs
index 7974069184..8597f3922a 100644
--- a/src/Avalonia.FreeDesktop/DBusSystemDialog.cs
+++ b/src/Avalonia.FreeDesktop/DBusSystemDialog.cs
@@ -72,7 +72,7 @@ namespace Avalonia.FreeDesktop
using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException);
var uris = await tsc.Task ?? Array.Empty();
- return uris.Select(path => new BclStorageFile(new FileInfo(new Uri(path).AbsolutePath))).ToList();
+ return uris.Select(path => new BclStorageFile(new FileInfo(new Uri(path).LocalPath))).ToList();
}
public override async Task SaveFilePickerAsync(FilePickerSaveOptions options)
@@ -96,7 +96,7 @@ namespace Avalonia.FreeDesktop
var tsc = new TaskCompletionSource();
using var disposable = await request.WatchResponseAsync(x => tsc.SetResult(x.results["uris"] as string[]), tsc.SetException);
var uris = await tsc.Task;
- var path = uris?.FirstOrDefault() is { } filePath ? new Uri(filePath).AbsolutePath : null;
+ var path = uris?.FirstOrDefault() is { } filePath ? new Uri(filePath).LocalPath : null;
if (path is null)
{
@@ -126,7 +126,7 @@ namespace Avalonia.FreeDesktop
var uris = await tsc.Task ?? Array.Empty();
return uris
- .Select(path => new Uri(path).AbsolutePath)
+ .Select(path => new Uri(path).LocalPath)
// WSL2 freedesktop allows to select files as well in directory picker, filter it out.
.Where(Directory.Exists)
.Select(path => new BclStorageFolder(new DirectoryInfo(path))).ToList();
diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
index 5576368240..cb23c6c336 100644
--- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
+++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs
@@ -416,6 +416,11 @@ namespace Avalonia.Headless
}
+ public object GetFeature(Type t)
+ {
+ return null;
+ }
+
public void DrawLine(IPen pen, Point p1, Point p2)
{
}
diff --git a/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs b/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs
index e42a7b1a71..b233b46dd0 100644
--- a/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs
+++ b/src/Avalonia.Headless/HeadlessPlatformThreadingInterface.cs
@@ -36,35 +36,35 @@ namespace Avalonia.Headless
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick)
{
- var cancelled = false;
- var enqueued = false;
- var l = new object();
- var timer = new Timer(_ =>
+ if (interval.TotalMilliseconds < 10)
+ interval = TimeSpan.FromMilliseconds(10);
+
+ var stopped = false;
+ Timer timer = null;
+ timer = new Timer(_ =>
{
- lock (l)
+ if (stopped)
+ return;
+
+ Dispatcher.UIThread.Post(() =>
{
- if (cancelled || enqueued)
- return;
- enqueued = true;
- Dispatcher.UIThread.Post(() =>
+ try
{
- lock (l)
- {
- enqueued = false;
- if (cancelled)
- return;
- tick();
- }
- }, priority);
- }
- }, null, interval, interval);
+ tick();
+ }
+ finally
+ {
+ if (!stopped)
+ timer.Change(interval, Timeout.InfiniteTimeSpan);
+ }
+ });
+ },
+ null, interval, Timeout.InfiniteTimeSpan);
+
return Disposable.Create(() =>
{
- lock (l)
- {
- timer.Dispose();
- cancelled = true;
- }
+ stopped = true;
+ timer.Dispose();
});
}
diff --git a/src/Avalonia.MicroCom/MicroComProxyBase.cs b/src/Avalonia.MicroCom/MicroComProxyBase.cs
index 140af3e4ef..fe8a2bf9cf 100644
--- a/src/Avalonia.MicroCom/MicroComProxyBase.cs
+++ b/src/Avalonia.MicroCom/MicroComProxyBase.cs
@@ -65,7 +65,7 @@ namespace Avalonia.MicroCom
protected virtual void Dispose(bool disposing)
{
- if(_nativePointer == null)
+ if(_nativePointer == IntPtr.Zero)
return;
if (_ownsHandle)
{
diff --git a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
index 68442c1fd3..279e7e750d 100644
--- a/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
+++ b/src/Avalonia.OpenGL/Controls/OpenGlControlBase.cs
@@ -34,7 +34,7 @@ namespace Avalonia.OpenGL.Controls
_attachment.Present();
}
- context.DrawImage(_bitmap, new Rect(_bitmap.Size), Bounds);
+ context.DrawImage(_bitmap, new Rect(_bitmap.Size), new Rect(Bounds.Size));
base.Render(context);
}
@@ -84,6 +84,7 @@ namespace Avalonia.OpenGL.Controls
using (_context.MakeCurrent())
{
var gl = _context.GlInterface;
+ gl.ActiveTexture(GL_TEXTURE0);
gl.BindTexture(GL_TEXTURE_2D, 0);
gl.BindFramebuffer(GL_FRAMEBUFFER, 0);
gl.DeleteFramebuffer(_fb);
@@ -146,6 +147,13 @@ namespace Avalonia.OpenGL.Controls
return false;
}
+ if (_context == null)
+ {
+ Logger.TryGet(LogEventLevel.Error, "OpenGL")?.Log("OpenGlControlBase",
+ "Unable to initialize OpenGL: unable to create additional OpenGL context.");
+ return false;
+ }
+
GlVersion = _context.Version;
try
{
diff --git a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
index 5c713804e9..9f69b4ee6e 100644
--- a/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
+++ b/src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
@@ -2,6 +2,7 @@ using System;
using System.Reactive.Linq;
using Avalonia.VisualTree;
using Avalonia.Controls;
+using Avalonia.Interactivity;
using ReactiveUI;
namespace Avalonia.ReactiveUI
@@ -25,27 +26,28 @@ namespace Avalonia.ReactiveUI
public IObservable GetActivationForView(IActivatableView view)
{
if (!(view is IVisual visual)) return Observable.Return(false);
- if (view is WindowBase window) return GetActivationForWindowBase(window);
+ if (view is Control control) return GetActivationForControl(control);
return GetActivationForVisual(visual);
}
///
- /// Listens to Opened and Closed events for Avalonia windows.
+ /// Listens to Loaded and Unloaded
+ /// events for Avalonia Control.
///
- private IObservable GetActivationForWindowBase(WindowBase window)
+ private IObservable GetActivationForControl(Control control)
{
- var windowLoaded = Observable
- .FromEventPattern(
- x => window.Opened += x,
- x => window.Opened -= x)
+ var controlLoaded = Observable
+ .FromEventPattern(
+ x => control.Loaded += x,
+ x => control.Loaded -= x)
.Select(args => true);
- var windowUnloaded = Observable
- .FromEventPattern(
- x => window.Closed += x,
- x => window.Closed -= x)
+ var controlUnloaded = Observable
+ .FromEventPattern(
+ x => control.Unloaded += x,
+ x => control.Unloaded -= x)
.Select(args => false);
- return windowLoaded
- .Merge(windowUnloaded)
+ return controlLoaded
+ .Merge(controlUnloaded)
.DistinctUntilChanged();
}
diff --git a/src/Avalonia.ReactiveUI/RoutedViewHost.cs b/src/Avalonia.ReactiveUI/RoutedViewHost.cs
index 9269dc70f8..2d848d4cd7 100644
--- a/src/Avalonia.ReactiveUI/RoutedViewHost.cs
+++ b/src/Avalonia.ReactiveUI/RoutedViewHost.cs
@@ -50,7 +50,7 @@ namespace Avalonia.ReactiveUI
/// ReactiveUI routing documentation website for more info.
///
///
- public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger
+ public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger, IStyleable
{
///
/// for the property.
@@ -64,6 +64,12 @@ namespace Avalonia.ReactiveUI
public static readonly StyledProperty ViewContractProperty =
AvaloniaProperty.Register(nameof(ViewContract));
+ ///
+ /// for the property.
+ ///
+ public static readonly StyledProperty DefaultContentProperty =
+ ViewModelViewHost.DefaultContentProperty.AddOwner();
+
///
/// Initializes a new instance of the class.
///
@@ -106,11 +112,22 @@ namespace Avalonia.ReactiveUI
set => SetValue(ViewContractProperty, value);
}
+ ///
+ /// Gets or sets the content displayed whenever there is no page currently routed.
+ ///
+ public object? DefaultContent
+ {
+ get => GetValue(DefaultContentProperty);
+ set => SetValue(DefaultContentProperty, value);
+ }
+
///
/// Gets or sets the ReactiveUI view locator used by this router.
///
public IViewLocator? ViewLocator { get; set; }
+ Type IStyleable.StyleKey => typeof(TransitioningContentControl);
+
///
/// Invoked when ReactiveUI router navigates to a view model.
///
diff --git a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs b/src/Avalonia.ReactiveUI/TransitioningContentControl.cs
deleted file mode 100644
index d26e90b2da..0000000000
--- a/src/Avalonia.ReactiveUI/TransitioningContentControl.cs
+++ /dev/null
@@ -1,80 +0,0 @@
-using System;
-using System.Threading;
-
-using Avalonia.Animation;
-using Avalonia.Controls;
-using Avalonia.Styling;
-
-namespace Avalonia.ReactiveUI
-{
- ///
- /// A ContentControl that animates the transition when its content is changed.
- ///
- [Obsolete("Use TransitioningContentControl in Avalonia.Controls namespace")]
- public class TransitioningContentControl : ContentControl, IStyleable
- {
- ///
- /// for the property.
- ///
- public static readonly StyledProperty PageTransitionProperty =
- AvaloniaProperty.Register(nameof(PageTransition),
- new CrossFade(TimeSpan.FromSeconds(0.5)));
-
- ///
- /// for the property.
- ///
- public static readonly StyledProperty DefaultContentProperty =
- AvaloniaProperty.Register(nameof(DefaultContent));
-
- private CancellationTokenSource? _lastTransitionCts;
-
- ///
- /// Gets or sets the animation played when content appears and disappears.
- ///
- public IPageTransition? PageTransition
- {
- get => GetValue(PageTransitionProperty);
- set => SetValue(PageTransitionProperty, value);
- }
-
- ///
- /// Gets or sets the content displayed whenever there is no page currently routed.
- ///
- public object? DefaultContent
- {
- get => GetValue(DefaultContentProperty);
- set => SetValue(DefaultContentProperty, value);
- }
-
- ///
- /// Gets or sets the content with animation.
- ///
- public new object? Content
- {
- get => base.Content;
- set => UpdateContentWithTransition(value);
- }
-
- ///
- /// TransitioningContentControl uses the default ContentControl
- /// template from Avalonia default theme.
- ///
- Type IStyleable.StyleKey => typeof(ContentControl);
-
- ///
- /// Updates the content with transitions.
- ///
- /// New content to set.
- private async void UpdateContentWithTransition(object? content)
- {
- _lastTransitionCts?.Cancel();
- _lastTransitionCts = new CancellationTokenSource();
-
- if (PageTransition != null)
- await PageTransition.Start(this, null, true, _lastTransitionCts.Token);
- base.Content = content;
- if (PageTransition != null)
- await PageTransition.Start(null, this, true, _lastTransitionCts.Token);
- }
- }
-}
diff --git a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs
index 16dee00ebc..0750fef067 100644
--- a/src/Avalonia.ReactiveUI/ViewModelViewHost.cs
+++ b/src/Avalonia.ReactiveUI/ViewModelViewHost.cs
@@ -1,5 +1,8 @@
using System;
using System.Reactive.Disposables;
+
+using Avalonia.Controls;
+using Avalonia.Styling;
using ReactiveUI;
using Splat;
@@ -10,7 +13,7 @@ namespace Avalonia.ReactiveUI
/// the ViewModel property and display it. This control is very useful
/// inside a DataTemplate to display the View associated with a ViewModel.
///
- public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger
+ public class ViewModelViewHost : TransitioningContentControl, IViewFor, IEnableLogger, IStyleable
{
///
/// for the property.
@@ -24,6 +27,12 @@ namespace Avalonia.ReactiveUI
public static readonly StyledProperty ViewContractProperty =
AvaloniaProperty.Register(nameof(ViewContract));
+ ///
+ /// for the property.
+ ///
+ public static readonly StyledProperty DefaultContentProperty =
+ AvaloniaProperty.Register(nameof(DefaultContent));
+
///
/// Initializes a new instance of the class.
///
@@ -55,11 +64,22 @@ namespace Avalonia.ReactiveUI
set => SetValue(ViewContractProperty, value);
}
+ ///
+ /// Gets or sets the content displayed whenever there is no page currently routed.
+ ///
+ public object? DefaultContent
+ {
+ get => GetValue(DefaultContentProperty);
+ set => SetValue(DefaultContentProperty, value);
+ }
+
///
/// Gets or sets the view locator.
///
public IViewLocator? ViewLocator { get; set; }
+ Type IStyleable.StyleKey => typeof(TransitioningContentControl);
+
///
/// Invoked when ReactiveUI router navigates to a view model.
///
diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml b/src/Avalonia.Themes.Default/DefaultTheme.xaml
deleted file mode 100644
index 8f5bea557c..0000000000
--- a/src/Avalonia.Themes.Default/DefaultTheme.xaml
+++ /dev/null
@@ -1,75 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Avalonia.Themes.Default/DefaultTheme.xaml.cs b/src/Avalonia.Themes.Default/DefaultTheme.xaml.cs
deleted file mode 100644
index 598b418977..0000000000
--- a/src/Avalonia.Themes.Default/DefaultTheme.xaml.cs
+++ /dev/null
@@ -1,11 +0,0 @@
-using Avalonia.Styling;
-
-namespace Avalonia.Themes.Default
-{
- ///
- /// The default Avalonia theme.
- ///
- public class DefaultTheme : Styles
- {
- }
-}
diff --git a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml
index 855dc5363e..c6980bf97d 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ButtonSpinner.xaml
@@ -41,13 +41,32 @@
M0,9 L10,0 20,9 19,10 10,2 1,10 z
M0,1 L10,10 20,1 19,0 10,8 1,0 z
-
-
+
-
+
+
+
+
+
+
+
+
+
@@ -83,7 +102,6 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness, Converter={StaticResource ButtonSpinnerLeftThickness}}"
- CornerRadius="0"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
Foreground="{TemplateBinding Foreground}"
@@ -99,7 +117,6 @@
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness, Converter={StaticResource ButtonSpinnerLeftThickness}}"
- CornerRadius="0"
VerticalAlignment="Stretch"
VerticalContentAlignment="Center"
Foreground="{TemplateBinding Foreground}"
diff --git a/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml b/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml
index 8d54cb93ca..7042f51c71 100644
--- a/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/Calendar.xaml
@@ -23,11 +23,11 @@
-
-
-
-
+
@@ -134,7 +134,7 @@
-
-
-
@@ -101,7 +94,7 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Top"/>
-
-
-
+
-
-
-
-
-
-
+
@@ -188,62 +181,62 @@
Padding="{DynamicResource DateTimeFlyoutBorderPadding}"
MaxHeight="398" CornerRadius="{TemplateBinding CornerRadius}">
-
+
-
+
-
-
-
+
+
-
+
-
-
-
+
+
-
+
-
-
-
+
+
-
-
-
+
@@ -251,7 +244,7 @@
VerticalAlignment="Top"
Fill="{DynamicResource DatePickerFlyoutPresenterSpacerFill}"
Grid.ColumnSpan="2"/>
-
-
-
+
-
+
+
+
+
+
@@ -209,7 +213,7 @@
-
-
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
index 0d1b416d7a..fcd661a4b5 100644
--- a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
@@ -55,18 +55,11 @@
-
-
@@ -101,7 +94,7 @@
HorizontalAlignment="Stretch"
VerticalAlignment="Top" />
-
-
+
-
-
-
-
-
-
-
-
-
-
+
@@ -216,42 +209,42 @@
Padding="{DynamicResource DateTimeFlyoutBorderPadding}"
MaxHeight="398">
-
+
-
+
-
-
-
+
+
-
+
-
-
-
+
+
-
+
-
-
-
+
+
-
-
-
-
diff --git a/src/Avalonia.Themes.Default/Controls/TimePicker.xaml b/src/Avalonia.Themes.Simple/Controls/TimePicker.xaml
similarity index 85%
rename from src/Avalonia.Themes.Default/Controls/TimePicker.xaml
rename to src/Avalonia.Themes.Simple/Controls/TimePicker.xaml
index b0ffc97e68..5aa717e9ee 100644
--- a/src/Avalonia.Themes.Default/Controls/TimePicker.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/TimePicker.xaml
@@ -41,7 +41,7 @@
0,3,0,6
0,3,0,6
-
@@ -102,7 +102,7 @@
ContentTemplate="{TemplateBinding HeaderTemplate}"
Foreground="{DynamicResource ThemeForegroundColor}" />
-
+ Theme="{StaticResource SimpleTimePickerFlyoutButton}">
-
+
-
-
-
-
-
-
-
-
-
-
+
@@ -214,51 +214,51 @@
CornerRadius="{TemplateBinding CornerRadius}">
-
+
-
-
-
-
+
+
-
-
-
-
+
+
-
-
-
-
+
+
-
-
-
+ Theme="{StaticResource SimpleDateTimePickerButton}">
-
+ Theme="{StaticResource SimpleDateTimePickerButton}">
-