committed by
GitHub
224 changed files with 5646 additions and 2012 deletions
@ -0,0 +1,32 @@ |
|||
# API Compatibility |
|||
|
|||
Avalonia maintains strict **source and binary compatibility** within major versions. Automated API compatibility checks run on every CI build to enforce this policy—builds will fail if breaking changes are detected. |
|||
|
|||
## When Breaking Changes Are Permitted |
|||
|
|||
Breaking changes are only acceptable under these specific circumstances and **must be approved** by the Avalonia code team: |
|||
|
|||
- **Major version targeting**: When `master` branch is targeting a new major version |
|||
- **Accidental public APIs**: When code was unintentionally exposed as a public API and is unlikely to have external dependencies |
|||
- **Experimental features**: When the API is explicitly marked as unstable or experimental |
|||
|
|||
## Handling Approved Breaking Changes |
|||
|
|||
When a breaking change is approved, you can bypass CI validation using an API suppression file: |
|||
|
|||
1. **Generate suppression file**: Run `nuke --update-api-suppression true` |
|||
2. **Commit changes**: Commit the [updated suppression file](../api/) in a separate commit |
|||
|
|||
> **Note**: The suppression file should only be updated after the breaking change has been reviewed and approved. |
|||
|
|||
## Baseline Version Configuration |
|||
|
|||
API changes are validated against a **baseline version**—the reference point for compatibility checks. |
|||
|
|||
- **Default behavior**: Uses the current major version (e.g., for version 11.0.5, baseline is 11.0.0) |
|||
- **Custom baseline**: Override using the [`api-baseline`](https://github.com/AvaloniaUI/Avalonia/blob/56d94d64b9aa6f16200be39b3bcb17f03325b7f9/nukebuild/BuildParameters.cs#L27) parameter with `nuke` |
|||
- **Fallback**: When not specified, uses the version defined in [`SharedVersion.props`](https://github.com/AvaloniaUI/Avalonia/blob/56d94d64b9aa6f16200be39b3bcb17f03325b7f9/build/SharedVersion.props#L6) |
|||
|
|||
## Additional Resources |
|||
|
|||
- [API Validation Tool Implementation](https://github.com/AvaloniaUI/Avalonia/pull/12072) - Pull request that introduced this feature |
|||
@ -0,0 +1,15 @@ |
|||
# Debugging the XAML Compiler |
|||
|
|||
The Avalonia XAML compiler can be debugged by setting an MSBuild property which triggers a debugger launch during compilation. This allows you to step through the XAML compilation process and troubleshoot issues. |
|||
|
|||
To enable XAML compiler debugging, set the `AvaloniaXamlIlDebuggerLaunch` MSBuild property to `true` in your project file: |
|||
|
|||
```xml |
|||
<PropertyGroup> |
|||
<AvaloniaXamlIlDebuggerLaunch>true</AvaloniaXamlIlDebuggerLaunch> |
|||
</PropertyGroup> |
|||
``` |
|||
|
|||
When this property is enabled, the XAML compiler will call `Debugger.Launch()` on startup, which prompts you to attach a debugger to the compiler process. |
|||
|
|||
If you're working with the Sandbox project in the Avalonia repository, you can enable debugging by simply uncommenting the property line in the [project file](https://github.com/AvaloniaUI/Avalonia/blob/56d94d64b9aa6f16200be39b3bcb17f03325b7f9/samples/Sandbox/Sandbox.csproj#L8). |
|||
|
After Width: | Height: | Size: 576 KiB |
@ -0,0 +1,19 @@ |
|||
# Avalonia Developer Documentation |
|||
|
|||
This documentation covers Avalonia framework development. For user documentation on how to use Avalonia, visit https://docs.avaloniaui.net/. |
|||
|
|||
## Getting Started |
|||
|
|||
- [Building Avalonia](build.md) describes how to build Avalonia from source |
|||
- You should read the [Contributing guidelines](../CONTRIBUTING.md) before you start |
|||
- We have a [Code of Conduct](../CODE_OF_CONDUCT.md) |
|||
|
|||
## Development |
|||
|
|||
- [Debugging the XAML Compiler](debug-xaml-compiler.md) |
|||
- [Porting Code from 3rd Party Sources](porting-code-from-3rd-party-sources.md) |
|||
|
|||
## Releases |
|||
|
|||
- [API Compatibility](api-compat.md) describes the API compatibility guarantees provided by Avalonia, and exceptions to these guarantees |
|||
- Our [Release Process](release.md) describes the process for creating a new Avalonia release |
|||
@ -0,0 +1,71 @@ |
|||
# macOS Native Code |
|||
|
|||
The macOS platform backend has a native component, written in objective-c and located in [`native/Avalonia.Native/src/OSX`](../native/Avalonia.Native/src/OSX/). To work on this code, open the [`Avalonia.Native.OSX.xcodeproj`](../native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj)project in Xcode. |
|||
|
|||
Changes to the native portion of the code can be recompiled in two ways: |
|||
|
|||
1. Using the [`build.sh`](build.md#build-native-libraries-macos-only) script: this has the downside that it will trigger a full recompile of Avalonia |
|||
2. Using `AvaloniaNativeLibraryPath` described below |
|||
|
|||
## Using `AvaloniaNativeLibraryPath ` |
|||
|
|||
When you make changes in Xcode and recompile using Cmd+B, the binary will be compiled to a location that can be seen under "Products": |
|||
|
|||
 |
|||
|
|||
To use this build in Avalonia, one can specifiy the path by using `AvaloniaNativePlatformOptions.AvaloniaNativeLibraryPath` in an application's AppBuilder: |
|||
|
|||
```csharp |
|||
public static AppBuilder BuildAvaloniaApp() => |
|||
AppBuilder.Configure<App>() |
|||
.UsePlatformDetect() |
|||
.With(new AvaloniaNativePlatformOptions |
|||
{ |
|||
AvaloniaNativeLibraryPath = "[Path to your dylib]", |
|||
}) |
|||
``` |
|||
|
|||
# Bundling Development Code |
|||
|
|||
In certain situations you need to run an Avalonia sample application as an app bundle. One of these situations is testing macOS Accessibility - Xcode's Accessibility Inspector fails to recognise the application otherwise. To facilitate this, the [`IntegrationTestApp`](../samples/IntegrationTestApp/) has a [`bundle.sh`](../samples/IntegrationTestApp/bundle.sh) script which can be run to create a bundle of that application. |
|||
|
|||
Alteratively, if you need to bundle another project, another solution is to change the sample's output path to resemble an app bundle. You can do this by modifying the output path in the csproj, e.g.: |
|||
|
|||
```xml |
|||
<OutputPath>bin\$(Configuration)\$(Platform)\ControlCatalog.NetCore.app/Contents/MacOS</OutputPath> |
|||
<AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath> |
|||
<UseAppHost>true</UseAppHost> |
|||
``` |
|||
|
|||
And in the Contents output directory place a valid `Info.plist` file. An example for ControlCatalog.NetCore is: |
|||
|
|||
```xml |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> |
|||
<plist version="1.0"> |
|||
<dict> |
|||
<key>CFBundleName</key> |
|||
<string>ControlCatalog.NetCore</string> |
|||
<key>CFBundleDisplayName</key> |
|||
<string>ControlCatalog.NetCore</string> |
|||
<key>CFBundleIdentifier</key> |
|||
<string>ControlCatalog.NetCore</string> |
|||
<key>CFBundleVersion</key> |
|||
<string>0.10.999</string> |
|||
<key>CFBundlePackageType</key> |
|||
<string>AAPL</string> |
|||
<key>CFBundleSignature</key> |
|||
<string>????</string> |
|||
<key>CFBundleExecutable</key> |
|||
<string>ControlCatalog.NetCore</string> |
|||
<key>CFBundleIconFile</key> |
|||
<string>ControlCatalog.NetCore.icns</string> |
|||
<key>CFBundleShortVersionString</key> |
|||
<string>0.1</string> |
|||
<key>NSPrincipalClass</key> |
|||
<string>NSApplication</string> |
|||
<key>NSHighResolutionCapable</key> |
|||
<true /> |
|||
</dict> |
|||
</plist> |
|||
``` |
|||
@ -0,0 +1,12 @@ |
|||
# Porting Code from 3rd Party Sources |
|||
|
|||
When porting code or adapting code from other projects with a MIT compatible license, the source file must contain appropriate license headers. For example when porting code from WPF the header should contain: |
|||
|
|||
``` |
|||
// This source file is adapted from the Windows Presentation Foundation project. |
|||
// (https://github.com/dotnet/wpf/) |
|||
// |
|||
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation. |
|||
``` |
|||
|
|||
If the file is a port of a specific commit of file from a 3rd party source, then consider including a permalink to the source file. |
|||
@ -0,0 +1,30 @@ |
|||
# Release Process |
|||
|
|||
This document describes the process for creating a new Avalonia release |
|||
|
|||
## Backports |
|||
|
|||
- Go through PRs with "backport-candidate-*" tags, make sure this list makes sense |
|||
- Checkout https://github.com/grokys/avalonia-backport tool from the source code, get github api token |
|||
- Checkout stable release branch, like `release/11.0` |
|||
- Run backport cherry-picking process to the stable release branch |
|||
- Test if everything builds and tests are passing |
|||
- Test nightly builds of Avalonia |
|||
- Run labeling process (automatically replace "backport-candidate" with "backported" tags) and generate changelog |
|||
|
|||
## Release |
|||
|
|||
- Create a branch named e.g. `release/11.0.9` for the specific minor version |
|||
- Update the version number in the file [SharedVersion.props](../build/SharedVersion.props), e.g. `<Version>11.0.9</Version>` |
|||
- Add a tag for this version, e.g. `git tag 11.0.9` |
|||
- Push the release branch and the tag. |
|||
- Wait for azure pipelines to finish the build. Nightly build with 11.0.9 version should be released soon after. |
|||
- Using the nightly build run a due diligence test to make sure you're happy with the package. |
|||
- On azure pipelines, on the release for your release branch `release/11.0.9` click on the badge for "Nuget Release" |
|||
- Click deploy |
|||
- Make a release on Github releases |
|||
- Press "Auto-generate changelog", so GitHub will append information about new contributors |
|||
- Replace changelog with one generated by avalonia-backport tool. Enable discussion for the specific release |
|||
- Review the release information and publish. |
|||
- Update the dotnet templates, visual studio templates. |
|||
- Announce on telegram (RU and EN), twitter, etc |
|||
@ -0,0 +1,168 @@ |
|||
using System; |
|||
using System.Diagnostics; |
|||
using Android.OS; |
|||
using Avalonia.Controls.Documents; |
|||
using Avalonia.Threading; |
|||
using Java.Lang; |
|||
using App = Android.App.Application; |
|||
using Object = Java.Lang.Object; |
|||
|
|||
namespace Avalonia.Android |
|||
{ |
|||
internal sealed class AndroidDispatcherImpl : IDispatcherImplWithExplicitBackgroundProcessing, |
|||
IDispatcherImplWithPendingInput |
|||
{ |
|||
[ThreadStatic] private static bool? s_isUIThread; |
|||
private readonly Looper _mainLooper; |
|||
private readonly Handler _handler; |
|||
private readonly Runnable _signaler; |
|||
private readonly Runnable _timerSignaler; |
|||
private readonly Runnable _wakeupSignaler; |
|||
private readonly MessageQueue _queue; |
|||
private readonly object _lock = new(); |
|||
private bool _signaled; |
|||
private bool _backgroundProcessingRequested; |
|||
|
|||
|
|||
public AndroidDispatcherImpl() |
|||
{ |
|||
_mainLooper = App.Context.MainLooper ?? |
|||
throw new InvalidOperationException( |
|||
"Application.Context.MainLooper was not expected to be null."); |
|||
if (!CurrentThreadIsLoopThread) |
|||
throw new InvalidOperationException("This class should be instanciated from the UI thread"); |
|||
_handler = new Handler(_mainLooper); |
|||
_signaler = new Runnable(OnSignaled); |
|||
_timerSignaler = new Runnable(OnTimer); |
|||
_wakeupSignaler = new Runnable(() => { }); |
|||
_queue = Looper.MyQueue(); |
|||
Looper.MyQueue().AddIdleHandler(new IdleHandler(this)); |
|||
CanQueryPendingInput = OperatingSystem.IsAndroidVersionAtLeast(23); |
|||
} |
|||
|
|||
public event Action? Timer; |
|||
private void OnTimer() => Timer?.Invoke(); |
|||
|
|||
public event Action? Signaled; |
|||
private void OnSignaled() |
|||
{ |
|||
lock (_lock) |
|||
_signaled = false; |
|||
Signaled?.Invoke(); |
|||
} |
|||
|
|||
public bool CurrentThreadIsLoopThread |
|||
{ |
|||
get |
|||
{ |
|||
if (s_isUIThread.HasValue) |
|||
return s_isUIThread.Value; |
|||
var uiThread = OperatingSystem.IsAndroidVersionAtLeast(23) |
|||
? _mainLooper.IsCurrentThread |
|||
: _mainLooper.Thread.Equals(Java.Lang.Thread.CurrentThread()); |
|||
|
|||
s_isUIThread = uiThread; |
|||
return uiThread; |
|||
} |
|||
} |
|||
|
|||
public void Signal() |
|||
{ |
|||
lock (_lock) |
|||
{ |
|||
if(_signaled) |
|||
return; |
|||
_signaled = true; |
|||
_handler.Post(_signaler); |
|||
} |
|||
} |
|||
|
|||
readonly Stopwatch _clock = Stopwatch.StartNew(); |
|||
public long Now => _clock.ElapsedMilliseconds; |
|||
|
|||
public void UpdateTimer(long? dueTimeInMs) |
|||
{ |
|||
_handler.RemoveCallbacks(_timerSignaler); |
|||
if (dueTimeInMs.HasValue) |
|||
{ |
|||
var delay = dueTimeInMs.Value - Now; |
|||
if (delay > 0) |
|||
_handler.PostDelayed(_timerSignaler, delay); |
|||
else |
|||
_handler.Post(_timerSignaler); |
|||
} |
|||
} |
|||
|
|||
class IdleHandler : Object, MessageQueue.IIdleHandler |
|||
{ |
|||
private readonly AndroidDispatcherImpl _parent; |
|||
|
|||
public IdleHandler(AndroidDispatcherImpl parent) |
|||
{ |
|||
_parent = parent; |
|||
} |
|||
|
|||
public bool QueueIdle() |
|||
{ |
|||
_parent.OnIdle(); |
|||
|
|||
return true; |
|||
} |
|||
} |
|||
|
|||
public event Action? ReadyForBackgroundProcessing; |
|||
|
|||
public void RequestBackgroundProcessing() |
|||
{ |
|||
_backgroundProcessingRequested = true; |
|||
} |
|||
|
|||
void OnIdle() |
|||
{ |
|||
tailCall: |
|||
if (_backgroundProcessingRequested) |
|||
{ |
|||
_backgroundProcessingRequested = false; |
|||
ReadyForBackgroundProcessing?.Invoke(); |
|||
} |
|||
|
|||
if (_backgroundProcessingRequested) |
|||
{ |
|||
// Dispatcher requested background processing again, however if the queue is empty and we
|
|||
// just return here, Android's Looper will go to sleep and won't call us again and we'll have
|
|||
// "background" jobs not being processed
|
|||
// So we need to examine the queue state to prevent that scenario
|
|||
|
|||
lock (_lock) |
|||
{ |
|||
// There are higher priority jobs enqueued, we'll be called again
|
|||
if (_signaled) |
|||
return; |
|||
} |
|||
|
|||
if (CanQueryPendingInput) |
|||
{ |
|||
if (!HasPendingInput) |
|||
// There are no events in the queue, so if we just return here, Looper will go to sleep,
|
|||
// so just run our logic again
|
|||
goto tailCall; |
|||
// Nothing to do otherwise, we'll be called again after higher priority events get processed
|
|||
} |
|||
else |
|||
{ |
|||
// On this API level we can't check if there is pending input,
|
|||
// so we explicitly wake up the Looper to make sure that it will call idle hooks again
|
|||
// before going to sleep
|
|||
_handler.Post(_wakeupSignaler); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public bool CanQueryPendingInput { get; } |
|||
|
|||
// See check in ctor
|
|||
#pragma warning disable CA1416
|
|||
public bool HasPendingInput => !_queue.IsIdle; |
|||
#pragma warning restore CA1416
|
|||
} |
|||
} |
|||
@ -1,87 +0,0 @@ |
|||
using System; |
|||
using System.Threading; |
|||
|
|||
using Android.OS; |
|||
|
|||
using Avalonia.Platform; |
|||
using Avalonia.Reactive; |
|||
using Avalonia.Threading; |
|||
|
|||
using App = Android.App.Application; |
|||
|
|||
namespace Avalonia.Android |
|||
{ |
|||
internal sealed class AndroidThreadingInterface : IPlatformThreadingInterface |
|||
{ |
|||
private Handler _handler; |
|||
private static Thread? s_uiThread; |
|||
|
|||
public AndroidThreadingInterface() |
|||
{ |
|||
_handler = new Handler(App.Context.MainLooper |
|||
?? throw new InvalidOperationException("Application.Context.MainLooper was not expected to be null.")); |
|||
} |
|||
|
|||
public IDisposable StartTimer(DispatcherPriority priority, TimeSpan interval, Action tick) |
|||
{ |
|||
if (interval.TotalMilliseconds < 10) |
|||
interval = TimeSpan.FromMilliseconds(10); |
|||
|
|||
var stopped = false; |
|||
Timer? timer = null; |
|||
timer = new Timer(_ => |
|||
{ |
|||
if (stopped) |
|||
return; |
|||
|
|||
EnsureInvokeOnMainThread(() => |
|||
{ |
|||
try |
|||
{ |
|||
tick(); |
|||
} |
|||
finally |
|||
{ |
|||
if (!stopped) |
|||
timer!.Change(interval, Timeout.InfiniteTimeSpan); |
|||
} |
|||
}); |
|||
}, |
|||
null, interval, Timeout.InfiniteTimeSpan); |
|||
|
|||
return Disposable.Create(() => |
|||
{ |
|||
stopped = true; |
|||
timer.Dispose(); |
|||
}); |
|||
} |
|||
|
|||
private void EnsureInvokeOnMainThread(Action action) => _handler.Post(action); |
|||
|
|||
public void Signal(DispatcherPriority prio) |
|||
{ |
|||
EnsureInvokeOnMainThread(() => Signaled?.Invoke(null)); |
|||
} |
|||
|
|||
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 == true) |
|||
{ |
|||
s_uiThread = Thread.CurrentThread; |
|||
return true; |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
} |
|||
public event Action<DispatcherPriority?>? Signaled; |
|||
} |
|||
} |
|||
@ -0,0 +1,28 @@ |
|||
using System; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.ApplicationLifetimes; |
|||
using Avalonia.Logging; |
|||
|
|||
namespace Avalonia.Android; |
|||
|
|||
internal class ApplicationLifetime : IActivityApplicationLifetime, ISingleViewApplicationLifetime |
|||
{ |
|||
private Control? _mainView; |
|||
|
|||
public Func<Control>? MainViewFactory { get; set; } |
|||
|
|||
public Control? MainView |
|||
{ |
|||
get => _mainView; set |
|||
{ |
|||
_mainView = value; |
|||
|
|||
Logger.TryGet(LogEventLevel.Warning, LogArea.AndroidPlatform)?.Log(this, "ISingleViewApplicationLifetime.MainView is not fully supported on Android." + |
|||
" Consider setting IActivityApplicationLifetime.MainViewFactory."); |
|||
if (_mainView != null) |
|||
MainViewFactory = () => _mainView; |
|||
else |
|||
MainViewFactory = null; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,67 @@ |
|||
using System; |
|||
using Android.Views; |
|||
using Android.Views.InputMethods; |
|||
using Avalonia.Android.Platform.SkiaPlatform; |
|||
|
|||
namespace Avalonia.Android |
|||
{ |
|||
public partial class AvaloniaView : IInitEditorInfo |
|||
{ |
|||
private Func<TopLevelImpl, EditorInfo, IInputConnection>? _initEditorInfo; |
|||
|
|||
public override IInputConnection OnCreateInputConnection(EditorInfo? outAttrs) |
|||
{ |
|||
return _initEditorInfo?.Invoke(_view, outAttrs!)!; |
|||
} |
|||
|
|||
void IInitEditorInfo.InitEditorInfo(Func<TopLevelImpl, EditorInfo, IInputConnection> init) |
|||
{ |
|||
_initEditorInfo = init; |
|||
} |
|||
|
|||
protected override void OnFocusChanged(bool gainFocus, FocusSearchDirection direction, global::Android.Graphics.Rect? previouslyFocusedRect) |
|||
{ |
|||
base.OnFocusChanged(gainFocus, direction, previouslyFocusedRect); |
|||
_accessHelper.OnFocusChanged(gainFocus, (int)direction, previouslyFocusedRect); |
|||
} |
|||
|
|||
protected override bool DispatchHoverEvent(MotionEvent? e) |
|||
{ |
|||
return _accessHelper.DispatchHoverEvent(e!) || base.DispatchHoverEvent(e); |
|||
} |
|||
|
|||
protected override bool DispatchGenericPointerEvent(MotionEvent? e) |
|||
{ |
|||
var result = _view.PointerHelper.DispatchMotionEvent(e, out var callBase); |
|||
|
|||
var baseResult = callBase && base.DispatchGenericPointerEvent(e); |
|||
|
|||
return result ?? baseResult; |
|||
} |
|||
|
|||
public override bool DispatchTouchEvent(MotionEvent? e) |
|||
{ |
|||
var result = _view.PointerHelper.DispatchMotionEvent(e, out var callBase); |
|||
var baseResult = callBase && base.DispatchTouchEvent(e); |
|||
|
|||
if(result == true) |
|||
{ |
|||
// Request focus for this view
|
|||
RequestFocus(); |
|||
} |
|||
|
|||
return result ?? baseResult; |
|||
} |
|||
|
|||
public override bool DispatchKeyEvent(KeyEvent? e) |
|||
{ |
|||
var res = _view.KeyboardHelper.DispatchKeyEvent(e, out var callBase); |
|||
if (res == false) |
|||
callBase = !_accessHelper.DispatchKeyEvent(e!) && callBase; |
|||
|
|||
var baseResult = callBase && base.DispatchKeyEvent(e); |
|||
|
|||
return res ?? baseResult; |
|||
} |
|||
} |
|||
} |
|||
@ -1,9 +1,11 @@ |
|||
using System; |
|||
using Android.Views; |
|||
|
|||
namespace Avalonia.Android.Platform.Specific |
|||
{ |
|||
public interface IAndroidView |
|||
{ |
|||
[Obsolete("Use TopLevel.TryGetPlatformHandle instead, which can be casted to AndroidViewControlHandle.")] |
|||
View View { get; } |
|||
} |
|||
} |
|||
|
|||
@ -1,46 +0,0 @@ |
|||
using System.Diagnostics.CodeAnalysis; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Controls.ApplicationLifetimes; |
|||
|
|||
namespace Avalonia.Android; |
|||
|
|||
internal class SingleViewLifetime : ISingleViewApplicationLifetime, ISingleTopLevelApplicationLifetime |
|||
{ |
|||
private Control? _mainView; |
|||
private AvaloniaMainActivity? _activity; |
|||
|
|||
/// <summary>
|
|||
/// Since Main Activity can be swapped, we should adjust lifetime as well.
|
|||
/// </summary>
|
|||
public AvaloniaMainActivity Activity |
|||
{ |
|||
[return: MaybeNull] get => _activity!; |
|||
internal set |
|||
{ |
|||
if (_activity != null) |
|||
{ |
|||
_activity.Content = null; |
|||
} |
|||
_activity = value; |
|||
_activity.Content = _mainView; |
|||
} |
|||
} |
|||
|
|||
public Control? MainView |
|||
{ |
|||
get => _mainView; |
|||
set |
|||
{ |
|||
if (_mainView != value) |
|||
{ |
|||
_mainView = value; |
|||
if (_activity != null) |
|||
{ |
|||
_activity.Content = _mainView; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
public TopLevel? TopLevel => _activity?._view?.TopLevel; |
|||
} |
|||
@ -1,91 +0,0 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
|
|||
namespace Avalonia.Data.Core |
|||
{ |
|||
public class PropertyPath |
|||
{ |
|||
public IReadOnlyList<IPropertyPathElement> Elements { get; } |
|||
|
|||
public PropertyPath(IEnumerable<IPropertyPathElement> elements) |
|||
{ |
|||
Elements = elements.ToArray(); |
|||
} |
|||
} |
|||
|
|||
public class PropertyPathBuilder |
|||
{ |
|||
readonly List<IPropertyPathElement> _elements = new List<IPropertyPathElement>(); |
|||
|
|||
public PropertyPathBuilder Property(IPropertyInfo property) |
|||
{ |
|||
_elements.Add(new PropertyPropertyPathElement(property)); |
|||
return this; |
|||
} |
|||
|
|||
|
|||
public PropertyPathBuilder ChildTraversal() |
|||
{ |
|||
_elements.Add(new ChildTraversalPropertyPathElement()); |
|||
return this; |
|||
} |
|||
|
|||
public PropertyPathBuilder EnsureType(Type type) |
|||
{ |
|||
_elements.Add(new EnsureTypePropertyPathElement(type)); |
|||
return this; |
|||
} |
|||
|
|||
public PropertyPathBuilder Cast(Type type) |
|||
{ |
|||
_elements.Add(new CastTypePropertyPathElement(type)); |
|||
return this; |
|||
} |
|||
|
|||
public PropertyPath Build() |
|||
{ |
|||
return new PropertyPath(_elements); |
|||
} |
|||
} |
|||
|
|||
public interface IPropertyPathElement |
|||
{ |
|||
|
|||
} |
|||
|
|||
public class PropertyPropertyPathElement : IPropertyPathElement |
|||
{ |
|||
public IPropertyInfo Property { get; } |
|||
|
|||
public PropertyPropertyPathElement(IPropertyInfo property) |
|||
{ |
|||
Property = property; |
|||
} |
|||
} |
|||
|
|||
public class ChildTraversalPropertyPathElement : IPropertyPathElement |
|||
{ |
|||
|
|||
} |
|||
|
|||
public class EnsureTypePropertyPathElement : IPropertyPathElement |
|||
{ |
|||
public Type Type { get; } |
|||
|
|||
public EnsureTypePropertyPathElement(Type type) |
|||
{ |
|||
Type = type; |
|||
} |
|||
} |
|||
|
|||
public class CastTypePropertyPathElement : IPropertyPathElement |
|||
{ |
|||
public CastTypePropertyPathElement(Type type) |
|||
{ |
|||
Type = type; |
|||
} |
|||
|
|||
public Type Type { get; } |
|||
} |
|||
} |
|||
@ -0,0 +1,70 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Interactivity; |
|||
|
|||
namespace Avalonia.Input |
|||
{ |
|||
public class FocusChangingEventArgs : RoutedEventArgs |
|||
{ |
|||
/// <summary>
|
|||
/// Provides data for focus changing.
|
|||
/// </summary>
|
|||
internal FocusChangingEventArgs(RoutedEvent routedEvent) : base(routedEvent) |
|||
{ |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the element that focus has moved to.
|
|||
/// </summary>
|
|||
public IInputElement? NewFocusedElement { get; internal set; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the element that previously had focus.
|
|||
/// </summary>
|
|||
public IInputElement? OldFocusedElement { get; init; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets a value indicating how the change in focus occurred.
|
|||
/// </summary>
|
|||
public NavigationMethod NavigationMethod { get; init; } |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets any key modifiers active at the time of focus.
|
|||
/// </summary>
|
|||
public KeyModifiers KeyModifiers { get; init; } |
|||
|
|||
/// <summary>
|
|||
/// Gets whether focus change is canceled.
|
|||
/// </summary>
|
|||
public bool Canceled { get; private set; } |
|||
|
|||
internal bool CanCancelOrRedirectFocus { get; init; } |
|||
|
|||
/// <summary>
|
|||
/// Attempts to cancel the current focus change
|
|||
/// </summary>
|
|||
/// <returns>true if focus change was cancelled; otherwise, false</returns>
|
|||
public bool TryCancel() |
|||
{ |
|||
Canceled = CanCancelOrRedirectFocus; |
|||
|
|||
return Canceled; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Attempts to redirect focus from the targeted element to the specified element.
|
|||
/// </summary>
|
|||
public bool TrySetNewFocusedElement(IInputElement? inputElement) |
|||
{ |
|||
if(CanCancelOrRedirectFocus) |
|||
{ |
|||
NewFocusedElement = inputElement; |
|||
} |
|||
|
|||
return inputElement == NewFocusedElement; |
|||
} |
|||
} |
|||
} |
|||
@ -1,40 +1,130 @@ |
|||
using System; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Avalonia.Threading; |
|||
|
|||
public class DispatcherPriorityAwaitable : INotifyCompletion |
|||
/// <summary>
|
|||
/// A simple awaitable type that will return a DispatcherPriorityAwaiter.
|
|||
/// </summary>
|
|||
public struct DispatcherPriorityAwaitable |
|||
{ |
|||
private readonly Dispatcher _dispatcher; |
|||
private protected readonly Task Task; |
|||
private readonly Task? _task; |
|||
private readonly DispatcherPriority _priority; |
|||
|
|||
internal DispatcherPriorityAwaitable(Dispatcher dispatcher, Task task, DispatcherPriority priority) |
|||
internal DispatcherPriorityAwaitable(Dispatcher dispatcher, Task? task, DispatcherPriority priority) |
|||
{ |
|||
_dispatcher = dispatcher; |
|||
Task = task; |
|||
_task = task; |
|||
_priority = priority; |
|||
} |
|||
|
|||
public void OnCompleted(Action continuation) => |
|||
Task.ContinueWith(_ => _dispatcher.Post(continuation, _priority)); |
|||
|
|||
public bool IsCompleted => Task.IsCompleted; |
|||
public DispatcherPriorityAwaiter GetAwaiter() => new(_dispatcher, _task, _priority); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A simple awaiter type that will queue the continuation to a dispatcher at a specific priority.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This is returned from DispatcherPriorityAwaitable.GetAwaiter()
|
|||
/// </remarks>
|
|||
public struct DispatcherPriorityAwaiter : INotifyCompletion |
|||
{ |
|||
private readonly Dispatcher _dispatcher; |
|||
private readonly Task? _task; |
|||
private readonly DispatcherPriority _priority; |
|||
|
|||
internal DispatcherPriorityAwaiter(Dispatcher dispatcher, Task? task, DispatcherPriority priority) |
|||
{ |
|||
_dispatcher = dispatcher; |
|||
_task = task; |
|||
_priority = priority; |
|||
} |
|||
|
|||
public void OnCompleted(Action continuation) |
|||
{ |
|||
if(_task == null || _task.IsCompleted) |
|||
_dispatcher.Post(continuation, _priority); |
|||
else |
|||
{ |
|||
var self = this; |
|||
_task.ConfigureAwait(false).GetAwaiter().OnCompleted(() => |
|||
{ |
|||
self._dispatcher.Post(continuation, self._priority); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This always returns false since continuation is requested to be queued to a dispatcher queue
|
|||
/// </summary>
|
|||
public bool IsCompleted => false; |
|||
|
|||
public void GetResult() |
|||
{ |
|||
if (_task != null) |
|||
_task.GetAwaiter().GetResult(); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// A simple awaitable type that will return a DispatcherPriorityAwaiter<T>.
|
|||
/// </summary>
|
|||
public struct DispatcherPriorityAwaitable<T> |
|||
{ |
|||
private readonly Dispatcher _dispatcher; |
|||
private readonly Task<T> _task; |
|||
private readonly DispatcherPriority _priority; |
|||
|
|||
public void GetResult() => Task.GetAwaiter().GetResult(); |
|||
internal DispatcherPriorityAwaitable(Dispatcher dispatcher, Task<T> task, DispatcherPriority priority) |
|||
{ |
|||
_dispatcher = dispatcher; |
|||
_task = task; |
|||
_priority = priority; |
|||
} |
|||
|
|||
public DispatcherPriorityAwaitable GetAwaiter() => this; |
|||
public DispatcherPriorityAwaiter<T> GetAwaiter() => new(_dispatcher, _task, _priority); |
|||
} |
|||
|
|||
public sealed class DispatcherPriorityAwaitable<T> : DispatcherPriorityAwaitable |
|||
/// <summary>
|
|||
/// A simple awaiter type that will queue the continuation to a dispatcher at a specific priority.
|
|||
/// </summary>
|
|||
/// <remarks>
|
|||
/// This is returned from DispatcherPriorityAwaitable<T>.GetAwaiter()
|
|||
/// </remarks>
|
|||
public struct DispatcherPriorityAwaiter<T> : INotifyCompletion |
|||
{ |
|||
internal DispatcherPriorityAwaitable(Dispatcher dispatcher, Task<T> task, DispatcherPriority priority) : base( |
|||
dispatcher, task, priority) |
|||
private readonly Dispatcher _dispatcher; |
|||
private readonly Task<T> _task; |
|||
private readonly DispatcherPriority _priority; |
|||
|
|||
internal DispatcherPriorityAwaiter(Dispatcher dispatcher, Task<T> task, DispatcherPriority priority) |
|||
{ |
|||
_dispatcher = dispatcher; |
|||
_task = task; |
|||
_priority = priority; |
|||
} |
|||
|
|||
public new T GetResult() => ((Task<T>)Task).GetAwaiter().GetResult(); |
|||
public void OnCompleted(Action continuation) |
|||
{ |
|||
if(_task.IsCompleted) |
|||
_dispatcher.Post(continuation, _priority); |
|||
else |
|||
{ |
|||
var self = this; |
|||
_task.ConfigureAwait(false).GetAwaiter().OnCompleted(() => |
|||
{ |
|||
self._dispatcher.Post(continuation, self._priority); |
|||
}); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// This always returns false since continuation is requested to be queued to a dispatcher queue
|
|||
/// </summary>
|
|||
public bool IsCompleted => false; |
|||
|
|||
public new DispatcherPriorityAwaitable<T> GetAwaiter() => this; |
|||
} |
|||
public void GetResult() => _task.GetAwaiter().GetResult(); |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue