Browse Source

Merge branch 'master' into patch-1

pull/2906/head
Steven Kirk 7 years ago
committed by GitHub
parent
commit
407ab6108d
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      build/Microsoft.Reactive.Testing.props
  2. 14
      build/NetFX.props
  3. 2
      build/ReactiveUI.props
  4. 2
      build/Rx.props
  5. 9
      build/SharedVersion.props
  6. 2
      build/System.Memory.props
  7. 3
      dirs.proj
  8. 4
      native/Avalonia.Native/inc/avalonia-native.h
  9. 4
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  10. 2
      native/Avalonia.Native/src/OSX/Screens.mm
  11. 24
      native/Avalonia.Native/src/OSX/app.mm
  12. 4
      native/Avalonia.Native/src/OSX/common.h
  13. 14
      native/Avalonia.Native/src/OSX/main.mm
  14. 59
      native/Avalonia.Native/src/OSX/platformthreading.mm
  15. 30
      native/Avalonia.Native/src/OSX/window.mm
  16. 3
      samples/BindingDemo/MainWindow.xaml
  17. 7
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  18. 8
      samples/ControlCatalog.NetCore/Program.cs
  19. 12
      samples/ControlCatalog/MainView.xaml
  20. 4
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  21. 21
      samples/ControlCatalog/Pages/ListBoxPage.xaml.cs
  22. 7
      samples/ControlCatalog/Pages/ScreenPage.cs
  23. 5
      samples/RenderDemo/ViewModels/MainWindowViewModel.cs
  24. 20
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  25. 1
      scripts/ReplaceNugetCache.sh
  26. 22
      src/Avalonia.Animation/Animatable.cs
  27. 88
      src/Avalonia.Base/AvaloniaObject.cs
  28. 5
      src/Avalonia.Base/AvaloniaProperty.cs
  29. 118
      src/Avalonia.Base/AvaloniaPropertyRegistry.cs
  30. 28
      src/Avalonia.Base/BoxedValue.cs
  31. 10
      src/Avalonia.Base/Data/Core/ExpressionNode.cs
  32. 2
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  33. 5
      src/Avalonia.Base/Data/Core/SettableNode.cs
  34. 39
      src/Avalonia.Base/PriorityBindingEntry.cs
  35. 49
      src/Avalonia.Base/PriorityLevel.cs
  36. 14
      src/Avalonia.Base/PriorityValue.cs
  37. 11
      src/Avalonia.Base/StyledPropertyBase.cs
  38. 14
      src/Avalonia.Base/StyledPropertyMetadata`1.cs
  39. 30
      src/Avalonia.Base/Utilities/DeferredSetter.cs
  40. 50
      src/Avalonia.Base/Utilities/TypeUtilities.cs
  41. 3
      src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs
  42. 4
      src/Avalonia.Controls.DataGrid/Themes/Default.xaml
  43. 32
      src/Avalonia.Controls/ContentControl.cs
  44. 15
      src/Avalonia.Controls/ControlExtensions.cs
  45. 6
      src/Avalonia.Controls/Generators/ItemContainerGenerator.cs
  46. 13
      src/Avalonia.Controls/ItemsControl.cs
  47. 6
      src/Avalonia.Controls/ListBox.cs
  48. 166
      src/Avalonia.Controls/Mixins/ContentControlMixin.cs
  49. 5
      src/Avalonia.Controls/Notifications/ReversibleStackPanel.cs
  50. 2
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  51. 5
      src/Avalonia.Controls/Platform/Screen.cs
  52. 47
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  53. 13
      src/Avalonia.Controls/Presenters/IContentPresenter.cs
  54. 13
      src/Avalonia.Controls/Presenters/IContentPresenterHost.cs
  55. 11
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  56. 2
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  57. 26
      src/Avalonia.Controls/Primitives/HeaderedContentControl.cs
  58. 32
      src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
  59. 32
      src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs
  60. 9
      src/Avalonia.Controls/Primitives/Popup.cs
  61. 6
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  62. 6
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs
  63. 41
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  64. 25
      src/Avalonia.Controls/RadioButton.cs
  65. 7
      src/Avalonia.Controls/Repeater/ViewManager.cs
  66. 2
      src/Avalonia.Controls/StackPanel.cs
  67. 36
      src/Avalonia.Controls/TabControl.cs
  68. 2
      src/Avalonia.Controls/TopLevel.cs
  69. 4
      src/Avalonia.Controls/TreeView.cs
  70. 23
      src/Avalonia.Controls/TreeViewItem.cs
  71. 204
      src/Avalonia.Controls/WrapPanel.cs
  72. 2
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  73. 7
      src/Avalonia.Diagnostics/DevTools.xaml.cs
  74. 5
      src/Avalonia.Diagnostics/Views/TreePageView.xaml.cs
  75. 9
      src/Avalonia.Input/AccessKeyHandler.cs
  76. 32
      src/Avalonia.Input/Gestures.cs
  77. 2
      src/Avalonia.Input/IMouseDevice.cs
  78. 12
      src/Avalonia.Input/InputElement.cs
  79. 4
      src/Avalonia.Input/InputExtensions.cs
  80. 129
      src/Avalonia.Input/KeyGesture.cs
  81. 5
      src/Avalonia.Input/MouseDevice.cs
  82. 4
      src/Avalonia.Interactivity/EventSubscription.cs
  83. 101
      src/Avalonia.Interactivity/Interactive.cs
  84. 50
      src/Avalonia.Interactivity/RoutedEvent.cs
  85. 23
      src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs
  86. 1
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  87. 19
      src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs
  88. 18
      src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs
  89. 2
      src/Avalonia.Native/PopupImpl.cs
  90. 1
      src/Avalonia.Native/ScreenImpl.cs
  91. 9
      src/Avalonia.Native/WindowImplBase.cs
  92. 2
      src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs
  93. 2
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  94. 2
      src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs
  95. 4
      src/Avalonia.Themes.Default/ProgressBar.xaml
  96. 4
      src/Avalonia.Themes.Default/TreeViewItem.xaml
  97. 4
      src/Avalonia.Visuals/Rect.cs
  98. 57
      src/Avalonia.Visuals/Rendering/DeferredRenderer.cs
  99. 30
      src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs
  100. 14
      src/Avalonia.Visuals/Rendering/RenderLoop.cs

2
build/Microsoft.Reactive.Testing.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="Microsoft.Reactive.Testing" Version="4.0.0" />
<PackageReference Include="Microsoft.Reactive.Testing" Version="4.1.6" />
</ItemGroup>
</Project>

14
build/NetFX.props

@ -1,11 +1,7 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition=" '$(TargetFramework)' == 'net461' and '$(OS)' == 'Unix' ">
<FrameworkPathOverride>/usr/lib/mono/4.6.1-api</FrameworkPathOverride>
<FrameworkPathOverride Condition="$([MSBuild]::IsOsPlatform('OSX'))">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.6.1-api</FrameworkPathOverride>
</PropertyGroup>
<PropertyGroup Condition=" '$(TargetFramework)' == 'net47' and '$(OS)' == 'Unix' ">
<FrameworkPathOverride>/usr/lib/mono/4.7-api/</FrameworkPathOverride>
<FrameworkPathOverride Condition="$([MSBuild]::IsOsPlatform('OSX'))">/Library/Frameworks/Mono.framework/Versions/Current/lib/mono/4.7-api</FrameworkPathOverride>
</PropertyGroup>
<Project>
<ItemGroup>
<PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0-preview.2" PrivateAssets="All" />
</ItemGroup>
</Project>

2
build/ReactiveUI.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="reactiveui" Version="9.0.1" />
<PackageReference Include="ReactiveUI" Version="10.3.6" />
</ItemGroup>
</Project>

2
build/Rx.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.Reactive" Version="4.0.0" />
<PackageReference Include="System.Reactive" Version="4.1.6" />
</ItemGroup>
</Project>

9
build/SharedVersion.props

@ -4,11 +4,16 @@
<Product>Avalonia</Product>
<Version>0.8.999</Version>
<Copyright>Copyright 2019 &#169; The AvaloniaUI Project</Copyright>
<PackageLicenseUrl>https://github.com/AvaloniaUI/Avalonia/blob/master/licence.md</PackageLicenseUrl>
<PackageProjectUrl>https://github.com/AvaloniaUI/Avalonia/</PackageProjectUrl>
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>CS1591</NoWarn>
<LangVersion>latest</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIconUrl>https://avatars2.githubusercontent.com/u/14075148?s=200</PackageIconUrl>
<PackageDescription>Avalonia is a WPF/UWP-inspired cross-platform XAML-based UI framework providing a flexible styling system and supporting a wide range of Operating Systems such as Windows (.NET Framework, .NET Core), Linux (via Xorg), MacOS and with experimental support for Android and iOS.</PackageDescription>
<PackageTags>avalonia;avaloniaui;mvvm;rx;reactive extensions;android;ios;mac;forms;wpf;net;netstandard;net461;uwp;xamarin</PackageTags>
<PackageReleaseNotes>https://github.com/AvaloniaUI/Avalonia/releases</PackageReleaseNotes>
<RepositoryType>git</RepositoryType>
</PropertyGroup>
</Project>

2
build/System.Memory.props

@ -1,5 +1,5 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="System.Memory" Version="4.5.1" />
<PackageReference Include="System.Memory" Version="4.5.3" />
</ItemGroup>
</Project>

3
dirs.proj

@ -8,7 +8,8 @@
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/PortableXaml/**/*.*proj" />
<ProjectReference Remove="src/Markup/Avalonia.Markup.Xaml/XamlIl/**/*.*proj" />
</ItemGroup>
<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">
<!--<ItemGroup Condition="!Exists('$(MSBuildExtensionsPath)\Xamarin\Android')">-->
<ItemGroup>
<ProjectReference Remove="src/Android/**/*.*proj" />
<ProjectReference Remove="samples/ControlCatalog.Android/ControlCatalog.Android.csproj" />
</ItemGroup>

4
native/Avalonia.Native/inc/avalonia-native.h

@ -52,6 +52,7 @@ struct AvnScreen
{
AvnRect Bounds;
AvnRect WorkingArea;
float PixelDensity;
bool Primary;
};
@ -187,7 +188,6 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT Close() = 0;
virtual HRESULT Activate () = 0;
virtual HRESULT GetClientSize(AvnSize*ret) = 0;
virtual HRESULT GetMaxClientSize(AvnSize* ret) = 0;
virtual HRESULT GetScaling(double*ret)=0;
virtual HRESULT SetMinMaxSize(AvnSize minSize, AvnSize maxSize) = 0;
virtual HRESULT Resize(double width, double height) = 0;
@ -280,7 +280,7 @@ AVNCOM(IAvnPlatformThreadingInterface, 0b) : IUnknown
virtual bool GetCurrentThreadIsLoopThread() = 0;
virtual void SetSignaledCallback(IAvnSignaledCallback* cb) = 0;
virtual IAvnLoopCancellation* CreateLoopCancellation() = 0;
virtual void RunLoop(IAvnLoopCancellation* cancel) = 0;
virtual HRESULT RunLoop(IAvnLoopCancellation* cancel) = 0;
// Can't pass int* to sharpgentools for some reason
virtual void Signal(int priority) = 0;
virtual IUnknown* StartTimer(int priority, int ms, IAvnActionCallback* callback) = 0;

4
native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj

@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
1A002B9E232135EE00021753 /* app.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A002B9D232135EE00021753 /* app.mm */; };
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37DDA9AF219330F8002E132B /* AvnString.mm */; };
@ -22,6 +23,7 @@
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
1A002B9D232135EE00021753 /* app.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = app.mm; sourceTree = "<group>"; };
379860FE214DA0C000CD0246 /* KeyTransform.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = KeyTransform.h; sourceTree = "<group>"; };
37A4E71A2178846A00EACBCD /* headers */ = {isa = PBXFileReference; lastKnownFileType = folder; name = headers; path = ../../inc; sourceTree = "<group>"; };
37A517B22159597E00FBA241 /* Screens.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = Screens.mm; sourceTree = "<group>"; };
@ -68,6 +70,7 @@
AB7A61E62147C814003C5833 = {
isa = PBXGroup;
children = (
1A002B9D232135EE00021753 /* app.mm */,
37DDA9B121933371002E132B /* AvnString.h */,
37DDA9AF219330F8002E132B /* AvnString.mm */,
37A4E71A2178846A00EACBCD /* headers */,
@ -164,6 +167,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1A002B9E232135EE00021753 /* app.mm in Sources */,
5B8BD94F215BFEA6005ED2A7 /* clipboard.mm in Sources */,
5B21A982216530F500CEE36E /* cursor.mm in Sources */,
37DDA9B0219330F8002E132B /* AvnString.mm in Sources */,

2
native/Avalonia.Native/src/OSX/Screens.mm

@ -38,6 +38,8 @@ class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
ret->WorkingArea.Height = [screen visibleFrame].size.height;
ret->WorkingArea.Width = [screen visibleFrame].size.width;
ret->PixelDensity = [screen backingScaleFactor];
ret->Primary = index == 0;
return S_OK;

24
native/Avalonia.Native/src/OSX/app.mm

@ -0,0 +1,24 @@
#include "common.h"
@interface AvnAppDelegate : NSObject<NSApplicationDelegate>
@end
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivationPolicyRegular;
@implementation AvnAppDelegate
- (void)applicationWillFinishLaunching:(NSNotification *)notification
{
[[NSApplication sharedApplication] setActivationPolicy: AvnDesiredActivationPolicy];
}
- (void)applicationDidFinishLaunching:(NSNotification *)notification
{
[NSApp activateIgnoringOtherApps:true];
}
@end
extern void InitializeAvnApp()
{
NSApplication* app = [NSApplication sharedApplication];
id delegate = [AvnAppDelegate new];
[app setDelegate:delegate];
}

4
native/Avalonia.Native/src/OSX/common.h

@ -19,12 +19,12 @@ extern IAvnClipboard* CreateClipboard();
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlFeature* GetGlFeature();
extern IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(NSWindow* window, NSView* view);
extern void InitializeAvnApp();
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p);
extern AvnPoint ToAvnPoint (NSPoint p);
extern AvnPoint ConvertPointY (AvnPoint p);
extern NSSize ToNSSize (AvnSize s);
#ifdef DEBUG
#define NSDebugLog(...) NSLog(__VA_ARGS__)
#else

14
native/Avalonia.Native/src/OSX/main.mm

@ -5,21 +5,14 @@
#define COM_GUIDS_MATERIALIZE
#include "common.h"
static BOOL ShowInDock = 1;
static void SetActivationPolicy()
{
[[NSApplication sharedApplication] setActivationPolicy: (ShowInDock ? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory)];
}
class MacOptions : public ComSingleObject<IAvnMacOptions, &IID_IAvnMacOptions>
{
public:
FORWARD_IUNKNOWN()
virtual HRESULT SetShowInDock(int show) override
{
ShowInDock = show;
SetActivationPolicy();
AvnDesiredActivationPolicy = show
? NSApplicationActivationPolicyRegular : NSApplicationActivationPolicyAccessory;
return S_OK;
}
};
@ -64,8 +57,9 @@ public:
{
@autoreleasepool{
[[ThreadingInitializer new] do];
return S_OK;
}
InitializeAvnApp();
return S_OK;
};
virtual IAvnMacOptions* GetMacOptions() override

59
native/Avalonia.Native/src/OSX/platformthreading.mm

@ -57,16 +57,36 @@ class PlatformThreadingInterface : public ComSingleObject<IAvnPlatformThreadingI
{
private:
Signaler* _signaler;
bool _wasRunningAtLeastOnce = false;
class LoopCancellation : public ComSingleObject<IAvnLoopCancellation, &IID_IAvnLoopCancellation>
{
public:
FORWARD_IUNKNOWN()
bool Cancelled = 0;
virtual void Cancel() override
bool Running = false;
bool Cancelled = false;
virtual void Cancel()
{
Cancelled = 1;
Cancelled = true;
if(Running)
{
Running = false;
dispatch_async(dispatch_get_main_queue(), ^{
[[NSApplication sharedApplication] stop:nil];
NSEvent* event = [NSEvent otherEventWithType:NSEventTypeApplicationDefined
location:NSMakePoint(0, 0)
modifierFlags:0
timestamp:0
windowNumber:0
context:nil
subtype:0
data1:0
data2:0];
[NSApp postEvent:event atStart:YES];
});
}
}
};
public:
@ -99,30 +119,17 @@ public:
return new LoopCancellation();
}
virtual void RunLoop(IAvnLoopCancellation* cancel) override
virtual HRESULT RunLoop(IAvnLoopCancellation* cancel) override
{
@autoreleasepool {
auto can = dynamic_cast<LoopCancellation*>(cancel);
[[NSApplication sharedApplication] activateIgnoringOtherApps:true];
while(true)
{
@autoreleasepool
{
if(can != NULL && can->Cancelled)
return;
NSEvent* ev = [[NSApplication sharedApplication]
nextEventMatchingMask:NSEventMaskAny
untilDate: [NSDate dateWithTimeIntervalSinceNow:1]
inMode:NSDefaultRunLoopMode
dequeue:true];
if(can != NULL && can->Cancelled)
return;
if(ev != NULL)
[[NSApplication sharedApplication] sendEvent:ev];
}
}
NSDebugLog(@"RunLoop exited");
}
auto can = dynamic_cast<LoopCancellation*>(cancel);
if(can->Cancelled)
return S_OK;
if(_wasRunningAtLeastOnce)
return E_FAIL;
can->Running = true;
_wasRunningAtLeastOnce = true;
[NSApp run];
return S_OK;
}
virtual void Signal(int priority) override

30
native/Avalonia.Native/src/OSX/window.mm

@ -54,7 +54,6 @@ public:
FORWARD_IUNKNOWN()
virtual ~WindowBaseImpl()
{
NSDebugLog(@"~WindowBaseImpl()");
View = NULL;
Window = NULL;
}
@ -161,22 +160,6 @@ public:
}
}
virtual HRESULT GetMaxClientSize(AvnSize* ret) override
{
@autoreleasepool
{
if(ret == nullptr)
return E_POINTER;
auto size = [NSScreen.screens objectAtIndex:0].frame.size;
ret->Height = size.height;
ret->Width = size.width;
return S_OK;
}
}
virtual HRESULT GetScaling (double* ret) override
{
@autoreleasepool
@ -369,12 +352,9 @@ public:
virtual void UpdateCursor()
{
[View resetCursorRects];
if (cursor != nil)
{
auto rect = [Window frame];
[View addCursorRect:rect cursor:cursor];
[cursor set];
[cursor set];
}
}
@ -416,8 +396,8 @@ private:
INHERIT_INTERFACE_MAP(WindowBaseImpl)
INTERFACE_MAP_ENTRY(IAvnWindow, IID_IAvnWindow)
END_INTERFACE_MAP()
virtual ~WindowImpl(){
NSDebugLog(@"~WindowImpl");
virtual ~WindowImpl()
{
}
ComPtr<IAvnWindowEvents> WindowEvents;
@ -425,6 +405,7 @@ private:
{
WindowEvents = events;
[Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
}
virtual HRESULT Show () override
@ -664,10 +645,8 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)dealloc
{
NSDebugLog(@"AvnView dealloc");
}
- (void)onClosed
{
_parent = NULL;
@ -1067,7 +1046,6 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)dealloc
{
NSDebugLog(@"AvnWindow dealloc");
}
- (void)pollModalSession:(nonnull NSModalSession)session

3
samples/BindingDemo/MainWindow.xaml

@ -24,6 +24,9 @@
<TextBox Watermark="Two Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue}" Name="first"/>
<TextBox Watermark="One Way" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWay}"/>
<TextBox Watermark="One Time" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneTime}"/>
<!-- Removed due to #2983: reinstate when that's fixed.
<TextBox Watermark="One Way to Source" UseFloatingWatermark="True" Text="{Binding Path=StringValue, Mode=OneWayToSource}"/>
-->
</StackPanel>
<StackPanel Margin="18" Spacing="4" Width="200">
<TextBlock FontSize="16" Text="Collection Bindings"/>

7
samples/BindingDemo/ViewModels/MainWindowViewModel.cs

@ -1,10 +1,11 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using ReactiveUI;
using System.Reactive;
using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Threading;
using ReactiveUI;
namespace BindingDemo.ViewModels
{
@ -56,7 +57,7 @@ namespace BindingDemo.ViewModels
public ObservableCollection<TestItem> Items { get; }
public ObservableCollection<TestItem> SelectedItems { get; }
public ReactiveCommand ShuffleItems { get; }
public ReactiveCommand<Unit, Unit> ShuffleItems { get; }
public string BooleanString
{
@ -89,7 +90,7 @@ namespace BindingDemo.ViewModels
}
public IObservable<string> CurrentTimeObservable { get; }
public ReactiveCommand StringValueCommand { get; }
public ReactiveCommand<object, Unit> StringValueCommand { get; }
public DataAnnotationsErrorViewModel DataAnnotationsValidation { get; } = new DataAnnotationsErrorViewModel();
public ExceptionErrorViewModel ExceptionDataValidation { get; } = new ExceptionErrorViewModel();

8
samples/ControlCatalog.NetCore/Program.cs

@ -4,22 +4,16 @@ using System.Globalization;
using System.Linq;
using System.Threading;
using Avalonia;
using Avalonia.Controls;
using Avalonia.LinuxFramebuffer.Output;
using Avalonia.Skia;
using Avalonia.ReactiveUI;
using Avalonia.Dialogs;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace ControlCatalog.NetCore
{
static class Program
{
[STAThread]
static int Main(string[] args)
{
Thread.CurrentThread.TrySetApartmentState(ApartmentState.STA);
if (args.Contains("--wait-for-attach"))
{
Console.WriteLine("Attach debugger and use 'Set next statement'");

12
samples/ControlCatalog/MainView.xaml

@ -24,7 +24,6 @@
<TabItem Header="CheckBox"><pages:CheckBoxPage/></TabItem>
<TabItem Header="ComboBox"><pages:ComboBoxPage/></TabItem>
<TabItem Header="ContextMenu"><pages:ContextMenuPage/></TabItem>
<!-- DataGrid is our special snowflake -->
<TabItem Header="DataGrid"
ScrollViewer.VerticalScrollBarVisibility="Disabled"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
@ -34,12 +33,15 @@
<TabItem Header="Drag+Drop"><pages:DragAndDropPage/></TabItem>
<TabItem Header="Expander"><pages:ExpanderPage/></TabItem>
<TabItem Header="Image"><pages:ImagePage/></TabItem>
<TabItem Header="ItemsRepeater"><pages:ItemsRepeaterPage/></TabItem>
<TabItem Header="ItemsRepeater"
ScrollViewer.VerticalScrollBarVisibility="Disabled">
<pages:ItemsRepeaterPage/>
</TabItem>
<TabItem Header="LayoutTransformControl"><pages:LayoutTransformControlPage/></TabItem>
<TabItem Header="ListBox"><pages:ListBoxPage/></TabItem>
<TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="Notifications"><pages:NotificationsPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="Pointers (Touch)"><pages:PointersPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
@ -50,12 +52,12 @@
<TabItem Header="ToolTip"><pages:ToolTipPage/></TabItem>
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
<TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
<TabControl.Tag>
<TabControl.Tag>
<ComboBox x:Name="Themes" SelectedIndex="0" Width="100" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<ComboBoxItem>Light</ComboBoxItem>
<ComboBoxItem>Dark</ComboBoxItem>
</ComboBox>
</TabControl.Tag>
</TabControl.Tag>
</TabControl>
</Grid>
</UserControl>

4
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -10,12 +10,14 @@
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8">
<ListBox Items="{Binding Items}" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350"></ListBox>
<ListBox Items="{Binding Items}" SelectedItem="{Binding SelectedItem}" AutoScrollToSelectedItem="True" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350"></ListBox>
<Button Command="{Binding AddItemCommand}">Add</Button>
<Button Command="{Binding RemoveItemCommand}">Remove</Button>
<Button Command="{Binding SelectRandomItemCommand}">Select Random Item</Button>
<ComboBox SelectedIndex="{Binding SelectionMode, Mode=TwoWay}">
<ComboBoxItem>Single</ComboBoxItem>
<ComboBoxItem>Multiple</ComboBoxItem>

21
samples/ControlCatalog/Pages/ListBoxPage.xaml.cs

@ -1,3 +1,4 @@
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
@ -27,7 +28,7 @@ namespace ControlCatalog.Pages
public PageViewModel()
{
Items = new ObservableCollection<string>(Enumerable.Range(1, 10).Select(i => GenerateItem()));
Items = new ObservableCollection<string>(Enumerable.Range(1, 10000).Select(i => GenerateItem()));
SelectedItems = new ObservableCollection<string>();
AddItemCommand = ReactiveCommand.Create(() => Items.Add(GenerateItem()));
@ -39,16 +40,34 @@ namespace ControlCatalog.Pages
Items.Remove(SelectedItems[0]);
}
});
SelectRandomItemCommand = ReactiveCommand.Create(() =>
{
var random = new Random();
SelectedItem = Items[random.Next(Items.Count - 1)];
});
}
public ObservableCollection<string> Items { get; }
private string _selectedItem;
public string SelectedItem
{
get { return _selectedItem; }
set { this.RaiseAndSetIfChanged(ref _selectedItem, value); }
}
public ObservableCollection<string> SelectedItems { get; }
public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; }
public ReactiveCommand<Unit, Unit> SelectRandomItemCommand { get; }
public SelectionMode SelectionMode
{
get => _selectionMode;

7
samples/ControlCatalog/Pages/ScreenPage.cs

@ -57,12 +57,15 @@ namespace ControlCatalog.Pages
text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text);
text.Text = $"Scaling: {screen.PixelDensity * 100}%";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
text.Text = $"Primary: {screen.Primary}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling))))}";
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 80), text);
}
context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10));

5
samples/RenderDemo/ViewModels/MainWindowViewModel.cs

@ -1,4 +1,5 @@
using System;
using System.Reactive;
using ReactiveUI;
namespace RenderDemo.ViewModels
@ -26,7 +27,7 @@ namespace RenderDemo.ViewModels
set { this.RaiseAndSetIfChanged(ref drawFps, value); }
}
public ReactiveCommand ToggleDrawDirtyRects { get; }
public ReactiveCommand ToggleDrawFps { get; }
public ReactiveCommand<Unit, bool> ToggleDrawDirtyRects { get; }
public ReactiveCommand<Unit, bool> ToggleDrawFps { get; }
}
}

20
samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@ -4,10 +4,10 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive;
using Avalonia.Collections;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using ReactiveUI.Legacy;
using ReactiveUI;
using Avalonia.Layout;
@ -18,7 +18,7 @@ namespace VirtualizationDemo.ViewModels
private int _itemCount = 200;
private string _newItemString = "New Item";
private int _newItemIndex;
private IReactiveList<ItemViewModel> _items;
private AvaloniaList<ItemViewModel> _items;
private string _prefix = "Item";
private ScrollBarVisibility _horizontalScrollBarVisibility = ScrollBarVisibility.Auto;
private ScrollBarVisibility _verticalScrollBarVisibility = ScrollBarVisibility.Auto;
@ -54,7 +54,7 @@ namespace VirtualizationDemo.ViewModels
public AvaloniaList<ItemViewModel> SelectedItems { get; }
= new AvaloniaList<ItemViewModel>();
public IReactiveList<ItemViewModel> Items
public AvaloniaList<ItemViewModel> Items
{
get { return _items; }
private set { this.RaiseAndSetIfChanged(ref _items, value); }
@ -93,11 +93,11 @@ namespace VirtualizationDemo.ViewModels
public IEnumerable<ItemVirtualizationMode> VirtualizationModes =>
Enum.GetValues(typeof(ItemVirtualizationMode)).Cast<ItemVirtualizationMode>();
public ReactiveCommand AddItemCommand { get; private set; }
public ReactiveCommand RecreateCommand { get; private set; }
public ReactiveCommand RemoveItemCommand { get; private set; }
public ReactiveCommand SelectFirstCommand { get; private set; }
public ReactiveCommand SelectLastCommand { get; private set; }
public ReactiveCommand<Unit, Unit> AddItemCommand { get; private set; }
public ReactiveCommand<Unit, Unit> RecreateCommand { get; private set; }
public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; private set; }
public ReactiveCommand<Unit, Unit> SelectFirstCommand { get; private set; }
public ReactiveCommand<Unit, Unit> SelectLastCommand { get; private set; }
public void RandomizeSize()
{
@ -123,7 +123,7 @@ namespace VirtualizationDemo.ViewModels
{
var items = Enumerable.Range(0, count)
.Select(x => new ItemViewModel(x));
Items = new ReactiveList<ItemViewModel>(items);
Items = new AvaloniaList<ItemViewModel>(items);
}
else if (count > Items.Count)
{
@ -162,7 +162,7 @@ namespace VirtualizationDemo.ViewModels
_prefix = _prefix == "Item" ? "Recreated" : "Item";
var items = Enumerable.Range(0, _itemCount)
.Select(x => new ItemViewModel(x, _prefix));
Items = new ReactiveList<ItemViewModel>(items);
Items = new AvaloniaList<ItemViewModel>(items);
}
private void SelectItem(int index)

1
scripts/ReplaceNugetCache.sh

@ -2,7 +2,6 @@
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netcoreapp2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia/$1/lib/netstandard2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.gtk3/$1/lib/netstandard2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.skia/$1/lib/netstandard2.0/
cp ../samples/ControlCatalog.NetCore/bin/Debug/netcoreapp2.0/Avalonia**.dll ~/.nuget/packages/avalonia.native/$1/lib/netstandard2.0/

22
src/Avalonia.Animation/Animatable.cs

@ -45,16 +45,22 @@ namespace Avalonia.Animation
{
get
{
if (_transitions == null)
if (_transitions is null)
_transitions = new Transitions();
if (_previousTransitions == null)
if (_previousTransitions is null)
_previousTransitions = new Dictionary<AvaloniaProperty, IDisposable>();
return _transitions;
}
set
{
if (value is null)
return;
if (_previousTransitions is null)
_previousTransitions = new Dictionary<AvaloniaProperty, IDisposable>();
SetAndRaise(TransitionsProperty, ref _transitions, value);
}
}
@ -66,18 +72,20 @@ namespace Avalonia.Animation
/// <param name="e">The event args.</param>
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.Priority != BindingPriority.Animation && Transitions != null && _previousTransitions != null)
{
var match = Transitions.FirstOrDefault(x => x.Property == e.Property);
if (_transitions is null || _previousTransitions is null || e.Priority == BindingPriority.Animation) return;
if (match != null)
// PERF-SENSITIVE: Called on every property change. Don't use LINQ here (too many allocations).
foreach (var transition in _transitions)
{
if (transition.Property == e.Property)
{
if (_previousTransitions.TryGetValue(e.Property, out var dispose))
dispose.Dispose();
var instance = match.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue);
var instance = transition.Apply(this, Clock ?? Avalonia.Animation.Clock.GlobalClock, e.OldValue, e.NewValue);
_previousTransitions[e.Property] = instance;
return;
}
}
}

88
src/Avalonia.Base/AvaloniaObject.cs

@ -82,6 +82,7 @@ namespace Avalonia
set
{
VerifyAccess();
if (_inheritanceParent != value)
{
if (_inheritanceParent != null)
@ -89,25 +90,33 @@ namespace Avalonia
_inheritanceParent.InheritablePropertyChanged -= ParentPropertyChanged;
}
var properties = AvaloniaPropertyRegistry.Instance.GetRegistered(this)
.Concat(AvaloniaPropertyRegistry.Instance.GetRegisteredAttached(this.GetType()));
var inherited = (from property in properties
where property.Inherits
select new
{
Property = property,
Value = GetValue(property),
}).ToList();
var oldInheritanceParent = _inheritanceParent;
_inheritanceParent = value;
var valuestore = _values;
foreach (var i in inherited)
foreach (var property in AvaloniaPropertyRegistry.Instance.GetRegisteredInherited(GetType()))
{
object newValue = GetValue(i.Property);
if (valuestore != null && valuestore.GetValue(property) != AvaloniaProperty.UnsetValue)
{
// if local value set there can be no change
continue;
}
// get the value as it would have been with the previous InheritanceParent
object oldValue;
if (oldInheritanceParent is AvaloniaObject aobj)
{
oldValue = aobj.GetValueOrDefaultUnchecked(property);
}
else
{
oldValue = ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
}
if (!Equals(i.Value, newValue))
object newValue = GetDefaultValue(property);
if (!Equals(oldValue, newValue))
{
RaisePropertyChanged(i.Property, i.Value, newValue, BindingPriority.LocalValue);
RaisePropertyChanged(property, oldValue, newValue, BindingPriority.LocalValue);
}
}
@ -208,20 +217,9 @@ namespace Avalonia
{
return ((IDirectPropertyAccessor)GetRegistered(property)).GetValue(this);
}
else if (_values != null)
{
var result = Values.GetValue(property);
if (result == AvaloniaProperty.UnsetValue)
{
result = GetDefaultValue(property);
}
return result;
}
else
{
return GetDefaultValue(property);
return GetValueOrDefaultUnchecked(property);
}
}
@ -598,10 +596,46 @@ namespace Avalonia
private object GetDefaultValue(AvaloniaProperty property)
{
if (property.Inherits && InheritanceParent is AvaloniaObject aobj)
return aobj.GetValue(property);
return aobj.GetValueOrDefaultUnchecked(property);
return ((IStyledPropertyAccessor) property).GetDefaultValue(GetType());
}
/// <summary>
/// Gets the value or default value for a property.
/// </summary>
/// <param name="property">The property.</param>
/// <returns>The default value.</returns>
private object GetValueOrDefaultUnchecked(AvaloniaProperty property)
{
var aobj = this;
var valuestore = aobj._values;
if (valuestore != null)
{
var result = valuestore.GetValue(property);
if (result != AvaloniaProperty.UnsetValue)
{
return result;
}
}
if (property.Inherits)
{
while (aobj.InheritanceParent is AvaloniaObject parent)
{
aobj = parent;
valuestore = aobj._values;
if (valuestore != null)
{
var result = valuestore.GetValue(property);
if (result != AvaloniaProperty.UnsetValue)
{
return result;
}
}
}
}
return ((IStyledPropertyAccessor)property).GetDefaultValue(GetType());
}
/// <summary>
/// Sets the value of a direct property.
/// </summary>

5
src/Avalonia.Base/AvaloniaProperty.cs

@ -492,6 +492,11 @@ namespace Avalonia
return Name;
}
/// <summary>
/// True if <see cref="Initialized"/> has any observers.
/// </summary>
internal bool HasNotifyInitializedObservers => _initialized.HasObservers;
/// <summary>
/// Notifies the <see cref="Initialized"/> observable.
/// </summary>

118
src/Avalonia.Base/AvaloniaPropertyRegistry.cs

@ -24,8 +24,10 @@ namespace Avalonia
new Dictionary<Type, List<AvaloniaProperty>>();
private readonly Dictionary<Type, List<AvaloniaProperty>> _attachedCache =
new Dictionary<Type, List<AvaloniaProperty>>();
private readonly Dictionary<Type, List<KeyValuePair<AvaloniaProperty, object>>> _initializedCache =
new Dictionary<Type, List<KeyValuePair<AvaloniaProperty, object>>>();
private readonly Dictionary<Type, List<PropertyInitializationData>> _initializedCache =
new Dictionary<Type, List<PropertyInitializationData>>();
private readonly Dictionary<Type, List<AvaloniaProperty>> _inheritedCache =
new Dictionary<Type, List<AvaloniaProperty>>();
/// <summary>
/// Gets the <see cref="AvaloniaPropertyRegistry"/> instance
@ -103,6 +105,46 @@ namespace Avalonia
return result;
}
/// <summary>
/// Gets all inherited <see cref="AvaloniaProperty"/>s registered on a type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>A collection of <see cref="AvaloniaProperty"/> definitions.</returns>
public IEnumerable<AvaloniaProperty> GetRegisteredInherited(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
if (_inheritedCache.TryGetValue(type, out var result))
{
return result;
}
result = new List<AvaloniaProperty>();
var visited = new HashSet<AvaloniaProperty>();
foreach (var property in GetRegistered(type))
{
if (property.Inherits)
{
result.Add(property);
visited.Add(property);
}
}
foreach (var property in GetRegisteredAttached(type))
{
if (property.Inherits)
{
if (!visited.Contains(property))
{
result.Add(property);
}
}
}
_inheritedCache.Add(type, result);
return result;
}
/// <summary>
/// Gets all <see cref="AvaloniaProperty"/>s registered on a object.
/// </summary>
@ -230,6 +272,7 @@ namespace Avalonia
_registeredCache.Clear();
_initializedCache.Clear();
_inheritedCache.Clear();
}
/// <summary>
@ -266,6 +309,7 @@ namespace Avalonia
_attachedCache.Clear();
_initializedCache.Clear();
_inheritedCache.Clear();
}
internal void NotifyInitialized(AvaloniaObject o)
@ -286,35 +330,73 @@ namespace Avalonia
property.NotifyInitialized(e);
}
if (!_initializedCache.TryGetValue(type, out var items))
if (!_initializedCache.TryGetValue(type, out var initializationData))
{
var build = new Dictionary<AvaloniaProperty, object>();
var visited = new HashSet<AvaloniaProperty>();
initializationData = new List<PropertyInitializationData>();
foreach (var property in GetRegistered(type))
foreach (AvaloniaProperty property in GetRegistered(type))
{
var value = !property.IsDirect ?
((IStyledPropertyAccessor)property).GetDefaultValue(type) :
null;
build.Add(property, value);
if (property.IsDirect)
{
initializationData.Add(new PropertyInitializationData(property, (IDirectPropertyAccessor)property));
}
else
{
initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
}
visited.Add(property);
}
foreach (var property in GetRegisteredAttached(type))
foreach (AvaloniaProperty property in GetRegisteredAttached(type))
{
if (!build.ContainsKey(property))
if (!visited.Contains(property))
{
var value = ((IStyledPropertyAccessor)property).GetDefaultValue(type);
build.Add(property, value);
initializationData.Add(new PropertyInitializationData(property, (IStyledPropertyAccessor)property, type));
visited.Add(property);
}
}
items = build.ToList();
_initializedCache.Add(type, items);
_initializedCache.Add(type, initializationData);
}
foreach (PropertyInitializationData data in initializationData)
{
if (!data.Property.HasNotifyInitializedObservers)
{
continue;
}
object value = data.IsDirect ? data.DirectAccessor.GetValue(o) : data.Value;
Notify(data.Property, value);
}
}
private readonly struct PropertyInitializationData
{
public AvaloniaProperty Property { get; }
public object Value { get; }
public bool IsDirect { get; }
public IDirectPropertyAccessor DirectAccessor { get; }
public PropertyInitializationData(AvaloniaProperty property, IDirectPropertyAccessor directAccessor)
{
Property = property;
Value = null;
IsDirect = true;
DirectAccessor = directAccessor;
}
foreach (var i in items)
public PropertyInitializationData(AvaloniaProperty property, IStyledPropertyAccessor styledAccessor, Type type)
{
var value = i.Key.IsDirect ? o.GetValue(i.Key) : i.Value;
Notify(i.Key, value);
Property = property;
Value = styledAccessor.GetDefaultValue(type);
IsDirect = false;
DirectAccessor = null;
}
}
}

28
src/Avalonia.Base/BoxedValue.cs

@ -0,0 +1,28 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
namespace Avalonia
{
/// <summary>
/// Represents boxed value of type <typeparamref name="T"/>.
/// </summary>
/// <typeparam name="T">Type of stored value.</typeparam>
internal readonly struct BoxedValue<T>
{
public BoxedValue(T value)
{
Boxed = value;
Typed = value;
}
/// <summary>
/// Boxed value.
/// </summary>
public object Boxed { get; }
/// <summary>
/// Typed value.
/// </summary>
public T Typed { get; }
}
}

10
src/Avalonia.Base/Data/Core/ExpressionNode.cs

@ -8,9 +8,13 @@ namespace Avalonia.Data.Core
public abstract class ExpressionNode
{
private static readonly object CacheInvalid = new object();
protected static readonly WeakReference<object> UnsetReference =
new WeakReference<object>(AvaloniaProperty.UnsetValue);
protected static readonly WeakReference<object> NullReference =
new WeakReference<object>(null);
private WeakReference<object> _target = UnsetReference;
private Action<object> _subscriber;
private bool _listening;
@ -98,7 +102,7 @@ namespace Avalonia.Data.Core
if (notification == null)
{
LastValue = new WeakReference<object>(value);
LastValue = value != null ? new WeakReference<object>(value) : NullReference;
if (Next != null)
{
@ -111,7 +115,7 @@ namespace Avalonia.Data.Core
}
else
{
LastValue = new WeakReference<object>(notification.Value);
LastValue = notification.Value != null ? new WeakReference<object>(notification.Value) : NullReference;
if (Next != null)
{
@ -136,8 +140,8 @@ namespace Avalonia.Data.Core
}
else if (target != AvaloniaProperty.UnsetValue)
{
StartListeningCore(_target);
_listening = true;
StartListeningCore(_target);
}
else
{

2
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -103,8 +103,8 @@ namespace Avalonia.Data.Core.Plugins
protected override void SubscribeCore()
{
SendCurrentValue();
SubscribeToChanges();
SendCurrentValue();
}
protected override void UnsubscribeCore()

5
src/Avalonia.Base/Data/Core/SettableNode.cs

@ -29,6 +29,11 @@ namespace Avalonia.Data.Core
if (!isLastValueAlive)
{
if (value == null && LastValue == NullReference)
{
return true;
}
return false;
}

39
src/Avalonia.Base/PriorityBindingEntry.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Runtime.ExceptionServices;
using Avalonia.Data;
using Avalonia.Threading;
@ -10,9 +11,9 @@ namespace Avalonia
/// <summary>
/// A registered binding in a <see cref="PriorityValue"/>.
/// </summary>
internal class PriorityBindingEntry : IDisposable
internal class PriorityBindingEntry : IDisposable, IObserver<object>
{
private PriorityLevel _owner;
private readonly PriorityLevel _owner;
private IDisposable _subscription;
/// <summary>
@ -85,7 +86,7 @@ namespace Avalonia
Description = ((IDescription)binding).Description;
}
_subscription = binding.Subscribe(ValueChanged, Completed);
_subscription = binding.Subscribe(this);
}
/// <summary>
@ -96,43 +97,48 @@ namespace Avalonia
_subscription?.Dispose();
}
private void ValueChanged(object value)
void IObserver<object>.OnNext(object value)
{
void Signal()
void Signal(PriorityBindingEntry instance, object newValue)
{
var notification = value as BindingNotification;
var notification = newValue as BindingNotification;
if (notification != null)
{
if (notification.HasValue || notification.ErrorType == BindingErrorType.Error)
{
Value = notification.Value;
_owner.Changed(this);
instance.Value = notification.Value;
instance._owner.Changed(instance);
}
if (notification.ErrorType != BindingErrorType.None)
{
_owner.Error(this, notification);
instance._owner.Error(instance, notification);
}
}
else
{
Value = value;
_owner.Changed(this);
instance.Value = newValue;
instance._owner.Changed(instance);
}
}
if (Dispatcher.UIThread.CheckAccess())
{
Signal();
Signal(this, value);
}
else
{
Dispatcher.UIThread.Post(Signal);
// To avoid allocating closure in the outer scope we need to capture variables
// locally. This allows us to skip most of the allocations when on UI thread.
var instance = this;
var newValue = value;
Dispatcher.UIThread.Post(() => Signal(instance, newValue));
}
}
private void Completed()
void IObserver<object>.OnCompleted()
{
HasCompleted = true;
@ -145,5 +151,10 @@ namespace Avalonia
Dispatcher.UIThread.Post(() => _owner.Completed(this));
}
}
void IObserver<object>.OnError(Exception error)
{
ExceptionDispatchInfo.Capture(error).Throw();
}
}
}

49
src/Avalonia.Base/PriorityLevel.cs

@ -110,20 +110,7 @@ namespace Avalonia
entry.Start(binding);
return Disposable.Create(() =>
{
if (!entry.HasCompleted)
{
Bindings.Remove(node);
entry.Dispose();
if (entry.Index >= ActiveBindingIndex)
{
ActivateFirstBinding();
}
}
});
return new RemoveBindingDisposable(node, Bindings, this);
}
/// <summary>
@ -191,5 +178,39 @@ namespace Avalonia
ActiveBindingIndex = -1;
Owner.LevelValueChanged(this);
}
private sealed class RemoveBindingDisposable : IDisposable
{
private readonly LinkedListNode<PriorityBindingEntry> _binding;
private readonly LinkedList<PriorityBindingEntry> _bindings;
private readonly PriorityLevel _priorityLevel;
public RemoveBindingDisposable(
LinkedListNode<PriorityBindingEntry> binding,
LinkedList<PriorityBindingEntry> bindings,
PriorityLevel priorityLevel)
{
_binding = binding;
_bindings = bindings;
_priorityLevel = priorityLevel;
}
public void Dispose()
{
PriorityBindingEntry entry = _binding.Value;
if (!entry.HasCompleted)
{
_bindings.Remove(_binding);
entry.Dispose();
if (entry.Index >= _priorityLevel.ActiveBindingIndex)
{
_priorityLevel.ActivateFirstBinding();
}
}
}
}
}
}

14
src/Avalonia.Base/PriorityValue.cs

@ -24,13 +24,11 @@ namespace Avalonia
/// <see cref="IPriorityValueOwner.Changed"/> method on the
/// owner object is fired with the old and new values.
/// </remarks>
internal class PriorityValue
internal sealed class PriorityValue : ISetAndNotifyHandler<(object,int)>
{
private readonly Type _valueType;
private readonly SingleOrDictionary<int, PriorityLevel> _levels = new SingleOrDictionary<int, PriorityLevel>();
private readonly Func<object, object> _validate;
private readonly SetAndNotifyCallback<(object, int)> _setAndNotifyCallback;
private (object value, int priority) _value;
private DeferredSetter<object> _setter;
@ -52,7 +50,6 @@ namespace Avalonia
_valueType = valueType;
_value = (AvaloniaProperty.UnsetValue, int.MaxValue);
_validate = validate;
_setAndNotifyCallback = SetAndNotify;
}
/// <summary>
@ -257,10 +254,15 @@ namespace Avalonia
_setter = Owner.GetNonDirectDeferredSetter(Property);
}
_setter.SetAndNotifyCallback(Property, _setAndNotifyCallback, ref _value, newValue);
_setter.SetAndNotifyCallback(Property, this, ref _value, newValue);
}
void ISetAndNotifyHandler<(object, int)>.HandleSetAndNotify(AvaloniaProperty property, ref (object, int) backing, (object, int) value)
{
SetAndNotify(ref backing, value);
}
private void SetAndNotify(AvaloniaProperty property, ref (object value, int priority) backing, (object value, int priority) update)
private void SetAndNotify(ref (object value, int priority) backing, (object value, int priority) update)
{
var val = update.value;
var notification = val as BindingNotification;

11
src/Avalonia.Base/StyledPropertyBase.cs

@ -68,7 +68,7 @@ namespace Avalonia
{
Contract.Requires<ArgumentNullException>(type != null);
return GetMetadata(type).DefaultValue;
return GetMetadata(type).DefaultValue.Typed;
}
/// <summary>
@ -164,7 +164,14 @@ namespace Avalonia
}
/// <inheritdoc/>
object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultValue(type);
object IStyledPropertyAccessor.GetDefaultValue(Type type) => GetDefaultBoxedValue(type);
private object GetDefaultBoxedValue(Type type)
{
Contract.Requires<ArgumentNullException>(type != null);
return GetMetadata(type).DefaultValue.Boxed;
}
[DebuggerHidden]
private Func<IAvaloniaObject, TValue, TValue> Cast<THost>(Func<THost, TValue, TValue> validate)

14
src/Avalonia.Base/StyledPropertyMetadata`1.cs

@ -19,26 +19,26 @@ namespace Avalonia
/// <param name="validate">A validation function.</param>
/// <param name="defaultBindingMode">The default binding mode.</param>
public StyledPropertyMetadata(
TValue defaultValue = default(TValue),
TValue defaultValue = default,
Func<IAvaloniaObject, TValue, TValue> validate = null,
BindingMode defaultBindingMode = BindingMode.Default)
: base(defaultBindingMode)
{
DefaultValue = defaultValue;
DefaultValue = new BoxedValue<TValue>(defaultValue);
Validate = validate;
}
/// <summary>
/// Gets the default value for the property.
/// </summary>
public TValue DefaultValue { get; private set; }
internal BoxedValue<TValue> DefaultValue { get; private set; }
/// <summary>
/// Gets the validation callback.
/// </summary>
public Func<IAvaloniaObject, TValue, TValue> Validate { get; private set; }
object IStyledPropertyMetadata.DefaultValue => DefaultValue;
object IStyledPropertyMetadata.DefaultValue => DefaultValue.Boxed;
Func<IAvaloniaObject, object, object> IStyledPropertyMetadata.Validate => Cast(Validate);
@ -47,11 +47,9 @@ namespace Avalonia
{
base.Merge(baseMetadata, property);
var src = baseMetadata as StyledPropertyMetadata<TValue>;
if (src != null)
if (baseMetadata is StyledPropertyMetadata<TValue> src)
{
if (DefaultValue == null)
if (DefaultValue.Boxed == null)
{
DefaultValue = src.DefaultValue;
}

30
src/Avalonia.Base/Utilities/DeferredSetter.cs

@ -5,15 +5,6 @@ using System;
namespace Avalonia.Utilities
{
/// <summary>
/// Callback invoked when deferred setter wants to set a value.
/// </summary>
/// <typeparam name="TValue">Value type.</typeparam>
/// <param name="property">Property being set.</param>
/// <param name="backing">Backing field reference.</param>
/// <param name="value">New value.</param>
internal delegate void SetAndNotifyCallback<TValue>(AvaloniaProperty property, ref TValue backing, TValue value);
/// <summary>
/// A utility class to enable deferring assignment until after property-changed notifications are sent.
/// Used to fix #855.
@ -70,14 +61,14 @@ namespace Avalonia.Utilities
return false;
}
public bool SetAndNotifyCallback<TValue>(AvaloniaProperty property, SetAndNotifyCallback<TValue> setAndNotifyCallback, ref TValue backing, TValue value)
public bool SetAndNotifyCallback<TValue>(AvaloniaProperty property, ISetAndNotifyHandler<TValue> setAndNotifyHandler, ref TValue backing, TValue value)
where TValue : TSetRecord
{
if (!_isNotifying)
{
using (new NotifyDisposable(this))
{
setAndNotifyCallback(property, ref backing, value);
setAndNotifyHandler.HandleSetAndNotify(property, ref backing, value);
}
if (!_pendingValues.Empty)
@ -86,7 +77,7 @@ namespace Avalonia.Utilities
{
while (!_pendingValues.Empty)
{
setAndNotifyCallback(property, ref backing, (TValue) _pendingValues.Dequeue());
setAndNotifyHandler.HandleSetAndNotify(property, ref backing, (TValue)_pendingValues.Dequeue());
}
}
}
@ -119,4 +110,19 @@ namespace Avalonia.Utilities
}
}
}
/// <summary>
/// Handler for set and notify requests.
/// </summary>
/// <typeparam name="TValue">Value type.</typeparam>
internal interface ISetAndNotifyHandler<TValue>
{
/// <summary>
/// Handles deferred setter requests to set a value.
/// </summary>
/// <param name="property">Property being set.</param>
/// <param name="backing">Backing field reference.</param>
/// <param name="value">New value.</param>
void HandleSetAndNotify(AvaloniaProperty property, ref TValue backing, TValue value);
}
}

50
src/Avalonia.Base/Utilities/TypeUtilities.cs

@ -13,7 +13,7 @@ namespace Avalonia.Utilities
/// </summary>
public static class TypeUtilities
{
private static int[] Conversions =
private static readonly int[] Conversions =
{
0b101111111111101, // Boolean
0b100001111111110, // Char
@ -32,7 +32,7 @@ namespace Avalonia.Utilities
0b111111111111111, // String
};
private static int[] ImplicitConversions =
private static readonly int[] ImplicitConversions =
{
0b000000000000001, // Boolean
0b001110111100010, // Char
@ -51,7 +51,7 @@ namespace Avalonia.Utilities
0b100000000000000, // String
};
private static Type[] InbuiltTypes =
private static readonly Type[] InbuiltTypes =
{
typeof(Boolean),
typeof(Char),
@ -70,7 +70,7 @@ namespace Avalonia.Utilities
typeof(String),
};
private static readonly Type[] NumericTypes = new[]
private static readonly Type[] NumericTypes =
{
typeof(Byte),
typeof(Decimal),
@ -188,8 +188,7 @@ namespace Avalonia.Utilities
}
}
var cast = from.GetRuntimeMethods()
.FirstOrDefault(m => (m.Name == "op_Implicit" || m.Name == "op_Explicit") && m.ReturnType == to);
var cast = FindTypeConversionOperatorMethod(from, to, OperatorType.Implicit | OperatorType.Explicit);
if (cast != null)
{
@ -253,8 +252,7 @@ namespace Avalonia.Utilities
}
}
var cast = from.GetRuntimeMethods()
.FirstOrDefault(m => m.Name == "op_Implicit" && m.ReturnType == to);
var cast = FindTypeConversionOperatorMethod(from, to, OperatorType.Implicit);
if (cast != null)
{
@ -335,5 +333,41 @@ namespace Avalonia.Utilities
return NumericTypes.Contains(type);
}
}
[Flags]
private enum OperatorType
{
Implicit = 1,
Explicit = 2
}
private static MethodInfo FindTypeConversionOperatorMethod(Type fromType, Type toType, OperatorType operatorType)
{
const string implicitName = "op_Implicit";
const string explicitName = "op_Explicit";
bool allowImplicit = (operatorType & OperatorType.Implicit) != 0;
bool allowExplicit = (operatorType & OperatorType.Explicit) != 0;
foreach (MethodInfo method in fromType.GetMethods())
{
if (!method.IsSpecialName || method.ReturnType != toType)
{
continue;
}
if (allowImplicit && method.Name == implicitName)
{
return method;
}
if (allowExplicit && method.Name == explicitName)
{
return method;
}
}
return null;
}
}
}

3
src/Avalonia.Base/Utilities/WeakEventHandlerManager.cs

@ -161,9 +161,8 @@ namespace Avalonia.Utilities
for (int c = 0; c < _count; ++c)
{
var reference = _data[c].Subscriber;
TSubscriber instance;
if (reference != null && reference.TryGetTarget(out instance) && instance == s)
if (reference != null && reference.TryGetTarget(out TSubscriber instance) && Equals(instance, s.Target))
{
_data[c] = default;
removed = true;

4
src/Avalonia.Controls.DataGrid/Themes/Default.xaml

@ -217,12 +217,12 @@
<DataGridRowsPresenter Name="PART_RowsPresenter" Grid.ColumnSpan="2" Grid.Row="1" />
<Rectangle Name="BottomRightCorner" Fill="#FFE9EEF4" Grid.Column="2" Grid.Row="2" />
<Rectangle Name="BottomLeftCorner" Fill="#FFE9EEF4" Grid.Row="2" Grid.ColumnSpan="2" />
<ScrollBar Name="PART_VerticalScrollbar" Orientation="Vertical" Grid.Column="2" Grid.Row="1" Width="18" Margin="0,-1,-1,-1"/>
<ScrollBar Name="PART_VerticalScrollbar" Orientation="Vertical" Grid.Column="2" Grid.Row="1" Width="{DynamicResource ScrollBarThickness}" Margin="0,-1,-1,-1"/>
<Grid Grid.Column="1" Grid.Row="2"
ColumnDefinitions="Auto,*">
<Rectangle Name="PART_FrozenColumnScrollBarSpacer" />
<ScrollBar Name="PART_HorizontalScrollbar" Grid.Column="1" Orientation="Horizontal" Height="18" Margin="-1,0,-1,-1"/>
<ScrollBar Name="PART_HorizontalScrollbar" Grid.Column="1" Orientation="Horizontal" Height="{DynamicResource ScrollBarThickness}" Margin="-1,0,-1,-1"/>
</Grid>
</Grid>
</Border>

32
src/Avalonia.Controls/ContentControl.cs

@ -1,11 +1,13 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Collections;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Metadata;
namespace Avalonia.Controls
@ -39,12 +41,9 @@ namespace Avalonia.Controls
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
AvaloniaProperty.Register<ContentControl, VerticalAlignment>(nameof(VerticalContentAlignment));
/// <summary>
/// Initializes static members of the <see cref="ContentControl"/> class.
/// </summary>
static ContentControl()
{
ContentControlMixin.Attach<ContentControl>(ContentProperty, x => x.LogicalChildren);
ContentProperty.Changed.AddClassHandler<ContentControl>(x => x.ContentChanged);
}
/// <summary>
@ -95,20 +94,39 @@ namespace Avalonia.Controls
}
/// <inheritdoc/>
void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
IAvaloniaList<ILogical> IContentPresenterHost.LogicalChildren => LogicalChildren;
/// <inheritdoc/>
bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
{
RegisterContentPresenter(presenter);
return RegisterContentPresenter(presenter);
}
/// <summary>
/// Called when an <see cref="IContentPresenter"/> is registered with the control.
/// </summary>
/// <param name="presenter">The presenter.</param>
protected virtual void RegisterContentPresenter(IContentPresenter presenter)
protected virtual bool RegisterContentPresenter(IContentPresenter presenter)
{
if (presenter.Name == "PART_ContentPresenter")
{
Presenter = presenter;
return true;
}
return false;
}
private void ContentChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.OldValue is ILogical oldChild)
{
LogicalChildren.Remove(oldChild);
}
if (e.NewValue is ILogical newChild)
{
LogicalChildren.Add(newChild);
}
}
}

15
src/Avalonia.Controls/ControlExtensions.cs

@ -34,14 +34,17 @@ namespace Avalonia.Controls
{
Contract.Requires<ArgumentNullException>(control != null);
var ev = new RequestBringIntoViewEventArgs
if (control.IsEffectivelyVisible)
{
RoutedEvent = Control.RequestBringIntoViewEvent,
TargetObject = control,
TargetRect = rect,
};
var ev = new RequestBringIntoViewEventArgs
{
RoutedEvent = Control.RequestBringIntoViewEvent,
TargetObject = control,
TargetRect = rect,
};
control.RaiseEvent(ev);
control.RaiseEvent(ev);
}
}
/// <summary>

6
src/Avalonia.Controls/Generators/ItemContainerGenerator.cs

@ -128,6 +128,12 @@ namespace Avalonia.Controls.Generators
}
Dematerialized?.Invoke(this, new ItemContainerEventArgs(startingIndex, result));
if (toMove.Count > 0)
{
var containers = toMove.Select(x => x.Value).ToList();
Recycled?.Invoke(this, new ItemContainerEventArgs(containers[0].Index, containers));
}
}
return result;

13
src/Avalonia.Controls/ItemsControl.cs

@ -302,19 +302,6 @@ namespace Avalonia.Controls
/// <param name="e">The details of the containers.</param>
protected virtual void OnContainersRecycled(ItemContainerEventArgs e)
{
var toRemove = new List<ILogical>();
foreach (var container in e.Containers)
{
// If the item is its own container, then it will be removed from the logical tree
// when it is removed from the Items collection.
if (container?.ContainerControl != container?.Item)
{
toRemove.Add(container.ContainerControl);
}
}
LogicalChildren.RemoveAll(toRemove);
}
/// <summary>

6
src/Avalonia.Controls/ListBox.cs

@ -66,7 +66,11 @@ namespace Avalonia.Controls
}
/// <inheritdoc/>
public new IList SelectedItems => base.SelectedItems;
public new IList SelectedItems
{
get => base.SelectedItems;
set => base.SelectedItems = value;
}
/// <summary>
/// Gets or sets the selection mode.

166
src/Avalonia.Controls/Mixins/ContentControlMixin.cs

@ -1,166 +0,0 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Runtime.CompilerServices;
using Avalonia.Collections;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.LogicalTree;
namespace Avalonia.Controls.Mixins
{
/// <summary>
/// Adds content control functionality to control classes.
/// </summary>
/// <para>
/// The <see cref="ContentControlMixin"/> adds behavior to a control which acts as a content
/// control such as <see cref="ContentControl"/> and <see cref="HeaderedItemsControl"/>. It
/// keeps the control's logical children in sync with the content being displayed by the
/// control.
/// </para>
public class ContentControlMixin
{
private static Lazy<ConditionalWeakTable<TemplatedControl, IDisposable>> subscriptions =
new Lazy<ConditionalWeakTable<TemplatedControl, IDisposable>>(() =>
new ConditionalWeakTable<TemplatedControl, IDisposable>());
/// <summary>
/// Initializes a new instance of the <see cref="SelectableMixin"/> class.
/// </summary>
/// <typeparam name="TControl">The control type.</typeparam>
/// <param name="content">The content property.</param>
/// <param name="logicalChildrenSelector">
/// Given an control of <typeparamref name="TControl"/> should return the control's
/// logical children collection.
/// </param>
/// <param name="presenterName">
/// The name of the content presenter in the control's template.
/// </param>
public static void Attach<TControl>(
AvaloniaProperty content,
Func<TControl, IAvaloniaList<ILogical>> logicalChildrenSelector,
string presenterName = "PART_ContentPresenter")
where TControl : TemplatedControl
{
Contract.Requires<ArgumentNullException>(content != null);
Contract.Requires<ArgumentNullException>(logicalChildrenSelector != null);
void ChildChanging(object s, AvaloniaPropertyChangedEventArgs e)
{
if (s is IControl sender && sender?.TemplatedParent is TControl parent)
{
UpdateLogicalChild(
sender,
logicalChildrenSelector(parent),
e.OldValue,
null);
}
}
void TemplateApplied(object s, RoutedEventArgs ev)
{
if (s is TControl sender)
{
var e = (TemplateAppliedEventArgs)ev;
var presenter = e.NameScope.Find(presenterName) as IContentPresenter;
if (presenter != null)
{
presenter.ApplyTemplate();
var logicalChildren = logicalChildrenSelector(sender);
var subscription = new CompositeDisposable();
presenter.ChildChanging += ChildChanging;
subscription.Add(Disposable.Create(() => presenter.ChildChanging -= ChildChanging));
subscription.Add(presenter
.GetPropertyChangedObservable(ContentPresenter.ChildProperty)
.Subscribe(c => UpdateLogicalChild(
sender,
logicalChildren,
null,
c.NewValue)));
UpdateLogicalChild(
sender,
logicalChildren,
null,
presenter.GetValue(ContentPresenter.ChildProperty));
if (subscriptions.Value.TryGetValue(sender, out IDisposable previousSubscription))
{
subscription = new CompositeDisposable(previousSubscription, subscription);
subscriptions.Value.Remove(sender);
}
subscriptions.Value.Add(sender, subscription);
}
}
}
TemplatedControl.TemplateAppliedEvent.AddClassHandler(
typeof(TControl),
TemplateApplied,
RoutingStrategies.Direct);
content.Changed.Subscribe(e =>
{
if (e.Sender is TControl sender)
{
var logicalChildren = logicalChildrenSelector(sender);
UpdateLogicalChild(sender, logicalChildren, e.OldValue, e.NewValue);
}
});
Control.TemplatedParentProperty.Changed.Subscribe(e =>
{
if (e.Sender is TControl sender)
{
var logicalChild = logicalChildrenSelector(sender).FirstOrDefault() as IControl;
logicalChild?.SetValue(Control.TemplatedParentProperty, sender.TemplatedParent);
}
});
TemplatedControl.TemplateProperty.Changed.Subscribe(e =>
{
if (e.Sender is TControl sender)
{
if (subscriptions.Value.TryGetValue(sender, out IDisposable subscription))
{
subscription.Dispose();
subscriptions.Value.Remove(sender);
}
}
});
}
private static void UpdateLogicalChild(
IControl control,
IAvaloniaList<ILogical> logicalChildren,
object oldValue,
object newValue)
{
if (oldValue != newValue)
{
if (oldValue is IControl child)
{
logicalChildren.Remove(child);
((ISetInheritanceParent)child).SetParent(child.Parent);
}
child = newValue as IControl;
if (child != null && !logicalChildren.Contains(child))
{
child.SetValue(Control.TemplatedParentProperty, control.TemplatedParent);
logicalChildren.Add(child);
}
}
}
}
}

5
src/Avalonia.Controls/Notifications/ReversibleStackPanel.cs

@ -39,6 +39,11 @@ namespace Avalonia.Controls
foreach (Control child in children)
{
if (!child.IsVisible)
{
continue;
}
double childWidth = child.DesiredSize.Width;
double childHeight = child.DesiredSize.Height;

2
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -377,7 +377,7 @@ namespace Avalonia.Controls.Platform
if (mouse?.Type == RawPointerEventType.NonClientLeftButtonDown)
{
Menu.Close();
Menu?.Close();
}
}

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

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

47
src/Avalonia.Controls/Presenters/ContentPresenter.cs

@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Controls.Utils;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Media;
@ -83,7 +84,6 @@ namespace Avalonia.Controls.Presenters
private IControl _child;
private bool _createdChild;
EventHandler<AvaloniaPropertyChangedEventArgs> _childChanging;
private IDataTemplate _dataTemplate;
private readonly BorderRenderHelper _borderRenderer = new BorderRenderHelper();
@ -190,12 +190,10 @@ namespace Avalonia.Controls.Presenters
set { SetValue(PaddingProperty, value); }
}
/// <inheritdoc/>
event EventHandler<AvaloniaPropertyChangedEventArgs> IContentPresenter.ChildChanging
{
add => _childChanging += value;
remove => _childChanging -= value;
}
/// <summary>
/// Gets the host content control.
/// </summary>
internal IContentPresenterHost Host { get; private set; }
/// <inheritdoc/>
public sealed override void ApplyTemplate()
@ -222,34 +220,16 @@ namespace Avalonia.Controls.Presenters
var content = Content;
var oldChild = Child;
var newChild = CreateChild();
var logicalChildren = Host?.LogicalChildren ?? LogicalChildren;
// Remove the old child if we're not recycling it.
if (newChild != oldChild)
{
if (oldChild != null)
{
VisualChildren.Remove(oldChild);
}
if (oldChild?.Parent == this)
{
// If we're the child's parent then the presenter isn't in a ContentControl's
// template.
LogicalChildren.Remove(oldChild);
}
else if (TemplatedParent != null)
{
// If we're in a ContentControl's template then invoke ChildChanging to let
// ContentControlMixin handle removing the logical child.
_childChanging?.Invoke(this, new AvaloniaPropertyChangedEventArgs(
this,
ChildProperty,
oldChild,
newChild,
BindingPriority.LocalValue));
}
else if (oldChild != null)
{
logicalChildren.Remove(oldChild);
((ISetInheritanceParent)oldChild).SetParent(oldChild.Parent);
}
}
@ -272,15 +252,11 @@ namespace Avalonia.Controls.Presenters
else if (newChild != oldChild)
{
((ISetInheritanceParent)newChild).SetParent(this);
Child = newChild;
// If we're in a ContentControl's template then the child's parent will have been
// set by ContentControlMixin in response to Child changing. If not, then we're
// standalone and should make the control our own logical child.
if (newChild.Parent == null && TemplatedParent == null)
if (!logicalChildren.Contains(newChild))
{
LogicalChildren.Add(newChild);
logicalChildren.Add(newChild);
}
VisualChildren.Add(newChild);
@ -459,7 +435,8 @@ namespace Avalonia.Controls.Presenters
private void TemplatedParentChanged(AvaloniaPropertyChangedEventArgs e)
{
(e.NewValue as IContentPresenterHost)?.RegisterContentPresenter(this);
var host = e.NewValue as IContentPresenterHost;
Host = host?.RegisterContentPresenter(this) == true ? host : null;
}
}
}

13
src/Avalonia.Controls/Presenters/IContentPresenter.cs

@ -1,8 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives;
namespace Avalonia.Controls.Presenters
@ -22,16 +20,5 @@ namespace Avalonia.Controls.Presenters
/// Gets or sets the content to be displayed by the presenter.
/// </summary>
object Content { get; set; }
/// <summary>
/// Raised when <see cref="Child"/> property is about to change.
/// </summary>
/// <remarks>
/// This event should be raised after the child has been removed from the visual tree,
/// but before the <see cref="Child"/> property has changed. It is intended for consumption
/// by <see cref="ContentControlMixin"/> in order to update the host control's logical
/// children.
/// </remarks>
event EventHandler<AvaloniaPropertyChangedEventArgs> ChildChanging;
}
}

13
src/Avalonia.Controls/Presenters/IContentPresenterHost.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Collections;
using Avalonia.LogicalTree;
using Avalonia.Styling;
namespace Avalonia.Controls.Presenters
@ -18,10 +20,19 @@ namespace Avalonia.Controls.Presenters
/// </remarks>
public interface IContentPresenterHost : ITemplatedControl
{
/// <summary>
/// Gets a collection describing the logical children of the host control.
/// </summary>
IAvaloniaList<ILogical> LogicalChildren { get; }
/// <summary>
/// Registers an <see cref="IContentPresenter"/> with a host control.
/// </summary>
/// <param name="presenter">The content presenter.</param>
void RegisterContentPresenter(IContentPresenter presenter);
/// <returns>
/// True if the content presenter should add its child to the logical children of the
/// host; otherwise false.
/// </returns>
bool RegisterContentPresenter(IContentPresenter presenter);
}
}

11
src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs

@ -295,11 +295,14 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
public override void ScrollIntoView(object item)
{
var index = Items.IndexOf(item);
if (index != -1)
if (Items != null)
{
ScrollIntoView(index);
var index = Items.IndexOf(item);
if (index != -1)
{
ScrollIntoView(index);
}
}
}

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

@ -141,7 +141,7 @@ namespace Avalonia.Controls.Presenters
/// <returns>True if the scroll offset was changed; otherwise false.</returns>
public bool BringDescendantIntoView(IVisual target, Rect targetRect)
{
if (Child == null)
if (Child?.IsEffectivelyVisible != true)
{
return false;
}

26
src/Avalonia.Controls/Primitives/HeaderedContentControl.cs

@ -4,6 +4,7 @@
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.LogicalTree;
namespace Avalonia.Controls.Primitives
{
@ -29,10 +30,7 @@ namespace Avalonia.Controls.Primitives
/// </summary>
static HeaderedContentControl()
{
ContentControlMixin.Attach<HeaderedContentControl>(
HeaderProperty,
x => x.LogicalChildren,
"PART_HeaderPresenter");
ContentProperty.Changed.AddClassHandler<HeaderedContentControl>(x => x.HeaderChanged);
}
/// <summary>
@ -63,13 +61,29 @@ namespace Avalonia.Controls.Primitives
}
/// <inheritdoc/>
protected override void RegisterContentPresenter(IContentPresenter presenter)
protected override bool RegisterContentPresenter(IContentPresenter presenter)
{
base.RegisterContentPresenter(presenter);
var result = base.RegisterContentPresenter(presenter);
if (presenter.Name == "PART_HeaderPresenter")
{
HeaderPresenter = presenter;
result = true;
}
return result;
}
private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.OldValue is ILogical oldChild)
{
LogicalChildren.Remove(oldChild);
}
if (e.NewValue is ILogical newChild)
{
LogicalChildren.Add(newChild);
}
}
}

32
src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs

@ -1,8 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Collections;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters;
using Avalonia.LogicalTree;
namespace Avalonia.Controls.Primitives
{
@ -22,10 +24,7 @@ namespace Avalonia.Controls.Primitives
/// </summary>
static HeaderedItemsControl()
{
ContentControlMixin.Attach<HeaderedItemsControl>(
HeaderProperty,
x => x.LogicalChildren,
"PART_HeaderPresenter");
HeaderProperty.Changed.AddClassHandler<HeaderedItemsControl>(x => x.HeaderChanged);
}
/// <summary>
@ -47,20 +46,39 @@ namespace Avalonia.Controls.Primitives
}
/// <inheritdoc/>
void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
IAvaloniaList<ILogical> IContentPresenterHost.LogicalChildren => LogicalChildren;
/// <inheritdoc/>
bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
{
RegisterContentPresenter(presenter);
return RegisterContentPresenter(presenter);
}
/// <summary>
/// Called when an <see cref="IContentPresenter"/> is registered with the control.
/// </summary>
/// <param name="presenter">The presenter.</param>
protected virtual void RegisterContentPresenter(IContentPresenter presenter)
protected virtual bool RegisterContentPresenter(IContentPresenter presenter)
{
if (presenter.Name == "PART_HeaderPresenter")
{
HeaderPresenter = presenter;
return true;
}
return false;
}
private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.OldValue is ILogical oldChild)
{
LogicalChildren.Remove(oldChild);
}
if (e.NewValue is ILogical newChild)
{
LogicalChildren.Add(newChild);
}
}
}

32
src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs

@ -1,8 +1,10 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Collections;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters;
using Avalonia.LogicalTree;
namespace Avalonia.Controls.Primitives
{
@ -22,10 +24,7 @@ namespace Avalonia.Controls.Primitives
/// </summary>
static HeaderedSelectingItemsControl()
{
ContentControlMixin.Attach<HeaderedSelectingItemsControl>(
HeaderProperty,
x => x.LogicalChildren,
"PART_HeaderPresenter");
HeaderProperty.Changed.AddClassHandler<HeaderedSelectingItemsControl>(x => x.HeaderChanged);
}
/// <summary>
@ -47,20 +46,39 @@ namespace Avalonia.Controls.Primitives
}
/// <inheritdoc/>
void IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
IAvaloniaList<ILogical> IContentPresenterHost.LogicalChildren => LogicalChildren;
/// <inheritdoc/>
bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
{
RegisterContentPresenter(presenter);
return RegisterContentPresenter(presenter);
}
/// <summary>
/// Called when an <see cref="IContentPresenter"/> is registered with the control.
/// </summary>
/// <param name="presenter">The presenter.</param>
protected virtual void RegisterContentPresenter(IContentPresenter presenter)
protected virtual bool RegisterContentPresenter(IContentPresenter presenter)
{
if (presenter.Name == "PART_HeaderPresenter")
{
HeaderPresenter = presenter;
return true;
}
return false;
}
private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.OldValue is ILogical oldChild)
{
LogicalChildren.Remove(oldChild);
}
if (e.NewValue is ILogical newChild)
{
LogicalChildren.Add(newChild);
}
}
}

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

@ -450,7 +450,14 @@ namespace Avalonia.Controls.Primitives
private bool IsChildOrThis(IVisual child)
{
return _popupHost != null && ((IVisual)_popupHost).FindCommonVisualAncestor(child) == _popupHost;
IVisual root = child.GetVisualRoot();
while (root is IHostedVisualTreeRoot hostedRoot )
{
if (root == this._popupHost)
return true;
root = hostedRoot.Host?.GetVisualRoot();
}
return false;
}
public bool IsInsidePopup(IVisual visual)

6
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs

@ -100,6 +100,12 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
?? screens.FirstOrDefault(s => s.Bounds.Contains(parentGeometry.TopLeft))
?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry))
?? screens.FirstOrDefault();
if (targetScreen != null && targetScreen.WorkingArea.IsEmpty)
{
return targetScreen.Bounds;
}
return targetScreen?.WorkingArea
?? new Rect(0, 0, double.MaxValue, double.MaxValue);
}

6
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositionerPopupImplHelper.cs

@ -32,7 +32,7 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
{
// Popup positioner operates with abstract coordinates, but in our case they are pixel ones
var point = _parent.PointToScreen(default);
var size = PixelSize.FromSize(_parent.ClientSize, _parent.Scaling);
var size = TranslateSize(_parent.ClientSize);
return new Rect(point.X, point.Y, size.Width, size.Height);
}
@ -43,8 +43,8 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
_moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _parent.Scaling);
}
public Point TranslatePoint(Point pt) => pt * _parent.Scaling;
public virtual Point TranslatePoint(Point pt) => pt * _parent.Scaling;
public Size TranslateSize(Size size) => size * _parent.Scaling;
public virtual Size TranslateSize(Size size) => size * _parent.Scaling;
}
}

41
src/Avalonia.Controls/Primitives/SelectingItemsControl.cs

@ -112,7 +112,7 @@ namespace Avalonia.Controls.Primitives
private bool _syncingSelectedItems;
private int _updateCount;
private int _updateSelectedIndex;
private IList _updateSelectedItems;
private object _updateSelectedItem;
/// <summary>
/// Initializes static members of the <see cref="SelectingItemsControl"/> class.
@ -160,7 +160,7 @@ namespace Avalonia.Controls.Primitives
else
{
_updateSelectedIndex = value;
_updateSelectedItems = null;
_updateSelectedItem = null;
}
}
}
@ -183,7 +183,7 @@ namespace Avalonia.Controls.Primitives
}
else
{
_updateSelectedItems = new AvaloniaList<object>(value);
_updateSelectedItem = value;
_updateSelectedIndex = int.MinValue;
}
}
@ -304,6 +304,11 @@ namespace Avalonia.Controls.Primitives
{
base.ItemsCollectionChanged(sender, e);
if (_updateCount > 0)
{
return;
}
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
@ -855,11 +860,6 @@ namespace Avalonia.Controls.Primitives
_selectedItem = ElementAt(Items, _selectedIndex);
RaisePropertyChanged(SelectedIndexProperty, -1, _selectedIndex, BindingPriority.LocalValue);
RaisePropertyChanged(SelectedItemProperty, null, _selectedItem, BindingPriority.LocalValue);
if (AutoScrollToSelectedItem)
{
ScrollIntoView(_selectedIndex);
}
}
}
@ -979,13 +979,14 @@ namespace Avalonia.Controls.Primitives
}
var item = ElementAt(Items, index);
var itemChanged = !Equals(item, oldItem);
var added = -1;
HashSet<int> removed = null;
_selectedIndex = index;
_selectedItem = item;
if (oldIndex != index || _selection.HasMultiple)
if (oldIndex != index || itemChanged || _selection.HasMultiple)
{
if (clear)
{
@ -1022,7 +1023,7 @@ namespace Avalonia.Controls.Primitives
index);
}
if (!Equals(item, oldItem))
if (itemChanged)
{
RaisePropertyChanged(
SelectedItemProperty,
@ -1045,6 +1046,11 @@ namespace Avalonia.Controls.Primitives
removed?.Select(x => ElementAt(Items, x)).ToArray() ?? Array.Empty<object>());
RaiseEvent(e);
}
if (AutoScrollToSelectedItem && _selectedIndex != -1)
{
ScrollIntoView(_selectedItem);
}
}
private void UpdateSelectedItems(Action action)
@ -1070,13 +1076,20 @@ namespace Avalonia.Controls.Primitives
private void UpdateFinished()
{
if (_updateSelectedIndex != int.MinValue)
if (_updateSelectedItem != null)
{
SelectedIndex = _updateSelectedIndex;
SelectedItem = _updateSelectedItem;
}
else if (_updateSelectedItems != null)
else
{
SelectedItems = _updateSelectedItems;
if (ItemCount == 0 && SelectedIndex != -1)
{
SelectedIndex = -1;
}
else
{
SelectedIndex = _updateSelectedIndex != int.MinValue ? _updateSelectedIndex : 0;
}
}
}

25
src/Avalonia.Controls/RadioButton.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls
public static readonly RadioButtonGroupManager Default = new RadioButtonGroupManager();
static readonly ConditionalWeakTable<IRenderRoot, RadioButtonGroupManager> s_registeredVisualRoots
= new ConditionalWeakTable<IRenderRoot, RadioButtonGroupManager>();
readonly Dictionary<string, List<WeakReference<RadioButton>>> s_registeredGroups
= new Dictionary<string, List<WeakReference<RadioButton>>>();
@ -127,13 +127,11 @@ namespace Avalonia.Controls
{
if (!string.IsNullOrEmpty(GroupName))
{
var manager = RadioButtonGroupManager.GetOrCreateForRoot(e.Root);
if (manager != _groupManager)
{
_groupManager.Remove(this, _groupName);
_groupManager = manager;
manager.Add(this);
}
_groupManager?.Remove(this, _groupName);
_groupManager = RadioButtonGroupManager.GetOrCreateForRoot(e.Root);
_groupManager.Add(this);
}
base.OnAttachedToVisualTree(e);
}
@ -141,9 +139,10 @@ namespace Avalonia.Controls
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnDetachedFromVisualTree(e);
if (!string.IsNullOrEmpty(GroupName) && _groupManager != null)
if (!string.IsNullOrEmpty(GroupName))
{
_groupManager.Remove(this, _groupName);
_groupManager?.Remove(this, _groupName);
}
}
@ -152,9 +151,9 @@ namespace Avalonia.Controls
string oldGroupName = GroupName;
if (newGroupName != oldGroupName)
{
if (!string.IsNullOrEmpty(oldGroupName) && _groupManager != null)
if (!string.IsNullOrEmpty(oldGroupName))
{
_groupManager.Remove(this, oldGroupName);
_groupManager?.Remove(this, oldGroupName);
}
_groupName = newGroupName;
if (!string.IsNullOrEmpty(newGroupName))
@ -181,7 +180,7 @@ namespace Avalonia.Controls
.GetVisualChildren()
.OfType<RadioButton>()
.Where(x => x != this);
foreach (var sibling in siblings)
{
if (sibling.IsChecked.GetValueOrDefault())

7
src/Avalonia.Controls/Repeater/ViewManager.cs

@ -128,8 +128,7 @@ namespace Avalonia.Controls
private void MoveFocusFromClearedIndex(int clearedIndex)
{
IControl focusedChild = null;
var focusCandidate = FindFocusCandidate(clearedIndex, focusedChild);
var focusCandidate = FindFocusCandidate(clearedIndex, out var focusedChild);
if (focusCandidate != null)
{
focusCandidate.Focus();
@ -145,7 +144,7 @@ namespace Avalonia.Controls
}
}
IControl FindFocusCandidate(int clearedIndex, IControl focusedChild)
IControl FindFocusCandidate(int clearedIndex, out IControl focusedChild)
{
// Walk through all the children and find elements with index before and after the cleared index.
// Note that during a delete the next element would now have the same index.
@ -183,7 +182,7 @@ namespace Avalonia.Controls
// TODO: Find the next element if one exists, if not use the previous element.
// If the container itself is not focusable, find a descendent that is.
focusedChild = nextElement;
return nextElement;
}

2
src/Avalonia.Controls/StackPanel.cs

@ -251,7 +251,7 @@ namespace Avalonia.Controls
{
var child = children[i];
if (child == null)
if (child == null || !child.IsVisible)
{ continue; }
if (fHorizontal)

36
src/Avalonia.Controls/TabControl.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System.Linq;
using Avalonia.Collections;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Presenters;
@ -9,6 +10,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.VisualTree;
namespace Avalonia.Controls
@ -16,7 +18,7 @@ namespace Avalonia.Controls
/// <summary>
/// A tab control that displays a tab strip along with the content of the selected tab.
/// </summary>
public class TabControl : SelectingItemsControl
public class TabControl : SelectingItemsControl, IContentPresenterHost
{
/// <summary>
/// Defines the <see cref="TabStripPlacement"/> property.
@ -68,10 +70,6 @@ namespace Avalonia.Controls
SelectionModeProperty.OverrideDefaultValue<TabControl>(SelectionMode.AlwaysSelected);
ItemsPanelProperty.OverrideDefaultValue<TabControl>(DefaultPanel);
AffectsMeasure<TabControl>(TabStripPlacementProperty);
ContentControlMixin.Attach<TabControl>(
SelectedContentProperty,
x => x.LogicalChildren,
"PART_SelectedContentHost");
}
/// <summary>
@ -136,7 +134,31 @@ namespace Avalonia.Controls
internal ItemsPresenter ItemsPresenterPart { get; private set; }
internal ContentPresenter ContentPart { get; private set; }
internal IContentPresenter ContentPart { get; private set; }
/// <inheritdoc/>
IAvaloniaList<ILogical> IContentPresenterHost.LogicalChildren => LogicalChildren;
/// <inheritdoc/>
bool IContentPresenterHost.RegisterContentPresenter(IContentPresenter presenter)
{
return RegisterContentPresenter(presenter);
}
/// <summary>
/// Called when an <see cref="IContentPresenter"/> is registered with the control.
/// </summary>
/// <param name="presenter">The presenter.</param>
protected virtual bool RegisterContentPresenter(IContentPresenter presenter)
{
if (presenter.Name == "PART_SelectedContentHost")
{
ContentPart = presenter;
return true;
}
return false;
}
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
@ -148,8 +170,6 @@ namespace Avalonia.Controls
base.OnTemplateApplied(e);
ItemsPresenterPart = e.NameScope.Get<ItemsPresenter>("PART_ItemsPresenter");
ContentPart = e.NameScope.Get<ContentPresenter>("PART_SelectedContentHost");
}
/// <inheritdoc/>

2
src/Avalonia.Controls/TopLevel.cs

@ -269,8 +269,8 @@ namespace Avalonia.Controls
/// </summary>
protected virtual void HandleClosed()
{
(this as IInputRoot).MouseDevice?.TopLevelClosed(this);
PlatformImpl = null;
Closed?.Invoke(this, EventArgs.Empty);
Renderer?.Dispose();
Renderer = null;

4
src/Avalonia.Controls/TreeView.cs

@ -105,11 +105,13 @@ namespace Avalonia.Controls
get => _selectedItem;
set
{
var selectedItems = SelectedItems;
SetAndRaise(SelectedItemProperty, ref _selectedItem, value);
if (value != null)
{
if (SelectedItems.Count != 1 || SelectedItems[0] != value)
if (selectedItems.Count != 1 || selectedItems[0] != value)
{
_syncingSelectedItems = true;
SelectSingleItem(value);

23
src/Avalonia.Controls/TreeViewItem.cs

@ -42,6 +42,7 @@ namespace Avalonia.Controls
new FuncTemplate<IPanel>(() => new StackPanel());
private TreeView _treeView;
private IControl _header;
private bool _isExpanded;
private int _level;
@ -53,6 +54,7 @@ namespace Avalonia.Controls
SelectableMixin.Attach<TreeViewItem>(IsSelectedProperty);
FocusableProperty.OverrideDefaultValue<TreeViewItem>(true);
ItemsPanelProperty.OverrideDefaultValue<TreeViewItem>(DefaultPanel);
RequestBringIntoViewEvent.AddClassHandler<TreeViewItem>(x => x.OnRequestBringIntoView);
}
/// <summary>
@ -120,6 +122,21 @@ namespace Avalonia.Controls
ItemContainerGenerator.Clear();
}
protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e)
{
if (e.TargetObject == this && _header != null)
{
var m = _header.TransformToVisual(this);
if (m.HasValue)
{
var bounds = new Rect(_header.Bounds.Size);
var rect = bounds.TransformToAABB(m.Value);
e.TargetRect = rect;
}
}
}
/// <inheritdoc/>
protected override void OnKeyDown(KeyEventArgs e)
{
@ -146,6 +163,12 @@ namespace Avalonia.Controls
// Don't call base.OnKeyDown - let events bubble up to containing TreeView.
}
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
_header = e.NameScope.Find<IControl>("PART_Header");
}
private static int CalculateDistanceFromLogicalParent<T>(ILogical logical, int @default = -1) where T : class
{
var result = 0;

204
src/Avalonia.Controls/WrapPanel.cs

@ -15,7 +15,7 @@ namespace Avalonia.Controls
/// Positions child elements in sequential position from left to right,
/// breaking content to the next line at the edge of the containing box.
/// Subsequent ordering happens sequentially from top to bottom or from right to left,
/// depending on the value of the Orientation property.
/// depending on the value of the <see cref="Orientation"/> property.
/// </summary>
public class WrapPanel : Panel, INavigableContainer
{
@ -25,6 +25,18 @@ namespace Avalonia.Controls
public static readonly StyledProperty<Orientation> OrientationProperty =
AvaloniaProperty.Register<WrapPanel, Orientation>(nameof(Orientation), defaultValue: Orientation.Horizontal);
/// <summary>
/// Defines the <see cref="ItemWidth"/> property.
/// </summary>
public static readonly StyledProperty<double> ItemWidthProperty =
AvaloniaProperty.Register<WrapPanel, double>(nameof(ItemWidth), double.NaN);
/// <summary>
/// Defines the <see cref="ItemHeight"/> property.
/// </summary>
public static readonly StyledProperty<double> ItemHeightProperty =
AvaloniaProperty.Register<WrapPanel, double>(nameof(ItemHeight), double.NaN);
/// <summary>
/// Initializes static members of the <see cref="WrapPanel"/> class.
/// </summary>
@ -42,6 +54,24 @@ namespace Avalonia.Controls
set { SetValue(OrientationProperty, value); }
}
/// <summary>
/// Gets or sets the width of all items in the WrapPanel.
/// </summary>
public double ItemWidth
{
get { return GetValue(ItemWidthProperty); }
set { SetValue(ItemWidthProperty, value); }
}
/// <summary>
/// Gets or sets the height of all items in the WrapPanel.
/// </summary>
public double ItemHeight
{
get { return GetValue(ItemHeightProperty); }
set { SetValue(ItemHeightProperty, value); }
}
/// <summary>
/// Gets the next control in the specified direction.
/// </summary>
@ -51,7 +81,9 @@ namespace Avalonia.Controls
/// <returns>The control.</returns>
IInputElement INavigableContainer.GetControl(NavigationDirection direction, IInputElement from, bool wrap)
{
var horiz = Orientation == Orientation.Horizontal;
var orientation = Orientation;
var children = Children;
bool horiz = orientation == Orientation.Horizontal;
int index = Children.IndexOf((IControl)from);
switch (direction)
@ -60,7 +92,7 @@ namespace Avalonia.Controls
index = 0;
break;
case NavigationDirection.Last:
index = Children.Count - 1;
index = children.Count - 1;
break;
case NavigationDirection.Next:
++index;
@ -82,9 +114,9 @@ namespace Avalonia.Controls
break;
}
if (index >= 0 && index < Children.Count)
if (index >= 0 && index < children.Count)
{
return Children[index];
return children[index];
}
else
{
@ -95,116 +127,140 @@ namespace Avalonia.Controls
/// <inheritdoc/>
protected override Size MeasureOverride(Size constraint)
{
var curLineSize = new UVSize(Orientation);
var panelSize = new UVSize(Orientation);
var uvConstraint = new UVSize(Orientation, constraint.Width, constraint.Height);
var childConstraint = new Size(constraint.Width, constraint.Height);
for (int i = 0, count = Children.Count; i < count; i++)
double itemWidth = ItemWidth;
double itemHeight = ItemHeight;
var orientation = Orientation;
var children = Children;
var curLineSize = new UVSize(orientation);
var panelSize = new UVSize(orientation);
var uvConstraint = new UVSize(orientation, constraint.Width, constraint.Height);
bool itemWidthSet = !double.IsNaN(itemWidth);
bool itemHeightSet = !double.IsNaN(itemHeight);
var childConstraint = new Size(
itemWidthSet ? itemWidth : constraint.Width,
itemHeightSet ? itemHeight : constraint.Height);
for (int i = 0, count = children.Count; i < count; i++)
{
var child = Children[i];
if (child == null) continue;
//Flow passes its own constrint to children
child.Measure(childConstraint);
//this is the size of the child in UV space
var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) //need to switch to another line
var child = children[i];
if (child != null)
{
panelSize.U = Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
curLineSize = sz;
// Flow passes its own constraint to children
child.Measure(childConstraint);
// This is the size of the child in UV space
var sz = new UVSize(orientation,
itemWidthSet ? itemWidth : child.DesiredSize.Width,
itemHeightSet ? itemHeight : child.DesiredSize.Height);
if (MathUtilities.GreaterThan(sz.U, uvConstraint.U)) //the element is wider then the constrint - give it a separate line
if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvConstraint.U)) // Need to switch to another line
{
panelSize.U = Max(sz.U, panelSize.U);
panelSize.V += sz.V;
curLineSize = new UVSize(Orientation);
panelSize.U = Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
curLineSize = sz;
if (MathUtilities.GreaterThan(sz.U, uvConstraint.U)) // The element is wider then the constraint - give it a separate line
{
panelSize.U = Max(sz.U, panelSize.U);
panelSize.V += sz.V;
curLineSize = new UVSize(orientation);
}
}
else // Continue to accumulate a line
{
curLineSize.U += sz.U;
curLineSize.V = Max(sz.V, curLineSize.V);
}
}
else //continue to accumulate a line
{
curLineSize.U += sz.U;
curLineSize.V = Max(sz.V, curLineSize.V);
}
}
//the last line size, if any should be added
// The last line size, if any should be added
panelSize.U = Max(curLineSize.U, panelSize.U);
panelSize.V += curLineSize.V;
//go from UV space to W/H space
// Go from UV space to W/H space
return new Size(panelSize.Width, panelSize.Height);
}
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
double itemWidth = ItemWidth;
double itemHeight = ItemHeight;
var orientation = Orientation;
var children = Children;
int firstInLine = 0;
double accumulatedV = 0;
UVSize curLineSize = new UVSize(Orientation);
UVSize uvFinalSize = new UVSize(Orientation, finalSize.Width, finalSize.Height);
for (int i = 0; i < Children.Count; i++)
double itemU = orientation == Orientation.Horizontal ? itemWidth : itemHeight;
var curLineSize = new UVSize(orientation);
var uvFinalSize = new UVSize(orientation, finalSize.Width, finalSize.Height);
bool itemWidthSet = !double.IsNaN(itemWidth);
bool itemHeightSet = !double.IsNaN(itemHeight);
bool useItemU = orientation == Orientation.Horizontal ? itemWidthSet : itemHeightSet;
for (int i = 0; i < children.Count; i++)
{
var child = Children[i];
if (child == null) continue;
var child = children[i];
if (child != null)
{
var sz = new UVSize(orientation,
itemWidthSet ? itemWidth : child.DesiredSize.Width,
itemHeightSet ? itemHeight : child.DesiredSize.Height);
var sz = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) // Need to switch to another line
{
ArrangeLine(accumulatedV, curLineSize.V, firstInLine, i, useItemU, itemU);
if (MathUtilities.GreaterThan(curLineSize.U + sz.U, uvFinalSize.U)) //need to switch to another line
{
arrangeLine(accumulatedV, curLineSize.V, firstInLine, i);
accumulatedV += curLineSize.V;
curLineSize = sz;
accumulatedV += curLineSize.V;
curLineSize = sz;
if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U)) // The element is wider then the constraint - give it a separate line
{
// Switch to next line which only contain one element
ArrangeLine(accumulatedV, sz.V, i, ++i, useItemU, itemU);
if (MathUtilities.GreaterThan(sz.U, uvFinalSize.U)) //the element is wider then the constraint - give it a separate line
accumulatedV += sz.V;
curLineSize = new UVSize(orientation);
}
firstInLine = i;
}
else // Continue to accumulate a line
{
//switch to next line which only contain one element
arrangeLine(accumulatedV, sz.V, i, ++i);
accumulatedV += sz.V;
curLineSize = new UVSize(Orientation);
curLineSize.U += sz.U;
curLineSize.V = Max(sz.V, curLineSize.V);
}
firstInLine = i;
}
else //continue to accumulate a line
{
curLineSize.U += sz.U;
curLineSize.V = Max(sz.V, curLineSize.V);
}
}
//arrange the last line, if any
if (firstInLine < Children.Count)
// Arrange the last line, if any
if (firstInLine < children.Count)
{
arrangeLine(accumulatedV, curLineSize.V, firstInLine, Children.Count);
ArrangeLine(accumulatedV, curLineSize.V, firstInLine, children.Count, useItemU, itemU);
}
return finalSize;
}
private void arrangeLine(double v, double lineV, int start, int end)
private void ArrangeLine(double v, double lineV, int start, int end, bool useItemU, double itemU)
{
var orientation = Orientation;
var children = Children;
double u = 0;
bool isHorizontal = (Orientation == Orientation.Horizontal);
bool isHorizontal = orientation == Orientation.Horizontal;
for (int i = start; i < end; i++)
{
var child = Children[i];
var child = children[i];
if (child != null)
{
UVSize childSize = new UVSize(Orientation, child.DesiredSize.Width, child.DesiredSize.Height);
double layoutSlotU = childSize.U;
var childSize = new UVSize(orientation, child.DesiredSize.Width, child.DesiredSize.Height);
double layoutSlotU = useItemU ? itemU : childSize.U;
child.Arrange(new Rect(
(isHorizontal ? u : v),
(isHorizontal ? v : u),
(isHorizontal ? layoutSlotU : lineV),
(isHorizontal ? lineV : layoutSlotU)));
isHorizontal ? u : v,
isHorizontal ? v : u,
isHorizontal ? layoutSlotU : lineV,
isHorizontal ? lineV : layoutSlotU));
u += layoutSlotU;
}
}
@ -232,12 +288,12 @@ namespace Avalonia.Controls
internal double Width
{
get { return (_orientation == Orientation.Horizontal ? U : V); }
get { return _orientation == Orientation.Horizontal ? U : V; }
set { if (_orientation == Orientation.Horizontal) U = value; else V = value; }
}
internal double Height
{
get { return (_orientation == Orientation.Horizontal ? V : U); }
get { return _orientation == Orientation.Horizontal ? V : U; }
set { if (_orientation == Orientation.Horizontal) V = value; else U = value; }
}
}

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

@ -178,6 +178,6 @@ namespace Avalonia.DesignerSupport.Remote
public int ScreenCount => 1;
public IReadOnlyList<Screen> AllScreens { get; } =
new Screen[] { new Screen(new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) };
new Screen[] { new Screen(1, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) };
}
}

7
src/Avalonia.Diagnostics/DevTools.xaml.cs

@ -28,6 +28,11 @@ namespace Avalonia
{
Diagnostics.DevTools.Attach(control, gesture);
}
public static void OpenDevTools(this TopLevel control)
{
Diagnostics.DevTools.OpenDevTools(control);
}
}
}
@ -73,7 +78,7 @@ namespace Avalonia.Diagnostics
RoutingStrategies.Tunnel);
}
private static void OpenDevTools(TopLevel control)
internal static void OpenDevTools(TopLevel control)
{
if (s_open.TryGetValue(control, out var devToolsWindow))
{

5
src/Avalonia.Diagnostics/Views/TreePageView.xaml.cs

@ -70,7 +70,10 @@ namespace Avalonia.Diagnostics.Views
private void TreeViewItemTemplateApplied(object sender, TemplateAppliedEventArgs e)
{
var item = (TreeViewItem)sender;
var header = item.HeaderPresenter.Child;
var headerPresenter = item.HeaderPresenter;
headerPresenter.ApplyTemplate();
var header = headerPresenter.Child;
header.PointerEnter += AddAdorner;
header.PointerLeave += RemoveAdorner;
item.TemplateApplied -= TreeViewItemTemplateApplied;

9
src/Avalonia.Input/AccessKeyHandler.cs

@ -231,15 +231,6 @@ namespace Avalonia.Input
}
break;
case Key.F10:
_owner.ShowAccessKeys = _showingAccessKeys = true;
if (MainMenu != null)
{
MainMenu.Open();
e.Handled = true;
}
break;
}
}

32
src/Avalonia.Input/Gestures.cs

@ -31,7 +31,7 @@ namespace Avalonia.Input
RoutedEvent.Register<ScrollGestureEventArgs>(
"ScrollGestureEnded", RoutingStrategies.Bubble, typeof(Gestures));
private static WeakReference<IInteractive> s_lastPress;
private static WeakReference<IInteractive> s_lastPress = new WeakReference<IInteractive>(null);
static Gestures()
{
@ -39,6 +39,36 @@ namespace Avalonia.Input
InputElement.PointerReleasedEvent.RouteFinished.Subscribe(PointerReleased);
}
public static void AddTappedHandler(IInteractive element, EventHandler<RoutedEventArgs> handler)
{
element.AddHandler(TappedEvent, handler);
}
public static void AddDoubleTappedHandler(IInteractive element, EventHandler<RoutedEventArgs> handler)
{
element.AddHandler(DoubleTappedEvent, handler);
}
public static void AddRightTappedHandler(IInteractive element, EventHandler<RoutedEventArgs> handler)
{
element.AddHandler(RightTappedEvent, handler);
}
public static void RemoveTappedHandler(IInteractive element, EventHandler<RoutedEventArgs> handler)
{
element.RemoveHandler(TappedEvent, handler);
}
public static void RemoveDoubleTappedHandler(IInteractive element, EventHandler<RoutedEventArgs> handler)
{
element.RemoveHandler(DoubleTappedEvent, handler);
}
public static void RemoveRightTappedHandler(IInteractive element, EventHandler<RoutedEventArgs> handler)
{
element.RemoveHandler(RightTappedEvent, handler);
}
private static void PointerPressed(RoutedEventArgs ev)
{
if (ev.Route == RoutingStrategies.Bubble)

2
src/Avalonia.Input/IMouseDevice.cs

@ -16,6 +16,8 @@ namespace Avalonia.Input
[Obsolete("Use PointerEventArgs.GetPosition")]
PixelPoint Position { get; }
void TopLevelClosed(IInputRoot root);
void SceneInvalidated(IInputRoot root, Rect rect);
}
}

12
src/Avalonia.Input/InputElement.cs

@ -565,9 +565,17 @@ namespace Avalonia.Input
{
IsEffectivelyEnabled = IsEnabledCore && (parent?.IsEffectivelyEnabled ?? true);
foreach (var child in this.GetVisualChildren().OfType<InputElement>())
// PERF-SENSITIVE: This is called on entire hierarchy and using foreach or LINQ
// will cause extra allocations and overhead.
var children = VisualChildren;
// ReSharper disable once ForCanBeConvertedToForeach
for (int i = 0; i < children.Count; ++i)
{
child.UpdateIsEffectivelyEnabled(this);
var child = children[i] as InputElement;
child?.UpdateIsEffectivelyEnabled(this);
}
}
}

4
src/Avalonia.Input/InputExtensions.cs

@ -13,6 +13,8 @@ namespace Avalonia.Input
/// </summary>
public static class InputExtensions
{
private static readonly Func<IVisual, bool> s_hitTestDelegate = IsHitTestVisible;
/// <summary>
/// Returns the active input elements at a point on an <see cref="IInputElement"/>.
/// </summary>
@ -25,7 +27,7 @@ namespace Avalonia.Input
{
Contract.Requires<ArgumentNullException>(element != null);
return element.GetVisualsAt(p, IsHitTestVisible).Cast<IInputElement>();
return element.GetVisualsAt(p, s_hitTestDelegate).Cast<IInputElement>();
}
/// <summary>

129
src/Avalonia.Input/KeyGesture.cs

@ -1,41 +1,56 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Collections.Generic;
using System.Linq;
namespace Avalonia.Input
{
/// <summary>
/// Defines a keyboard input combination.
/// </summary>
public sealed class KeyGesture : IEquatable<KeyGesture>
{
public KeyGesture()
private static readonly Dictionary<string, Key> s_keySynonyms = new Dictionary<string, Key>
{
{ "+", Key.OemPlus }, { "-", Key.OemMinus }, { ".", Key.OemPeriod }
};
[Obsolete("Use constructor taking KeyModifiers")]
public KeyGesture(Key key, InputModifiers modifiers)
{
Key = key;
KeyModifiers = (KeyModifiers)(((int)modifiers) & 0xf);
}
public KeyGesture(Key key, InputModifiers modifiers = InputModifiers.None)
public KeyGesture(Key key, KeyModifiers modifiers = KeyModifiers.None)
{
Key = key;
Modifiers = modifiers;
KeyModifiers = modifiers;
}
public bool Equals(KeyGesture other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Key == other.Key && Modifiers == other.Modifiers;
return Key == other.Key && KeyModifiers == other.KeyModifiers;
}
public override bool Equals(object obj)
{
if (ReferenceEquals(null, obj)) return false;
if (ReferenceEquals(this, obj)) return true;
return obj is KeyGesture && Equals((KeyGesture) obj);
return obj is KeyGesture && Equals((KeyGesture)obj);
}
public override int GetHashCode()
{
unchecked
{
return ((int) Key*397) ^ (int) Modifiers;
return ((int)Key * 397) ^ (int)KeyModifiers;
}
}
@ -49,85 +64,85 @@ namespace Avalonia.Input
return !Equals(left, right);
}
public Key Key { get; set; }
public Key Key { get; }
[Obsolete("Use KeyModifiers")]
public InputModifiers Modifiers
{
get => (InputModifiers)KeyModifiers;
set => KeyModifiers = (KeyModifiers)(((int)value) & 0xf);
}
public KeyModifiers KeyModifiers { get; set; }
public InputModifiers Modifiers => (InputModifiers)KeyModifiers;
static readonly Dictionary<string, Key> KeySynonyms = new Dictionary<string, Key>
{
{"+", Key.OemPlus },
{"-", Key.OemMinus},
{".", Key.OemPeriod }
};
//TODO: Move that to external key parser
static Key ParseKey(string key)
{
Key rv;
if (KeySynonyms.TryGetValue(key.ToLower(), out rv))
return rv;
return (Key)Enum.Parse(typeof (Key), key, true);
}
static InputModifiers ParseModifier(string modifier)
{
if (modifier.Equals("ctrl", StringComparison.OrdinalIgnoreCase))
return InputModifiers.Control;
return (InputModifiers) Enum.Parse(typeof (InputModifiers), modifier, true);
}
public KeyModifiers KeyModifiers { get; }
public static KeyGesture Parse(string gesture)
{
//string.Split can't be used here because "Ctrl++" is a perfectly valid key gesture
// string.Split can't be used here because "Ctrl++" is a perfectly valid key gesture
var parts = new List<string>();
var key = Key.None;
var keyModifiers = KeyModifiers.None;
var cstart = 0;
for (var c = 0; c <= gesture.Length; c++)
{
var ch = c == gesture.Length ? '\0' : gesture[c];
if (c == gesture.Length || (ch == '+' && cstart != c))
bool isLast = c == gesture.Length;
if (isLast || (ch == '+' && cstart != c))
{
parts.Add(gesture.Substring(cstart, c - cstart));
var partSpan = gesture.AsSpan(cstart, c - cstart).Trim();
if (isLast)
{
key = ParseKey(partSpan.ToString());
}
else
{
keyModifiers |= ParseModifier(partSpan);
}
cstart = c + 1;
}
}
for (var c = 0; c < parts.Count; c++)
parts[c] = parts[c].Trim();
var rv = new KeyGesture();
for (var c = 0; c < parts.Count; c++)
{
if (c == parts.Count - 1)
rv.Key = ParseKey(parts[c]);
else
rv.Modifiers |= ParseModifier(parts[c]);
}
return rv;
return new KeyGesture(key, keyModifiers);
}
public override string ToString()
{
var parts = new List<string>();
foreach (var flag in Enum.GetValues(typeof (InputModifiers)).Cast<InputModifiers>())
foreach (var flag in Enum.GetValues(typeof(KeyModifiers)).Cast<KeyModifiers>())
{
if (Modifiers.HasFlag(flag) && flag != InputModifiers.None)
if (KeyModifiers.HasFlag(flag) && flag != KeyModifiers.None)
{
parts.Add(flag.ToString());
}
}
parts.Add(Key.ToString());
return string.Join(" + ", parts);
}
public bool Matches(KeyEventArgs keyEvent) => ResolveNumPadOperationKey(keyEvent.Key) == Key && keyEvent.Modifiers == Modifiers;
public bool Matches(KeyEventArgs keyEvent) => ResolveNumPadOperationKey(keyEvent.Key) == Key && keyEvent.KeyModifiers == KeyModifiers;
// TODO: Move that to external key parser
private static Key ParseKey(string key)
{
if (s_keySynonyms.TryGetValue(key.ToLower(), out Key rv))
return rv;
return (Key)Enum.Parse(typeof(Key), key, true);
}
private static KeyModifiers ParseModifier(ReadOnlySpan<char> modifier)
{
if (modifier.Equals("ctrl".AsSpan(), StringComparison.OrdinalIgnoreCase))
{
return KeyModifiers.Control;
}
return (KeyModifiers)Enum.Parse(typeof(KeyModifiers), modifier.ToString(), true);
}
private Key ResolveNumPadOperationKey(Key key)
{

5
src/Avalonia.Input/MouseDevice.cs

@ -86,6 +86,11 @@ namespace Avalonia.Input
ProcessRawEvent(margs);
}
public void TopLevelClosed(IInputRoot root)
{
ClearPointerOver(this, 0, root, PointerPointProperties.None, KeyModifiers.None);
}
public void SceneInvalidated(IInputRoot root, Rect rect)
{
var clientPoint = root.PointToClient(Position);

4
src/Avalonia.Interactivity/EventSubscription.cs

@ -5,8 +5,12 @@ using System;
namespace Avalonia.Interactivity
{
internal delegate void HandlerInvokeSignature(Delegate baseHandler, object sender, RoutedEventArgs args);
internal class EventSubscription
{
public HandlerInvokeSignature InvokeAdapter { get; set; }
public Delegate Handler { get; set; }
public RoutingStrategies Routes { get; set; }

101
src/Avalonia.Interactivity/Interactive.cs

@ -4,8 +4,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Layout;
using Avalonia.VisualTree;
@ -18,15 +16,14 @@ namespace Avalonia.Interactivity
{
private Dictionary<RoutedEvent, List<EventSubscription>> _eventHandlers;
private static readonly Dictionary<Type, HandlerInvokeSignature> s_invokeHandlerCache = new Dictionary<Type, HandlerInvokeSignature>();
/// <summary>
/// Gets the interactive parent of the object for bubbling and tunneling events.
/// </summary>
IInteractive IInteractive.InteractiveParent => ((IVisual)this).VisualParent as IInteractive;
private Dictionary<RoutedEvent, List<EventSubscription>> EventHandlers
{
get { return _eventHandlers ?? (_eventHandlers = new Dictionary<RoutedEvent, List<EventSubscription>>()); }
}
private Dictionary<RoutedEvent, List<EventSubscription>> EventHandlers => _eventHandlers ?? (_eventHandlers = new Dictionary<RoutedEvent, List<EventSubscription>>());
/// <summary>
/// Adds a handler for the specified routed event.
@ -45,24 +42,14 @@ namespace Avalonia.Interactivity
Contract.Requires<ArgumentNullException>(routedEvent != null);
Contract.Requires<ArgumentNullException>(handler != null);
List<EventSubscription> subscriptions;
if (!EventHandlers.TryGetValue(routedEvent, out subscriptions))
{
subscriptions = new List<EventSubscription>();
EventHandlers.Add(routedEvent, subscriptions);
}
var sub = new EventSubscription
var subscription = new EventSubscription
{
Handler = handler,
Routes = routes,
AlsoIfHandled = handledEventsToo,
};
subscriptions.Add(sub);
return Disposable.Create(() => subscriptions.Remove(sub));
return AddEventSubscription(routedEvent, subscription);
}
/// <summary>
@ -80,7 +67,37 @@ namespace Avalonia.Interactivity
RoutingStrategies routes = RoutingStrategies.Direct | RoutingStrategies.Bubble,
bool handledEventsToo = false) where TEventArgs : RoutedEventArgs
{
return AddHandler(routedEvent, (Delegate)handler, routes, handledEventsToo);
Contract.Requires<ArgumentNullException>(routedEvent != null);
Contract.Requires<ArgumentNullException>(handler != null);
// EventHandler delegate is not covariant, this forces us to create small wrapper
// that will cast our type erased instance and invoke it.
Type eventArgsType = routedEvent.EventArgsType;
if (!s_invokeHandlerCache.TryGetValue(eventArgsType, out var invokeAdapter))
{
void InvokeAdapter(Delegate baseHandler, object sender, RoutedEventArgs args)
{
var typedHandler = (EventHandler<TEventArgs>)baseHandler;
var typedArgs = (TEventArgs)args;
typedHandler(sender, typedArgs);
}
invokeAdapter = InvokeAdapter;
s_invokeHandlerCache.Add(eventArgsType, invokeAdapter);
}
var subscription = new EventSubscription
{
InvokeAdapter = invokeAdapter,
Handler = handler,
Routes = routes,
AlsoIfHandled = handledEventsToo,
};
return AddEventSubscription(routedEvent, subscription);
}
/// <summary>
@ -196,10 +213,54 @@ namespace Avalonia.Interactivity
if (correctRoute && notFinished)
{
sub.Handler.DynamicInvoke(this, e);
if (sub.InvokeAdapter != null)
{
sub.InvokeAdapter(sub.Handler, this, e);
}
else
{
sub.Handler.DynamicInvoke(this, e);
}
}
}
}
}
private List<EventSubscription> GetEventSubscriptions(RoutedEvent routedEvent)
{
if (!EventHandlers.TryGetValue(routedEvent, out var subscriptions))
{
subscriptions = new List<EventSubscription>();
EventHandlers.Add(routedEvent, subscriptions);
}
return subscriptions;
}
private IDisposable AddEventSubscription(RoutedEvent routedEvent, EventSubscription subscription)
{
List<EventSubscription> subscriptions = GetEventSubscriptions(routedEvent);
subscriptions.Add(subscription);
return new UnsubscribeDisposable(subscriptions, subscription);
}
private sealed class UnsubscribeDisposable : IDisposable
{
private readonly List<EventSubscription> _subscriptions;
private readonly EventSubscription _subscription;
public UnsubscribeDisposable(List<EventSubscription> subscriptions, EventSubscription subscription)
{
_subscriptions = subscriptions;
_subscription = subscription;
}
public void Dispose()
{
_subscriptions.Remove(_subscription);
}
}
}
}

50
src/Avalonia.Interactivity/RoutedEvent.cs

@ -4,7 +4,6 @@
using System;
using System.Reactive.Subjects;
using System.Reflection;
using System.Runtime.ExceptionServices;
namespace Avalonia.Interactivity
{
@ -18,8 +17,8 @@ namespace Avalonia.Interactivity
public class RoutedEvent
{
private Subject<Tuple<object, RoutedEventArgs>> _raised = new Subject<Tuple<object, RoutedEventArgs>>();
private Subject<RoutedEventArgs> _routeFinished = new Subject<RoutedEventArgs>();
private readonly Subject<(object, RoutedEventArgs)> _raised = new Subject<(object, RoutedEventArgs)>();
private readonly Subject<RoutedEventArgs> _routeFinished = new Subject<RoutedEventArgs>();
public RoutedEvent(
string name,
@ -38,31 +37,15 @@ namespace Avalonia.Interactivity
RoutingStrategies = routingStrategies;
}
public Type EventArgsType
{
get;
private set;
}
public Type EventArgsType { get; }
public string Name
{
get;
private set;
}
public string Name { get; }
public Type OwnerType
{
get;
private set;
}
public Type OwnerType { get; }
public RoutingStrategies RoutingStrategies
{
get;
private set;
}
public RoutingStrategies RoutingStrategies { get; }
public IObservable<Tuple<object, RoutedEventArgs>> Raised => _raised;
public IObservable<(object, RoutedEventArgs)> Raised => _raised;
public IObservable<RoutedEventArgs> RouteFinished => _routeFinished;
public static RoutedEvent<TEventArgs> Register<TOwner, TEventArgs>(
@ -98,29 +81,20 @@ namespace Avalonia.Interactivity
{
return Raised.Subscribe(args =>
{
var sender = args.Item1;
var e = args.Item2;
(object sender, RoutedEventArgs e) = args;
if (targetType.GetTypeInfo().IsAssignableFrom(sender.GetType().GetTypeInfo()) &&
((e.Route == RoutingStrategies.Direct) || (e.Route & routes) != 0) &&
if (targetType.IsInstanceOfType(sender) &&
(e.Route == RoutingStrategies.Direct || (e.Route & routes) != 0) &&
(!e.Handled || handledEventsToo))
{
try
{
handler.DynamicInvoke(sender, e);
}
catch (TargetInvocationException ex)
{
// Unwrap the inner exception.
ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}
handler(sender, e);
}
});
}
internal void InvokeRaised(object sender, RoutedEventArgs e)
{
_raised.OnNext(Tuple.Create(sender, e));
_raised.OnNext((sender, e));
}
internal void InvokeRouteFinished(RoutedEventArgs e)

23
src/Avalonia.Native/AvaloniaNativeDeferredRendererLock.cs

@ -1,5 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Disposables;
using System.Threading;
using Avalonia.Native.Interop;
using Avalonia.Rendering;
@ -13,11 +16,27 @@ namespace Avalonia.Native
{
_window = window;
}
public IDisposable TryLock()
{
if (_window.TryLock())
return Disposable.Create(() => _window.Unlock());
return new UnlockDisposable(_window);
return null;
}
private sealed class UnlockDisposable : IDisposable
{
private IAvnWindowBase _window;
public UnlockDisposable(IAvnWindowBase window)
{
_window = window;
}
public void Dispose()
{
Interlocked.Exchange(ref _window, null)?.Unlock();
}
}
}
}

1
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -1,7 +1,6 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Diagnostics.Contracts;
using System.Runtime.InteropServices;
using Avalonia.Controls.Platform;
using Avalonia.Input;

19
src/Avalonia.Native/MacOSMountedVolumeInfoProvider.cs

@ -8,16 +8,16 @@ using Avalonia.Controls.Platform;
namespace Avalonia.Native
{
internal class WindowsMountedVolumeInfoListener : IDisposable
internal class MacOSMountedVolumeInfoListener : IDisposable
{
private readonly CompositeDisposable _disposables;
private readonly ObservableCollection<MountedVolumeInfo> _targetObs;
private bool _beenDisposed = false;
private ObservableCollection<MountedVolumeInfo> mountedDrives;
public WindowsMountedVolumeInfoListener(ObservableCollection<MountedVolumeInfo> mountedDrives)
public MacOSMountedVolumeInfoListener(ObservableCollection<MountedVolumeInfo> mountedDrives)
{
this.mountedDrives = mountedDrives;
_disposables = new CompositeDisposable();
var pollTimer = Observable.Interval(TimeSpan.FromSeconds(1))
@ -30,7 +30,8 @@ namespace Avalonia.Native
private void Poll(long _)
{
var mountVolInfos = Directory.GetDirectories("/Volumes")
var mountVolInfos = Directory.GetDirectories("/Volumes/")
.Where(p=> p != null)
.Select(p => new MountedVolumeInfo()
{
VolumeLabel = Path.GetFileName(p),
@ -38,15 +39,15 @@ namespace Avalonia.Native
VolumeSizeBytes = 0
})
.ToArray();
if (_targetObs.SequenceEqual(mountVolInfos))
if (mountedDrives.SequenceEqual(mountVolInfos))
return;
else
{
_targetObs.Clear();
mountedDrives.Clear();
foreach (var i in mountVolInfos)
_targetObs.Add(i);
mountedDrives.Add(i);
}
}
@ -72,7 +73,7 @@ namespace Avalonia.Native
public IDisposable Listen(ObservableCollection<MountedVolumeInfo> mountedDrives)
{
Contract.Requires<ArgumentNullException>(mountedDrives != null);
return new WindowsMountedVolumeInfoListener(mountedDrives);
return new MacOSMountedVolumeInfoListener(mountedDrives);
}
}
}

18
src/Avalonia.Native/OsxManagedPopupPositionerPopupImplHelper.cs

@ -0,0 +1,18 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Platform;
namespace Avalonia.Native
{
class OsxManagedPopupPositionerPopupImplHelper : ManagedPopupPositionerPopupImplHelper
{
public OsxManagedPopupPositionerPopupImplHelper(IWindowBaseImpl parent, MoveResizeDelegate moveResize) : base(parent, moveResize)
{
}
public override Point TranslatePoint(Point pt) => pt;
public override Size TranslateSize(Size size) => size;
}
}

2
src/Avalonia.Native/PopupImpl.cs

@ -22,7 +22,7 @@ namespace Avalonia.Native
{
Init(factory.CreatePopup(e), factory.CreateScreens());
}
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
PopupPositioner = new ManagedPopupPositioner(new OsxManagedPopupPositionerPopupImplHelper(parent, MoveResize));
}
private void MoveResize(PixelPoint position, Size size, double scaling)

1
src/Avalonia.Native/ScreenImpl.cs

@ -31,6 +31,7 @@ namespace Avalonia.Native
var screen = _native.GetScreen(i);
result[i] = new Screen(
screen.PixelDensity,
screen.Bounds.ToAvaloniaPixelRect(),
screen.WorkingArea.ToAvaloniaPixelRect(),
screen.Primary);

9
src/Avalonia.Native/WindowImplBase.cs

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
@ -48,6 +49,11 @@ namespace Avalonia.Native
Screen = new ScreenImpl(screens);
_savedLogicalSize = ClientSize;
_savedScaling = Scaling;
var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
.FirstOrDefault(m => m.Bounds.Contains(Position));
Resize(new Size(monitor.WorkingArea.Width * 0.75d, monitor.WorkingArea.Height * 0.7d));
}
public Size ClientSize
@ -300,7 +306,8 @@ namespace Avalonia.Native
_native.BeginMoveDrag();
}
public Size MaxClientSize => _native.GetMaxClientSize().ToAvaloniaSize();
public Size MaxClientSize => Screen.AllScreens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity))
.OrderByDescending(x => x.Width + x.Height).FirstOrDefault();
public void SetTopmost(bool value)
{

2
src/Avalonia.ReactiveUI/AvaloniaActivationForViewFetcher.cs

@ -27,7 +27,7 @@ namespace Avalonia.ReactiveUI
/// <summary>
/// Returns activation observable for activatable Avalonia view.
/// </summary>
public IObservable<bool> GetActivationForView(IActivatable view)
public IObservable<bool> GetActivationForView(IActivatableView view)
{
if (!(view is IVisual visual)) return Observable.Return(false);
if (view is WindowBase window) return GetActivationForWindowBase(window);

2
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@ -53,7 +53,7 @@ namespace Avalonia.ReactiveUI
/// ReactiveUI routing documentation website</see> for more info.
/// </para>
/// </remarks>
public class RoutedViewHost : TransitioningContentControl, IActivatable, IEnableLogger
public class RoutedViewHost : TransitioningContentControl, IActivatableView, IEnableLogger
{
/// <summary>
/// <see cref="AvaloniaProperty"/> for the <see cref="Router"/> property.

2
src/Avalonia.Styling/Controls/ResourceProviderExtensions.cs → src/Avalonia.Styling/Controls/ResourceNodeExtensions.cs

@ -3,7 +3,7 @@ using Avalonia.Reactive;
namespace Avalonia.Controls
{
public static class ResourceProviderExtensions
public static class ResourceNodeExtensions
{
/// <summary>
/// Finds the specified resource by searching up the logical tree and then global styles.

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

@ -7,9 +7,7 @@
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Border Name="PART_Indicator"
BorderThickness="1"
Background="{TemplateBinding Foreground}"/>
<Border Name="PART_Indicator" Background="{TemplateBinding Foreground}"/>
</Border>
</ControlTemplate>
</Setter>

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

@ -16,7 +16,9 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
TemplatedControl.IsTemplateFocusTarget="True">
<Grid ColumnDefinitions="16, *"
<Grid Name="PART_Header"
ColumnDefinitions="16, Auto"
HorizontalAlignment="Left"
Margin="{TemplateBinding Level, Mode=OneWay, Converter={StaticResource LeftMarginConverter}}" >
<ToggleButton Name="expander"
Focusable="False"

4
src/Avalonia.Visuals/Rect.cs

@ -371,12 +371,12 @@ namespace Avalonia
/// <returns>The bounding box</returns>
public Rect TransformToAABB(Matrix matrix)
{
var points = new[]
ReadOnlySpan<Point> points = stackalloc Point[4]
{
TopLeft.Transform(matrix),
TopRight.Transform(matrix),
BottomRight.Transform(matrix),
BottomLeft.Transform(matrix),
BottomLeft.Transform(matrix)
};
var left = double.MaxValue;

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

@ -246,22 +246,7 @@ namespace Avalonia.Rendering
{
try
{
IDrawingContextImpl GetContext()
{
if (context != null)
return context;
if ((RenderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true)
{
RenderTarget.Dispose();
RenderTarget = null;
}
if (RenderTarget == null)
RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
return context = RenderTarget.CreateDrawingContext(this);
}
var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(GetContext);
var (scene, updated) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context);
using (scene)
{
@ -271,9 +256,9 @@ namespace Avalonia.Rendering
if (DrawDirtyRects)
_dirtyRectsDisplay.Tick();
if (overlay)
RenderOverlay(scene.Item, GetContext());
RenderOverlay(scene.Item, ref context);
if (updated || forceComposite || overlay)
RenderComposite(scene.Item, GetContext());
RenderComposite(scene.Item, ref context);
}
}
}
@ -291,7 +276,7 @@ namespace Avalonia.Rendering
}
}
private (IRef<Scene> scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(Func<IDrawingContextImpl> contextFactory,
private (IRef<Scene> scene, bool updated) UpdateRenderLayersAndConsumeSceneIfNeeded(ref IDrawingContextImpl context,
bool recursiveCall = false)
{
IRef<Scene> sceneRef;
@ -304,7 +289,8 @@ namespace Avalonia.Rendering
var scene = sceneRef.Item;
if (scene.Generation != _lastSceneId)
{
var context = contextFactory();
EnsureDrawingContext(ref context);
Layers.Update(scene, context);
RenderToLayers(scene);
@ -325,7 +311,7 @@ namespace Avalonia.Rendering
if (!recursiveCall && Dispatcher.UIThread.CheckAccess() && NeedsUpdate)
{
UpdateScene();
var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(contextFactory, true);
var (rs, _) = UpdateRenderLayersAndConsumeSceneIfNeeded(ref context, true);
return (rs, true);
}
@ -432,8 +418,10 @@ namespace Avalonia.Rendering
}
private void RenderOverlay(Scene scene, IDrawingContextImpl parentContent)
private void RenderOverlay(Scene scene, ref IDrawingContextImpl parentContent)
{
EnsureDrawingContext(ref parentContent);
if (DrawDirtyRects)
{
var overlay = GetOverlay(parentContent, scene.Size, scene.Scaling);
@ -460,8 +448,10 @@ namespace Avalonia.Rendering
}
}
private void RenderComposite(Scene scene, IDrawingContextImpl context)
private void RenderComposite(Scene scene, ref IDrawingContextImpl context)
{
EnsureDrawingContext(ref context);
context.Clear(Colors.Transparent);
var clientRect = new Rect(scene.Size);
@ -503,6 +493,27 @@ namespace Avalonia.Rendering
}
}
private void EnsureDrawingContext(ref IDrawingContextImpl context)
{
if (context != null)
{
return;
}
if ((RenderTarget as IRenderTargetWithCorruptionInfo)?.IsCorrupted == true)
{
RenderTarget.Dispose();
RenderTarget = null;
}
if (RenderTarget == null)
{
RenderTarget = ((IRenderRoot)_root).CreateRenderTarget();
}
context = RenderTarget.CreateDrawingContext(this);
}
private void UpdateScene()
{
Dispatcher.UIThread.VerifyAccess();

30
src/Avalonia.Visuals/Rendering/ManagedDeferredRendererLock.cs

@ -1,5 +1,7 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using System.Reactive.Disposables;
using System.Threading;
namespace Avalonia.Rendering
@ -7,7 +9,7 @@ namespace Avalonia.Rendering
public class ManagedDeferredRendererLock : IDeferredRendererLock
{
private readonly object _lock = new object();
/// <summary>
/// Tries to lock the target surface or window
/// </summary>
@ -15,7 +17,7 @@ namespace Avalonia.Rendering
public IDisposable TryLock()
{
if (Monitor.TryEnter(_lock))
return Disposable.Create(() => Monitor.Exit(_lock));
return new UnlockDisposable(_lock);
return null;
}
@ -25,7 +27,27 @@ namespace Avalonia.Rendering
public IDisposable Lock()
{
Monitor.Enter(_lock);
return Disposable.Create(() => Monitor.Exit(_lock));
return new UnlockDisposable(_lock);
}
private sealed class UnlockDisposable : IDisposable
{
private object _lock;
public UnlockDisposable(object @lock)
{
_lock = @lock;
}
public void Dispose()
{
object @lock = Interlocked.Exchange(ref _lock, null);
if (@lock != null)
{
Monitor.Exit(@lock);
}
}
}
}
}

14
src/Avalonia.Visuals/Rendering/RenderLoop.cs

@ -91,7 +91,19 @@ namespace Avalonia.Rendering
{
try
{
if (_items.Any(item => item.NeedsUpdate) &&
bool needsUpdate = false;
foreach (IRenderLoopTask item in _items)
{
if (item.NeedsUpdate)
{
needsUpdate = true;
break;
}
}
if (needsUpdate &&
Interlocked.CompareExchange(ref _inUpdate, 1, 0) == 0)
{
_dispatcher.Post(() =>

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

Loading…
Cancel
Save