Browse Source

Merge branch 'master' into fixes/3887-transitions-styled-property

pull/3895/head
Jumar Macato 6 years ago
committed by GitHub
parent
commit
8525510ae6
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      Documentation/build.md
  2. 4
      build/SkiaSharp.props
  3. 57
      native/Avalonia.Native/inc/avalonia-native.h
  4. 4
      native/Avalonia.Native/src/OSX/Avalonia.Native.OSX.xcodeproj/project.pbxproj
  5. 3
      native/Avalonia.Native/src/OSX/AvnString.h
  6. 49
      native/Avalonia.Native/src/OSX/AvnString.mm
  7. 4
      native/Avalonia.Native/src/OSX/app.mm
  8. 90
      native/Avalonia.Native/src/OSX/clipboard.mm
  9. 8
      native/Avalonia.Native/src/OSX/common.h
  10. 89
      native/Avalonia.Native/src/OSX/dnd.mm
  11. 19
      native/Avalonia.Native/src/OSX/main.mm
  12. 7
      native/Avalonia.Native/src/OSX/window.h
  13. 470
      native/Avalonia.Native/src/OSX/window.mm
  14. 14
      nukebuild/Build.cs
  15. 5
      packages/Avalonia/AvaloniaBuildTasks.targets
  16. 4
      samples/BindingDemo/MainWindow.xaml
  17. 5
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  18. 2
      samples/ControlCatalog.NetCore/Program.cs
  19. 4
      samples/ControlCatalog/ControlCatalog.csproj
  20. 6
      samples/ControlCatalog/MainView.xaml
  21. 2
      samples/ControlCatalog/MainWindow.xaml
  22. 1
      samples/ControlCatalog/Pages/DialogsPage.xaml
  23. 14
      samples/ControlCatalog/Pages/DialogsPage.xaml.cs
  24. 12
      samples/ControlCatalog/Pages/DragAndDropPage.xaml
  25. 92
      samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs
  26. 2
      samples/ControlCatalog/Pages/ListBoxPage.xaml
  27. 29
      samples/ControlCatalog/Pages/OpenGlPage.xaml
  28. 401
      samples/ControlCatalog/Pages/OpenGlPage.xaml.cs
  29. 3
      samples/ControlCatalog/Pages/TreeViewPage.xaml
  30. 99
      samples/ControlCatalog/Pages/TreeViewPage.xaml.cs
  31. BIN
      samples/ControlCatalog/Pages/teapot.bin
  32. 27
      samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
  33. 115
      samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs
  34. 53
      samples/RenderDemo/Controls/LineBoundsDemoControl.cs
  35. 3
      samples/RenderDemo/MainWindow.xaml
  36. 28
      samples/RenderDemo/Pages/AnimationsPage.xaml
  37. 9
      samples/RenderDemo/Pages/LineBoundsPage.xaml
  38. 19
      samples/RenderDemo/Pages/LineBoundsPage.xaml.cs
  39. 3
      samples/RenderDemo/RenderDemo.csproj
  40. 2
      samples/VirtualizationDemo/MainWindow.xaml
  41. 14
      samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs
  42. 10
      scripts/ReplaceNugetCache.ps1
  43. 10
      scripts/ReplaceNugetCacheRelease.ps1
  44. 2
      src/Avalonia.Animation/Animation.cs
  45. 9
      src/Avalonia.Animation/AnimatorKeyFrame.cs
  46. 3
      src/Avalonia.Animation/Animators/Animator`1.cs
  47. 20
      src/Avalonia.Animation/KeyFrame.cs
  48. 349
      src/Avalonia.Animation/KeySpline.cs
  49. 50
      src/Avalonia.Base/Utilities/DisposableLock.cs
  50. 11
      src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs
  51. 9
      src/Avalonia.Build.Tasks/Extensions.cs
  52. 31
      src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs
  53. 4
      src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs
  54. 7
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  55. 2
      src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs
  56. 6
      src/Avalonia.Controls.DataGrid/DataGridCell.cs
  57. 2
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  58. 6
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  59. 6
      src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
  60. 6
      src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
  61. 8
      src/Avalonia.Controls/Application.cs
  62. 6
      src/Avalonia.Controls/AutoCompleteBox.cs
  63. 25
      src/Avalonia.Controls/Border.cs
  64. 2
      src/Avalonia.Controls/Button.cs
  65. 2
      src/Avalonia.Controls/ButtonSpinner.cs
  66. 4
      src/Avalonia.Controls/Calendar/Calendar.cs
  67. 6
      src/Avalonia.Controls/Calendar/CalendarButton.cs
  68. 6
      src/Avalonia.Controls/Calendar/CalendarDayButton.cs
  69. 4
      src/Avalonia.Controls/Calendar/CalendarItem.cs
  70. 6
      src/Avalonia.Controls/Calendar/DatePicker.cs
  71. 28
      src/Avalonia.Controls/ComboBox.cs
  72. 39
      src/Avalonia.Controls/ContextMenu.cs
  73. 20
      src/Avalonia.Controls/Generators/TreeContainerIndex.cs
  74. 249
      src/Avalonia.Controls/ISelectionModel.cs
  75. 3
      src/Avalonia.Controls/Image.cs
  76. 180
      src/Avalonia.Controls/IndexPath.cs
  77. 232
      src/Avalonia.Controls/IndexRange.cs
  78. 22
      src/Avalonia.Controls/ListBox.cs
  79. 4
      src/Avalonia.Controls/MenuItem.cs
  80. 4
      src/Avalonia.Controls/Notifications/WindowNotificationManager.cs
  81. 2
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  82. 6
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  83. 16
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  84. 21
      src/Avalonia.Controls/Presenters/ContentPresenter.cs
  85. 2
      src/Avalonia.Controls/Presenters/IItemsPresenter.cs
  86. 4
      src/Avalonia.Controls/Presenters/ItemVirtualizer.cs
  87. 15
      src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs
  88. 15
      src/Avalonia.Controls/Presenters/ItemVirtualizerSimple.cs
  89. 4
      src/Avalonia.Controls/Presenters/ItemsPresenter.cs
  90. 2
      src/Avalonia.Controls/Presenters/ItemsPresenterBase.cs
  91. 59
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  92. 4
      src/Avalonia.Controls/Primitives/ScrollBar.cs
  93. 839
      src/Avalonia.Controls/Primitives/SelectingItemsControl.cs
  94. 11
      src/Avalonia.Controls/Primitives/TemplatedControl.cs
  95. 2
      src/Avalonia.Controls/ProgressBar.cs
  96. 4
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  97. 2
      src/Avalonia.Controls/RepeatButton.cs
  98. 28
      src/Avalonia.Controls/Repeater/ItemsRepeater.cs
  99. 2
      src/Avalonia.Controls/Repeater/ItemsSourceView.cs
  100. 36
      src/Avalonia.Controls/Repeater/ViewManager.cs

2
Documentation/build.md

@ -36,7 +36,7 @@ Avalonia requires [CastXML](https://github.com/CastXML/CastXML) for XML processi
On macOS:
```
brew install castxml
brew install https://raw.githubusercontent.com/Homebrew/homebrew-core/8a004a91a7fcd3f6620d5b01b6541ff0a640ffba/Formula/castxml.rb
```
On Debian based Linux (Debian, Ubuntu, Mint, etc):

4
build/SkiaSharp.props

@ -1,6 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="SkiaSharp" Version="1.68.1" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.1" />
<PackageReference Include="SkiaSharp" Version="1.68.2.1" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="SkiaSharp.NativeAssets.Linux" Version="1.68.2.1" />
</ItemGroup>
</Project>

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

@ -22,6 +22,9 @@ struct IAvnGlSurfaceRenderTarget;
struct IAvnGlSurfaceRenderingSession;
struct IAvnMenu;
struct IAvnMenuItem;
struct IAvnStringArray;
struct IAvnDndResultCallback;
struct IAvnGCHandleDeallocatorCallback;
struct IAvnMenuEvents;
enum SystemDecorations {
@ -130,11 +133,28 @@ enum AvnInputModifiers
XButton2MouseButton = 256
};
enum class AvnDragDropEffects
{
None = 0,
Copy = 1,
Move = 2,
Link = 4,
};
enum class AvnDragEventType
{
Enter,
Over,
Leave,
Drop
};
enum AvnWindowState
{
Normal,
Minimized,
Maximized,
FullScreen,
};
enum AvnStandardCursorType
@ -187,7 +207,7 @@ enum AvnMenuItemToggleType
AVNCOM(IAvaloniaNativeFactory, 01) : IUnknown
{
public:
virtual HRESULT Initialize() = 0;
virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator) = 0;
virtual IAvnMacOptions* GetMacOptions() = 0;
virtual HRESULT CreateWindow(IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnWindow** ppv) = 0;
virtual HRESULT CreatePopup (IAvnWindowEvents* cb, IAvnGlContext* gl, IAvnPopup** ppv) = 0;
@ -195,6 +215,7 @@ public:
virtual HRESULT CreateSystemDialogs (IAvnSystemDialogs** ppv) = 0;
virtual HRESULT CreateScreens (IAvnScreens** ppv) = 0;
virtual HRESULT CreateClipboard(IAvnClipboard** ppv) = 0;
virtual HRESULT CreateDndClipboard(IAvnClipboard** ppv) = 0;
virtual HRESULT CreateCursorFactory(IAvnCursorFactory** ppv) = 0;
virtual HRESULT ObtainGlDisplay(IAvnGlDisplay** ppv) = 0;
virtual HRESULT SetAppMenu(IAvnMenu* menu) = 0;
@ -235,6 +256,8 @@ AVNCOM(IAvnWindowBase, 02) : IUnknown
virtual HRESULT ObtainNSWindowHandleRetained(void** retOut) = 0;
virtual HRESULT ObtainNSViewHandle(void** retOut) = 0;
virtual HRESULT ObtainNSViewHandleRetained(void** retOut) = 0;
virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
IAvnClipboard* clipboard, IAvnDndResultCallback* cb, void* sourceHandle) = 0;
};
AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase
@ -244,9 +267,10 @@ AVNCOM(IAvnPopup, 03) : virtual IAvnWindowBase
AVNCOM(IAvnWindow, 04) : virtual IAvnWindowBase
{
virtual HRESULT ShowDialog (IAvnWindow* parent) = 0;
virtual HRESULT SetEnabled (bool enable) = 0;
virtual HRESULT SetParent (IAvnWindow* parent) = 0;
virtual HRESULT SetCanResize(bool value) = 0;
virtual HRESULT SetHasDecorations(SystemDecorations value) = 0;
virtual HRESULT SetDecorations(SystemDecorations value) = 0;
virtual HRESULT SetTitle (void* utf8Title) = 0;
virtual HRESULT SetTitleBarColor (AvnColor color) = 0;
virtual HRESULT SetWindowState(AvnWindowState state) = 0;
@ -270,6 +294,9 @@ AVNCOM(IAvnWindowBaseEvents, 05) : IUnknown
virtual bool RawTextInputEvent (unsigned int timeStamp, const char* text) = 0;
virtual void ScalingChanged(double scaling) = 0;
virtual void RunRenderPriorityJobs() = 0;
virtual AvnDragDropEffects DragEvent(AvnDragEventType type, AvnPoint position,
AvnInputModifiers modifiers, AvnDragDropEffects effects,
IAvnClipboard* clipboard, void* dataObjectHandle) = 0;
};
@ -283,6 +310,8 @@ AVNCOM(IAvnWindowEvents, 06) : IAvnWindowBaseEvents
virtual bool Closing () = 0;
virtual void WindowStateChanged (AvnWindowState state) = 0;
virtual void GotInputWhenDisabled () = 0;
};
AVNCOM(IAvnMacOptions, 07) : IUnknown
@ -353,8 +382,10 @@ AVNCOM(IAvnScreens, 0e) : IUnknown
AVNCOM(IAvnClipboard, 0f) : IUnknown
{
virtual HRESULT GetText (IAvnString**ppv) = 0;
virtual HRESULT SetText (void* utf8Text) = 0;
virtual HRESULT GetText (char* type, IAvnString**ppv) = 0;
virtual HRESULT SetText (char* type, void* utf8Text) = 0;
virtual HRESULT ObtainFormats(IAvnStringArray**ppv) = 0;
virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) = 0;
virtual HRESULT Clear() = 0;
};
@ -427,4 +458,20 @@ AVNCOM(IAvnMenuEvents, 1A) : IUnknown
virtual void NeedsUpdate () = 0;
};
AVNCOM(IAvnStringArray, 20) : IUnknown
{
virtual unsigned int GetCount() = 0;
virtual HRESULT Get(unsigned int index, IAvnString**ppv) = 0;
};
AVNCOM(IAvnDndResultCallback, 21) : IUnknown
{
virtual void OnDragAndDropComplete(AvnDragDropEffects effecct) = 0;
};
AVNCOM(IAvnGCHandleDeallocatorCallback, 22) : IUnknown
{
virtual void FreeGCHandle(void* handle) = 0;
};
extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative();

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

@ -12,6 +12,7 @@
1A3E5EAA23E9F26C00EDE661 /* IOSurface.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */; };
1A3E5EAE23E9FB1300EDE661 /* cgl.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A3E5EAD23E9FB1300EDE661 /* cgl.mm */; };
1A3E5EB023E9FE8300EDE661 /* QuartzCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */; };
1A465D10246AB61600C5858B /* dnd.mm in Sources */ = {isa = PBXBuildFile; fileRef = 1A465D0F246AB61600C5858B /* dnd.mm */; };
37155CE4233C00EB0034DCE9 /* menu.h in Headers */ = {isa = PBXBuildFile; fileRef = 37155CE3233C00EB0034DCE9 /* menu.h */; };
37A517B32159597E00FBA241 /* Screens.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37A517B22159597E00FBA241 /* Screens.mm */; };
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */ = {isa = PBXBuildFile; fileRef = 37C09D8721580FE4006A6758 /* SystemDialogs.mm */; };
@ -33,6 +34,7 @@
1A3E5EA923E9F26C00EDE661 /* IOSurface.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = IOSurface.framework; path = System/Library/Frameworks/IOSurface.framework; sourceTree = SDKROOT; };
1A3E5EAD23E9FB1300EDE661 /* cgl.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = cgl.mm; sourceTree = "<group>"; };
1A3E5EAF23E9FE8300EDE661 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
1A465D0F246AB61600C5858B /* dnd.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = dnd.mm; sourceTree = "<group>"; };
37155CE3233C00EB0034DCE9 /* menu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = menu.h; 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>"; };
@ -92,6 +94,7 @@
5BF943652167AD1D009CAE35 /* cursor.h */,
5B21A981216530F500CEE36E /* cursor.mm */,
5B8BD94E215BFEA6005ED2A7 /* clipboard.mm */,
1A465D0F246AB61600C5858B /* dnd.mm */,
AB8F7D6A21482D7F0057DBA5 /* platformthreading.mm */,
AB661C212148288600291242 /* common.h */,
379860FE214DA0C000CD0246 /* KeyTransform.h */,
@ -196,6 +199,7 @@
37E2330F21583241000CB7E2 /* KeyTransform.mm in Sources */,
520624B322973F4100C4DCEF /* menu.mm in Sources */,
37A517B32159597E00FBA241 /* Screens.mm in Sources */,
1A465D10246AB61600C5858B /* dnd.mm in Sources */,
AB00E4F72147CA920032A60A /* main.mm in Sources */,
37C09D8821580FE4006A6758 /* SystemDialogs.mm in Sources */,
AB661C202148286E00291242 /* window.mm in Sources */,

3
native/Avalonia.Native/src/OSX/AvnString.h

@ -10,5 +10,6 @@
#define AvnString_h
extern IAvnString* CreateAvnString(NSString* string);
extern IAvnStringArray* CreateAvnStringArray(NSArray<NSString*>* array);
extern IAvnStringArray* CreateAvnStringArray(NSString* string);
#endif /* AvnString_h */

49
native/Avalonia.Native/src/OSX/AvnString.mm

@ -7,6 +7,7 @@
//
#include "common.h"
#include <vector>
class AvnStringImpl : public virtual ComSingleObject<IAvnString, &IID_IAvnString>
{
@ -61,7 +62,55 @@ public:
}
};
class AvnStringArrayImpl : public virtual ComSingleObject<IAvnStringArray, &IID_IAvnStringArray>
{
private:
std::vector<ComPtr<IAvnString>> _list;
public:
FORWARD_IUNKNOWN()
AvnStringArrayImpl(NSArray<NSString*>* array)
{
for(int c = 0; c < [array count]; c++)
{
ComPtr<IAvnString> s;
*s.getPPV() = new AvnStringImpl([array objectAtIndex:c]);
_list.push_back(s);
}
}
AvnStringArrayImpl(NSString* string)
{
ComPtr<IAvnString> s;
*s.getPPV() = new AvnStringImpl(string);
_list.push_back(s);
}
virtual unsigned int GetCount() override
{
return (unsigned int)_list.size();
}
virtual HRESULT Get(unsigned int index, IAvnString**ppv) override
{
if(_list.size() <= index)
return E_INVALIDARG;
*ppv = _list[index].getRetainedReference();
return S_OK;
}
};
IAvnString* CreateAvnString(NSString* string)
{
return new AvnStringImpl(string);
}
IAvnStringArray* CreateAvnStringArray(NSArray<NSString*> * array)
{
return new AvnStringArrayImpl(array);
}
IAvnStringArray* CreateAvnStringArray(NSString* string)
{
return new AvnStringArrayImpl(string);
}

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

@ -15,6 +15,10 @@ NSApplicationActivationPolicy AvnDesiredActivationPolicy = NSApplicationActivati
}
[[NSApplication sharedApplication] setActivationPolicy: AvnDesiredActivationPolicy];
[[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"];
[[NSApplication sharedApplication] setHelpMenu: [[NSMenu new] initWithTitle:@""]];
}
}

90
native/Avalonia.Native/src/OSX/clipboard.mm

@ -3,16 +3,27 @@
class Clipboard : public ComSingleObject<IAvnClipboard, &IID_IAvnClipboard>
{
private:
NSPasteboard* _pb;
NSPasteboardItem* _item;
public:
FORWARD_IUNKNOWN()
Clipboard()
Clipboard(NSPasteboard* pasteboard, NSPasteboardItem* item)
{
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
[pasteBoard stringForType:NSPasteboardTypeString];
if(pasteboard == nil && item == nil)
pasteboard = [NSPasteboard generalPasteboard];
_pb = pasteboard;
_item = item;
}
virtual HRESULT GetText (IAvnString**ppv) override
NSPasteboardItem* TryGetItem()
{
return _item;
}
virtual HRESULT GetText (char* type, IAvnString**ppv) override
{
@autoreleasepool
{
@ -20,20 +31,53 @@ public:
{
return E_POINTER;
}
NSString* typeString = [NSString stringWithUTF8String:(const char*)type];
NSString* string = _item == nil ? [_pb stringForType:typeString] : [_item stringForType:typeString];
*ppv = CreateAvnString([[NSPasteboard generalPasteboard] stringForType:NSPasteboardTypeString]);
*ppv = CreateAvnString(string);
return S_OK;
}
}
virtual HRESULT SetText (void* utf8String) override
virtual HRESULT GetStrings(char* type, IAvnStringArray**ppv) override
{
@autoreleasepool
{
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
[pasteBoard clearContents];
[pasteBoard setString:[NSString stringWithUTF8String:(const char*)utf8String] forType:NSPasteboardTypeString];
*ppv= nil;
NSString* typeString = [NSString stringWithUTF8String:(const char*)type];
NSObject* data = _item == nil ? [_pb propertyListForType: typeString] : [_item propertyListForType: typeString];
if(data == nil)
return S_OK;
if([data isKindOfClass: [NSString class]])
{
*ppv = CreateAvnStringArray((NSString*) data);
return S_OK;
}
NSArray* arr = (NSArray*)data;
for(int c = 0; c < [arr count]; c++)
if(![[arr objectAtIndex:c] isKindOfClass:[NSString class]])
return E_INVALIDARG;
*ppv = CreateAvnStringArray(arr);
return S_OK;
}
}
virtual HRESULT SetText (char* type, void* utf8String) override
{
Clear();
@autoreleasepool
{
auto string = [NSString stringWithUTF8String:(const char*)utf8String];
auto typeString = [NSString stringWithUTF8String:(const char*)type];
if(_item == nil)
[_pb setString: string forType: typeString];
else
[_item setString: string forType:typeString];
}
return S_OK;
@ -43,16 +87,34 @@ public:
{
@autoreleasepool
{
NSPasteboard *pasteBoard = [NSPasteboard generalPasteboard];
[pasteBoard clearContents];
[pasteBoard setString:@"" forType:NSPasteboardTypeString];
if(_item != nil)
_item = [NSPasteboardItem new];
else
{
[_pb clearContents];
[_pb setString:@"" forType:NSPasteboardTypeString];
}
}
return S_OK;
}
virtual HRESULT ObtainFormats(IAvnStringArray** ppv) override
{
*ppv = CreateAvnStringArray(_item == nil ? [_pb types] : [_item types]);
return S_OK;
}
};
extern IAvnClipboard* CreateClipboard()
extern IAvnClipboard* CreateClipboard(NSPasteboard* pb, NSPasteboardItem* item)
{
return new Clipboard(pb, item);
}
extern NSPasteboardItem* TryGetPasteboardItem(IAvnClipboard*cb)
{
return new Clipboard();
auto clipboard = dynamic_cast<Clipboard*>(cb);
if(clipboard == nil)
return nil;
return clipboard->TryGetItem();
}

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

@ -8,11 +8,17 @@
#include <pthread.h>
extern IAvnPlatformThreadingInterface* CreatePlatformThreading();
extern void FreeAvnGCHandle(void* handle);
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events, IAvnGlContext* gl);
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events, IAvnGlContext* gl);
extern IAvnSystemDialogs* CreateSystemDialogs();
extern IAvnScreens* CreateScreens();
extern IAvnClipboard* CreateClipboard();
extern IAvnClipboard* CreateClipboard(NSPasteboard*, NSPasteboardItem*);
extern NSPasteboardItem* TryGetPasteboardItem(IAvnClipboard*);
extern NSObject<NSDraggingSource>* CreateDraggingSource(NSDragOperation op, IAvnDndResultCallback* cb, void* handle);
extern void* GetAvnDataObjectHandleFromDraggingInfo(NSObject<NSDraggingInfo>* info);
extern NSString* GetAvnCustomDataType();
extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop);
extern IAvnCursorFactory* CreateCursorFactory();
extern IAvnGlDisplay* GetGlDisplay();
extern IAvnMenu* CreateAppMenu(IAvnMenuEvents* events);

89
native/Avalonia.Native/src/OSX/dnd.mm

@ -0,0 +1,89 @@
#include "common.h"
extern AvnDragDropEffects ConvertDragDropEffects(NSDragOperation nsop)
{
int effects = 0;
if((nsop & NSDragOperationCopy) != 0)
effects |= (int)AvnDragDropEffects::Copy;
if((nsop & NSDragOperationMove) != 0)
effects |= (int)AvnDragDropEffects::Move;
if((nsop & NSDragOperationLink) != 0)
effects |= (int)AvnDragDropEffects::Link;
return (AvnDragDropEffects)effects;
};
extern NSString* GetAvnCustomDataType()
{
char buffer[256];
sprintf(buffer, "net.avaloniaui.inproc.uti.n%in", getpid());
return [NSString stringWithUTF8String:buffer];
}
@interface AvnDndSource : NSObject<NSDraggingSource>
@end
@implementation AvnDndSource
{
NSDragOperation _operation;
ComPtr<IAvnDndResultCallback> _cb;
void* _sourceHandle;
};
- (NSDragOperation)draggingSession:(nonnull NSDraggingSession *)session sourceOperationMaskForDraggingContext:(NSDraggingContext)context
{
return NSDragOperationCopy;
}
- (AvnDndSource*) initWithOperation: (NSDragOperation)operation
andCallback: (IAvnDndResultCallback*) cb
andSourceHandle: (void*) handle
{
self = [super init];
_operation = operation;
_cb = cb;
_sourceHandle = handle;
return self;
}
- (void)draggingSession:(NSDraggingSession *)session
endedAtPoint:(NSPoint)screenPoint
operation:(NSDragOperation)operation
{
if(_cb != nil)
{
auto cb = _cb;
_cb = nil;
cb->OnDragAndDropComplete(ConvertDragDropEffects(operation));
}
if(_sourceHandle != nil)
{
FreeAvnGCHandle(_sourceHandle);
_sourceHandle = nil;
}
}
- (void*) gcHandle
{
return _sourceHandle;
}
@end
extern NSObject<NSDraggingSource>* CreateDraggingSource(NSDragOperation op, IAvnDndResultCallback* cb, void* handle)
{
return [[AvnDndSource alloc] initWithOperation:op andCallback:cb andSourceHandle:handle];
};
extern void* GetAvnDataObjectHandleFromDraggingInfo(NSObject<NSDraggingInfo>* info)
{
id obj = [info draggingSource];
if(obj == nil)
return nil;
if([obj isKindOfClass: [AvnDndSource class]])
{
auto src = (AvnDndSource*)obj;
return [src gcHandle];
}
return nil;
}

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

@ -150,14 +150,15 @@ public:
}
@end
static ComPtr<IAvnGCHandleDeallocatorCallback> _deallocator;
class AvaloniaNative : public ComSingleObject<IAvaloniaNativeFactory, &IID_IAvaloniaNativeFactory>
{
public:
FORWARD_IUNKNOWN()
virtual HRESULT Initialize() override
virtual HRESULT Initialize(IAvnGCHandleDeallocatorCallback* deallocator) override
{
_deallocator = deallocator;
@autoreleasepool{
[[ThreadingInitializer new] do];
}
@ -207,7 +208,13 @@ public:
virtual HRESULT CreateClipboard(IAvnClipboard** ppv) override
{
*ppv = ::CreateClipboard ();
*ppv = ::CreateClipboard (nil, nil);
return S_OK;
}
virtual HRESULT CreateDndClipboard(IAvnClipboard** ppv) override
{
*ppv = ::CreateClipboard (nil, [NSPasteboardItem new]);
return S_OK;
}
@ -257,6 +264,12 @@ extern "C" IAvaloniaNativeFactory* CreateAvaloniaNative()
return new AvaloniaNative();
};
extern void FreeAvnGCHandle(void* handle)
{
if(_deallocator != nil)
_deallocator->FreeGCHandle(handle);
}
NSSize ToNSSize (AvnSize s)
{
NSSize result;

7
native/Avalonia.Native/src/OSX/window.h

@ -3,7 +3,7 @@
class WindowBaseImpl;
@interface AvnView : NSView<NSTextInputClient>
@interface AvnView : NSView<NSTextInputClient, NSDraggingDestination>
-(AvnView* _Nonnull) initWithParent: (WindowBaseImpl* _Nonnull) parent;
-(NSEvent* _Nonnull) lastMouseDownEvent;
-(AvnPoint) translateLocalPoint:(AvnPoint)pt;
@ -19,6 +19,7 @@ class WindowBaseImpl;
-(void) pollModalSession: (NSModalSession _Nonnull) session;
-(void) restoreParentWindow;
-(bool) shouldTryToHandleEvents;
-(void) setEnabled: (bool) enable;
-(void) showAppMenuOnly;
-(void) showWindowMenuWithAppMenu;
-(void) applyMenu:(NSMenu* _Nullable)menu;
@ -33,6 +34,10 @@ struct INSWindowHolder
struct IWindowStateChanged
{
virtual void WindowStateChanged () = 0;
virtual void StartStateTransition () = 0;
virtual void EndStateTransition () = 0;
virtual SystemDecorations Decorations () = 0;
virtual AvnWindowState WindowState () = 0;
};
#endif /* window_h */

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

@ -382,6 +382,50 @@ public:
*ppv = [renderTarget createSurfaceRenderTarget];
return *ppv == nil ? E_FAIL : S_OK;
}
virtual HRESULT BeginDragAndDropOperation(AvnDragDropEffects effects, AvnPoint point,
IAvnClipboard* clipboard, IAvnDndResultCallback* cb,
void* sourceHandle) override
{
auto item = TryGetPasteboardItem(clipboard);
[item setString:@"" forType:GetAvnCustomDataType()];
if(item == nil)
return E_INVALIDARG;
if(View == NULL)
return E_FAIL;
auto nsevent = [NSApp currentEvent];
auto nseventType = [nsevent type];
// If current event isn't a mouse one (probably due to malfunctioning user app)
// attempt to forge a new one
if(!((nseventType >= NSEventTypeLeftMouseDown && nseventType <= NSEventTypeMouseExited)
|| (nseventType >= NSEventTypeOtherMouseDown && nseventType <= NSEventTypeOtherMouseDragged)))
{
auto nspoint = [Window convertBaseToScreen: ToNSPoint(point)];
CGPoint cgpoint = NSPointToCGPoint(nspoint);
auto cgevent = CGEventCreateMouseEvent(NULL, kCGEventLeftMouseDown, cgpoint, kCGMouseButtonLeft);
nsevent = [NSEvent eventWithCGEvent: cgevent];
CFRelease(cgevent);
}
auto dragItem = [[NSDraggingItem alloc] initWithPasteboardWriter: item];
auto dragItemImage = [NSImage imageNamed:NSImageNameMultipleDocuments];
NSRect dragItemRect = {(float)point.X, (float)point.Y, [dragItemImage size].width, [dragItemImage size].height};
[dragItem setDraggingFrame: dragItemRect contents: dragItemImage];
int op = 0; int ieffects = (int)effects;
if((ieffects & (int)AvnDragDropEffects::Copy) != 0)
op |= NSDragOperationCopy;
if((ieffects & (int)AvnDragDropEffects::Link) != 0)
op |= NSDragOperationLink;
if((ieffects & (int)AvnDragDropEffects::Move) != 0)
op |= NSDragOperationMove;
[View beginDraggingSessionWithItems: @[dragItem] event: nsevent
source: CreateDraggingSource((NSDragOperation) op, cb, sourceHandle)];
return S_OK;
}
protected:
virtual NSWindowStyleMask GetStyle()
@ -391,7 +435,7 @@ protected:
void UpdateStyle()
{
[Window setStyleMask:GetStyle()];
[Window setStyleMask: GetStyle()];
}
public:
@ -404,10 +448,13 @@ public:
class WindowImpl : public virtual WindowBaseImpl, public virtual IAvnWindow, public IWindowStateChanged
{
private:
bool _canResize = true;
SystemDecorations _hasDecorations = SystemDecorationsFull;
CGRect _lastUndecoratedFrame;
bool _canResize;
bool _fullScreenActive;
SystemDecorations _decorations;
AvnWindowState _lastWindowState;
bool _inSetWindowState;
NSRect _preZoomSize;
bool _transitioningWindowState;
FORWARD_IUNKNOWN()
BEGIN_INTERFACE_MAP()
@ -421,25 +468,54 @@ private:
ComPtr<IAvnWindowEvents> WindowEvents;
WindowImpl(IAvnWindowEvents* events, IAvnGlContext* gl) : WindowBaseImpl(events, gl)
{
_fullScreenActive = false;
_canResize = true;
_decorations = SystemDecorationsFull;
_transitioningWindowState = false;
_inSetWindowState = false;
_lastWindowState = Normal;
WindowEvents = events;
[Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
[Window setTabbingMode:NSWindowTabbingModeDisallowed];
}
void HideOrShowTrafficLights ()
{
for (id subview in Window.contentView.superview.subviews) {
if ([subview isKindOfClass:NSClassFromString(@"NSTitlebarContainerView")]) {
NSView *titlebarView = [subview subviews][0];
for (id button in titlebarView.subviews) {
if ([button isKindOfClass:[NSButton class]]) {
[button setHidden: (_decorations != SystemDecorationsFull)];
}
}
}
}
}
virtual HRESULT Show () override
{
@autoreleasepool
{
if([Window parentWindow] != nil)
[[Window parentWindow] removeChildWindow:Window];
{
WindowBaseImpl::Show();
HideOrShowTrafficLights();
return SetWindowState(_lastWindowState);
}
}
virtual HRESULT ShowDialog (IAvnWindow* parent) override
virtual HRESULT SetEnabled (bool enable) override
{
@autoreleasepool
{
[Window setEnabled:enable];
return S_OK;
}
}
virtual HRESULT SetParent (IAvnWindow* parent) override
{
@autoreleasepool
{
@ -451,43 +527,70 @@ private:
return E_INVALIDARG;
[cparent->Window addChildWindow:Window ordered:NSWindowAbove];
WindowBaseImpl::Show();
UpdateStyle();
return S_OK;
}
}
void StartStateTransition () override
{
_transitioningWindowState = true;
}
void EndStateTransition () override
{
_transitioningWindowState = false;
}
SystemDecorations Decorations () override
{
return _decorations;
}
AvnWindowState WindowState () override
{
return _lastWindowState;
}
void WindowStateChanged () override
{
AvnWindowState state;
GetWindowState(&state);
WindowEvents->WindowStateChanged(state);
if(!_inSetWindowState && !_transitioningWindowState)
{
AvnWindowState state;
GetWindowState(&state);
if(_lastWindowState != state)
{
_lastWindowState = state;
WindowEvents->WindowStateChanged(state);
}
}
}
bool UndecoratedIsMaximized ()
{
return CGRectEqualToRect([Window frame], [Window screen].visibleFrame);
auto windowSize = [Window frame];
auto available = [Window screen].visibleFrame;
return CGRectEqualToRect(windowSize, available);
}
bool IsZoomed ()
{
return _hasDecorations != SystemDecorationsNone ? [Window isZoomed] : UndecoratedIsMaximized();
return _decorations == SystemDecorationsFull ? [Window isZoomed] : UndecoratedIsMaximized();
}
void DoZoom()
{
switch (_hasDecorations)
switch (_decorations)
{
case SystemDecorationsNone:
if (!UndecoratedIsMaximized())
{
_lastUndecoratedFrame = [Window frame];
}
[Window zoom:Window];
case SystemDecorationsBorderOnly:
[Window setFrame:[Window screen].visibleFrame display:true];
break;
case SystemDecorationsBorderOnly:
case SystemDecorationsFull:
[Window performZoom:Window];
break;
@ -504,25 +607,52 @@ private:
}
}
virtual HRESULT SetHasDecorations(SystemDecorations value) override
virtual HRESULT SetDecorations(SystemDecorations value) override
{
@autoreleasepool
{
_hasDecorations = value;
auto currentWindowState = _lastWindowState;
_decorations = value;
if(_fullScreenActive)
{
return S_OK;
}
auto currentFrame = [Window frame];
UpdateStyle();
HideOrShowTrafficLights();
switch (_hasDecorations)
switch (_decorations)
{
case SystemDecorationsNone:
[Window setHasShadow:NO];
[Window setTitleVisibility:NSWindowTitleHidden];
[Window setTitlebarAppearsTransparent:YES];
if(currentWindowState == Maximized)
{
if(!UndecoratedIsMaximized())
{
DoZoom();
}
}
break;
case SystemDecorationsBorderOnly:
[Window setHasShadow:YES];
[Window setTitleVisibility:NSWindowTitleHidden];
[Window setTitlebarAppearsTransparent:YES];
if(currentWindowState == Maximized)
{
if(!UndecoratedIsMaximized())
{
DoZoom();
}
}
break;
case SystemDecorationsFull:
@ -530,6 +660,13 @@ private:
[Window setTitleVisibility:NSWindowTitleVisible];
[Window setTitlebarAppearsTransparent:NO];
[Window setTitle:_lastTitle];
if(currentWindowState == Maximized)
{
auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
[View setFrameSize:newFrame];
}
break;
}
@ -586,13 +723,19 @@ private:
return E_POINTER;
}
if(([Window styleMask] & NSWindowStyleMaskFullScreen) == NSWindowStyleMaskFullScreen)
{
*ret = FullScreen;
return S_OK;
}
if([Window isMiniaturized])
{
*ret = Minimized;
return S_OK;
}
if([Window isZoomed])
if(IsZoomed())
{
*ret = Maximized;
return S_OK;
@ -604,16 +747,57 @@ private:
}
}
void EnterFullScreenMode ()
{
_fullScreenActive = true;
[Window setHasShadow:YES];
[Window setTitleVisibility:NSWindowTitleVisible];
[Window setTitlebarAppearsTransparent:NO];
[Window setTitle:_lastTitle];
[Window setStyleMask:NSWindowStyleMaskTitled | NSWindowStyleMaskResizable];
[Window toggleFullScreen:nullptr];
}
void ExitFullScreenMode ()
{
[Window toggleFullScreen:nullptr];
_fullScreenActive = false;
SetDecorations(_decorations);
}
virtual HRESULT SetWindowState (AvnWindowState state) override
{
@autoreleasepool
{
if(_lastWindowState == state)
{
return S_OK;
}
_inSetWindowState = true;
auto currentState = _lastWindowState;
_lastWindowState = state;
if(currentState == Normal)
{
_preZoomSize = [Window frame];
}
if(_shown)
{
switch (state) {
case Maximized:
if(currentState == FullScreen)
{
ExitFullScreenMode();
}
lastPositionSet.X = 0;
lastPositionSet.Y = 0;
@ -629,40 +813,66 @@ private:
break;
case Minimized:
[Window miniaturize:Window];
if(currentState == FullScreen)
{
ExitFullScreenMode();
}
else
{
[Window miniaturize:Window];
}
break;
case FullScreen:
if([Window isMiniaturized])
{
[Window deminiaturize:Window];
}
EnterFullScreenMode();
break;
default:
case Normal:
if([Window isMiniaturized])
{
[Window deminiaturize:Window];
}
if(currentState == FullScreen)
{
ExitFullScreenMode();
}
if(IsZoomed())
{
DoZoom();
if(_decorations == SystemDecorationsFull)
{
DoZoom();
}
else
{
[Window setFrame:_preZoomSize display:true];
auto newFrame = [Window contentRectForFrameRect:[Window frame]].size;
[View setFrameSize:newFrame];
}
}
break;
}
}
_inSetWindowState = false;
return S_OK;
}
}
virtual void OnResized () override
{
if(_shown)
if(_shown && !_inSetWindowState && !_transitioningWindowState)
{
auto windowState = [Window isMiniaturized] ? Minimized
: (IsZoomed() ? Maximized : Normal);
if (windowState != _lastWindowState)
{
_lastWindowState = windowState;
WindowEvents->WindowStateChanged(windowState);
}
WindowStateChanged();
}
}
@ -671,9 +881,10 @@ protected:
{
unsigned long s = NSWindowStyleMaskBorderless;
switch (_hasDecorations)
switch (_decorations)
{
case SystemDecorationsNone:
s = s | NSWindowStyleMaskFullSizeContentView;
break;
case SystemDecorationsBorderOnly:
@ -681,15 +892,19 @@ protected:
break;
case SystemDecorationsFull:
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskMiniaturizable | NSWindowStyleMaskBorderless;
s = s | NSWindowStyleMaskTitled | NSWindowStyleMaskClosable | NSWindowStyleMaskBorderless;
if(_canResize)
{
s = s | NSWindowStyleMaskResizable;
}
break;
}
if([Window parentWindow] == nullptr)
{
s |= NSWindowStyleMaskMiniaturizable;
}
return s;
}
};
@ -745,7 +960,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_area = nullptr;
_lastPixelSize.Height = 100;
_lastPixelSize.Width = 100;
[self registerForDraggedTypes: @[@"public.data", GetAvnCustomDataType()]];
return self;
}
@ -871,15 +1086,28 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (bool) ignoreUserInput
{
auto parentWindow = objc_cast<AvnWindow>([self window]);
if(parentWindow == nil || ![parentWindow shouldTryToHandleEvents])
{
auto window = dynamic_cast<WindowImpl*>(_parent.getRaw());
if(window != nullptr)
{
window->WindowEvents->GotInputWhenDisabled();
}
return TRUE;
}
return FALSE;
}
- (void)mouseEvent:(NSEvent *)event withType:(AvnRawMouseEventType) type
{
if([self ignoreUserInput])
{
return;
}
[self becomeFirstResponder];
auto localPoint = [self convertPoint:[event locationInWindow] toView:self];
@ -1024,7 +1252,10 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void) keyboardEvent: (NSEvent *) event withType: (AvnRawKeyEventType)type
{
if([self ignoreUserInput])
{
return;
}
auto key = s_KeyMap[[event keyCode]];
auto timestamp = [event timestamp] * 1000;
@ -1136,6 +1367,68 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
return result;
}
- (NSDragOperation)triggerAvnDragEvent: (AvnDragEventType) type info: (id <NSDraggingInfo>)info
{
auto localPoint = [self convertPoint:[info draggingLocation] toView:self];
auto avnPoint = [self toAvnPoint:localPoint];
auto point = [self translateLocalPoint:avnPoint];
auto modifiers = [self getModifiers:[[NSApp currentEvent] modifierFlags]];
NSDragOperation nsop = [info draggingSourceOperationMask];
auto effects = ConvertDragDropEffects(nsop);
int reffects = (int)_parent->BaseEvents
->DragEvent(type, point, modifiers, effects,
CreateClipboard([info draggingPasteboard], nil),
GetAvnDataObjectHandleFromDraggingInfo(info));
NSDragOperation ret = 0;
// Ensure that the managed part didn't add any new effects
reffects = (int)effects & (int)reffects;
// OSX requires exactly one operation
if((reffects & (int)AvnDragDropEffects::Copy) != 0)
ret = NSDragOperationCopy;
else if((reffects & (int)AvnDragDropEffects::Move) != 0)
ret = NSDragOperationMove;
else if((reffects & (int)AvnDragDropEffects::Link) != 0)
ret = NSDragOperationLink;
if(ret == 0)
ret = NSDragOperationNone;
return ret;
}
- (NSDragOperation)draggingEntered:(id <NSDraggingInfo>)sender
{
return [self triggerAvnDragEvent: AvnDragEventType::Enter info:sender];
}
- (NSDragOperation)draggingUpdated:(id <NSDraggingInfo>)sender
{
return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender];
}
- (void)draggingExited:(id <NSDraggingInfo>)sender
{
[self triggerAvnDragEvent: AvnDragEventType::Leave info:sender];
}
- (BOOL)prepareForDragOperation:(id <NSDraggingInfo>)sender
{
return [self triggerAvnDragEvent: AvnDragEventType::Over info:sender] != NSDragOperationNone;
}
- (BOOL)performDragOperation:(id <NSDraggingInfo>)sender
{
return [self triggerAvnDragEvent: AvnDragEventType::Drop info:sender] != NSDragOperationNone;
}
- (void)concludeDragOperation:(nullable id <NSDraggingInfo>)sender
{
}
@end
@ -1144,6 +1437,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
ComPtr<WindowBaseImpl> _parent;
bool _canBecomeKeyAndMain;
bool _closed;
bool _isEnabled;
AvnMenu* _menu;
double _lastScaling;
}
@ -1164,6 +1458,20 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
}
}
- (void)performClose:(id)sender
{
if([[self delegate] respondsToSelector:@selector(windowShouldClose:)])
{
if(![[self delegate] windowShouldClose:self]) return;
}
else if([self respondsToSelector:@selector(windowShouldClose:)])
{
if(![self windowShouldClose:self]) return;
}
[self close];
}
- (void)pollModalSession:(nonnull NSModalSession)session
{
auto response = [NSApp runModalSession:session];
@ -1251,6 +1559,7 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
_parent = parent;
[self setDelegate:self];
_closed = false;
_isEnabled = true;
_lastScaling = [self backingScaleFactor];
[self setOpaque:NO];
@ -1317,14 +1626,12 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
-(bool)shouldTryToHandleEvents
{
for(NSWindow* uch in [self childWindows])
{
auto ch = objc_cast<AvnWindow>(uch);
if(ch == nil)
continue;
return FALSE;
}
return TRUE;
return _isEnabled;
}
-(void) setEnabled:(bool)enable
{
_isEnabled = enable;
}
-(void)makeKeyWindow
@ -1378,7 +1685,66 @@ NSArray* AllLoopModes = [NSArray arrayWithObjects: NSDefaultRunLoopMode, NSEvent
- (void)windowDidResize:(NSNotification *)notification
{
_parent->OnResized();
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->WindowStateChanged();
}
}
- (void)windowWillExitFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->StartStateTransition();
}
}
- (void)windowDidExitFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->EndStateTransition();
if(parent->Decorations() != SystemDecorationsFull && parent->WindowState() == Maximized)
{
NSRect screenRect = [[self screen] visibleFrame];
[self setFrame:screenRect display:YES];
}
if(parent->WindowState() == Minimized)
{
[self miniaturize:nullptr];
}
parent->WindowStateChanged();
}
}
- (void)windowWillEnterFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->StartStateTransition();
}
}
- (void)windowDidEnterFullScreen:(NSNotification *)notification
{
auto parent = dynamic_cast<IWindowStateChanged*>(_parent.operator->());
if(parent != nullptr)
{
parent->EndStateTransition();
parent->WindowStateChanged();
}
}
- (BOOL)windowShouldZoom:(NSWindow *)window toFrame:(NSRect)newFrame

14
nukebuild/Build.cs

@ -12,6 +12,7 @@ using Nuke.Common.ProjectModel;
using Nuke.Common.Tooling;
using Nuke.Common.Tools.DotNet;
using Nuke.Common.Tools.MSBuild;
using Nuke.Common.Tools.Npm;
using Nuke.Common.Utilities;
using Nuke.Common.Utilities.Collections;
using static Nuke.Common.EnvironmentInfo;
@ -121,8 +122,21 @@ partial class Build : NukeBuild
EnsureCleanDirectory(Parameters.TestResultsRoot);
});
Target CompileHtmlPreviewer => _ => _
.DependsOn(Clean)
.Executes(() =>
{
var webappDir = RootDirectory / "src" / "Avalonia.DesignerSupport" / "Remote" / "HtmlTransport" / "webapp";
NpmTasks.NpmInstall(c => c.SetWorkingDirectory(webappDir));
NpmTasks.NpmRun(c => c
.SetWorkingDirectory(webappDir)
.SetCommand("dist"));
});
Target Compile => _ => _
.DependsOn(Clean)
.DependsOn(CompileHtmlPreviewer)
.Executes(() =>
{
if (Parameters.IsRunningOnWindows)

5
packages/Avalonia/AvaloniaBuildTasks.targets

@ -2,6 +2,7 @@
<PropertyGroup>
<_AvaloniaUseExternalMSBuild>$(AvaloniaUseExternalMSBuild)</_AvaloniaUseExternalMSBuild>
<_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false</_AvaloniaUseExternalMSBuild>
<AvaloniaXamlReportImportance Condition="'$(AvaloniaXamlReportImportance)' == ''">low</AvaloniaXamlReportImportance>
</PropertyGroup>
<UsingTask TaskName="GenerateAvaloniaResourcesTask"
@ -38,7 +39,8 @@
Output="$(AvaloniaResourcesTemporaryFilePath)"
Root="$(MSBuildProjectDirectory)"
Resources="@(AvaloniaResource)"
EmbeddedResources="@(EmbeddedResources)"/>
EmbeddedResources="@(EmbeddedResources)"
ReportImportance="$(AvaloniaXamlReportImportance)"/>
<Exec
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"
Command="dotnet msbuild /nodereuse:false $(MSBuildProjectFile) /t:GenerateAvaloniaResources /p:_AvaloniaForceInternalMSBuild=true /p:Configuration=$(Configuration) /p:TargetFramework=$(TargetFramework) /p:BuildProjectReferences=false"/>
@ -67,6 +69,7 @@
OriginalCopyPath="$(AvaloniaXamlOriginalCopyFilePath)"
ProjectDirectory="$(MSBuildProjectDirectory)"
VerifyIl="$(AvaloniaXamlIlVerifyIl)"
ReportImportance="$(AvaloniaXamlReportImportance)"
/>
<Exec
Condition="'$(_AvaloniaUseExternalMSBuild)' == 'true'"

4
samples/BindingDemo/MainWindow.xaml

@ -74,11 +74,11 @@
</StackPanel.DataTemplates>
<StackPanel Margin="18" Spacing="4" Width="200">
<TextBlock FontSize="16" Text="Multiple"/>
<ListBox Items="{Binding Items}" SelectionMode="Multiple" SelectedItems="{Binding SelectedItems}"/>
<ListBox Items="{Binding Items}" SelectionMode="Multiple" Selection="{Binding Selection}"/>
</StackPanel>
<StackPanel Margin="18" Spacing="4" Width="200">
<TextBlock FontSize="16" Text="Multiple"/>
<ListBox Items="{Binding Items}" SelectionMode="Multiple" SelectedItems="{Binding SelectedItems}"/>
<ListBox Items="{Binding Items}" SelectionMode="Multiple" Selection="{Binding Selection}"/>
</StackPanel>
<ContentControl Content="{Binding SelectedItems[0]}">
<ContentControl.DataTemplates>

5
samples/BindingDemo/ViewModels/MainWindowViewModel.cs

@ -6,6 +6,7 @@ using System.Reactive.Linq;
using System.Threading.Tasks;
using System.Threading;
using ReactiveUI;
using Avalonia.Controls;
namespace BindingDemo.ViewModels
{
@ -27,7 +28,7 @@ namespace BindingDemo.ViewModels
Detail = "Item " + x + " details",
}));
SelectedItems = new ObservableCollection<TestItem>();
Selection = new SelectionModel();
ShuffleItems = ReactiveCommand.Create(() =>
{
@ -56,7 +57,7 @@ namespace BindingDemo.ViewModels
}
public ObservableCollection<TestItem> Items { get; }
public ObservableCollection<TestItem> SelectedItems { get; }
public SelectionModel Selection { get; }
public ReactiveCommand<Unit, Unit> ShuffleItems { get; }
public string BooleanString

2
samples/ControlCatalog.NetCore/Program.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
@ -6,6 +7,7 @@ using System.Threading;
using Avalonia;
using Avalonia.ReactiveUI;
using Avalonia.Dialogs;
using Avalonia.OpenGL;
namespace ControlCatalog.NetCore
{

4
samples/ControlCatalog/ControlCatalog.csproj

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<TargetFramework>netstandard2.0</TargetFramework>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<Compile Update="**\*.xaml.cs">
@ -17,6 +18,7 @@
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-BoldItalic.ttf" />
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-Italic.ttf" />
<EmbeddedResource Include="Assets\Fonts\SourceSansPro-Regular.ttf" />
<EmbeddedResource Include="Pages\teapot.bin" />
</ItemGroup>
<ItemGroup>

6
samples/ControlCatalog/MainView.xaml

@ -47,6 +47,7 @@
<TabItem Header="Menu"><pages:MenuPage/></TabItem>
<TabItem Header="Notifications"><pages:NotificationsPage/></TabItem>
<TabItem Header="NumericUpDown"><pages:NumericUpDownPage/></TabItem>
<TabItem Header="OpenGL"><pages:OpenGlPage/></TabItem>
<TabItem Header="Pointers (Touch)"><pages:PointersPage/></TabItem>
<TabItem Header="ProgressBar"><pages:ProgressBarPage/></TabItem>
<TabItem Header="RadioButton"><pages:RadioButtonPage/></TabItem>
@ -59,8 +60,8 @@
<TabItem Header="TreeView"><pages:TreeViewPage/></TabItem>
<TabItem Header="Viewbox"><pages:ViewboxPage/></TabItem>
<TabControl.Tag>
<StackPanel Width="115" Margin="8" HorizontalAlignment="Right" VerticalAlignment="Bottom">
<ComboBox x:Name="Decorations" SelectedIndex="0" Margin="0,0,0,8">
<StackPanel Width="115" Spacing="4" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="8">
<ComboBox x:Name="Decorations" SelectedIndex="0">
<ComboBoxItem>No Decorations</ComboBoxItem>
<ComboBoxItem>Border Only</ComboBoxItem>
<ComboBoxItem>Full Decorations</ComboBoxItem>
@ -69,6 +70,7 @@
<ComboBoxItem>Light</ComboBoxItem>
<ComboBoxItem>Dark</ComboBoxItem>
</ComboBox>
<ComboBox Items="{Binding WindowStates}" SelectedItem="{Binding WindowState}" />
</StackPanel>
</TabControl.Tag>
</TabControl>

2
samples/ControlCatalog/MainWindow.xaml

@ -7,7 +7,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="clr-namespace:ControlCatalog.ViewModels"
xmlns:v="clr-namespace:ControlCatalog.Views"
x:Class="ControlCatalog.MainWindow">
x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}">
<NativeMenu.Menu>
<NativeMenu>

1
samples/ControlCatalog/Pages/DialogsPage.xaml

@ -6,6 +6,7 @@
<Button Name="OpenFile">Open File</Button>
<Button Name="SaveFile">Save File</Button>
<Button Name="SelectFolder">Select Folder</Button>
<Button Name="OpenBoth">Select Both</Button>
<Button Name="DecoratedWindow">Decorated window</Button>
<Button Name="DecoratedWindowDialog">Decorated window (dialog)</Button>
<Button Name="Dialog">Dialog</Button>

14
samples/ControlCatalog/Pages/DialogsPage.xaml.cs

@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.Dialogs;
using Avalonia.Markup.Xaml;
#pragma warning disable 4014
@ -58,6 +59,19 @@ namespace ControlCatalog.Pages
Title = "Select folder",
}.ShowAsync(GetWindow());
};
this.FindControl<Button>("OpenBoth").Click += async delegate
{
var res = await new OpenFileDialog()
{
Title = "Select both",
AllowMultiple = true
}.ShowManagedAsync(GetWindow(), new ManagedFileDialogOptions
{
AllowDirectorySelection = true
});
if (res != null)
Console.WriteLine("Selected: \n" + string.Join("\n", res));
};
this.FindControl<Button>("DecoratedWindow").Click += delegate
{
new DecoratedWindow().Show();

12
samples/ControlCatalog/Pages/DragAndDropPage.xaml

@ -9,9 +9,15 @@
Margin="0,16,0,0"
HorizontalAlignment="Center"
Spacing="16">
<Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMe">
<TextBlock Name="DragState">Drag Me</TextBlock>
</Border>
<StackPanel>
<Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMeText">
<TextBlock Name="DragStateText">Drag Me</TextBlock>
</Border>
<Border BorderBrush="{DynamicResource ThemeAccentBrush}" BorderThickness="2" Padding="16" Name="DragMeCustom">
<TextBlock Name="DragStateCustom">Drag Me (custom)</TextBlock>
</Border>
</StackPanel>
<Border Background="{DynamicResource ThemeAccentBrush2}" Padding="16"
DragDrop.AllowDrop="True">
<TextBlock Name="DropState">Drop some text or files here</TextBlock>

92
samples/ControlCatalog/Pages/DragAndDropPage.xaml.cs

@ -3,69 +3,85 @@ using Avalonia.Input;
using Avalonia.Markup.Xaml;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
namespace ControlCatalog.Pages
{
public class DragAndDropPage : UserControl
{
private TextBlock _DropState;
private TextBlock _DragState;
private Border _DragMe;
private int DragCount = 0;
TextBlock _DropState;
private const string CustomFormat = "application/xxx-avalonia-controlcatalog-custom";
public DragAndDropPage()
{
this.InitializeComponent();
_DropState = this.Find<TextBlock>("DropState");
_DragMe.PointerPressed += DoDrag;
int textCount = 0;
SetupDnd("Text", d => d.Set(DataFormats.Text,
$"Text was dragged {++textCount} times"));
AddHandler(DragDrop.DropEvent, Drop);
AddHandler(DragDrop.DragOverEvent, DragOver);
SetupDnd("Custom", d => d.Set(CustomFormat, "Test123"));
}
private async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
void SetupDnd(string suffix, Action<DataObject> factory, DragDropEffects effects = DragDropEffects.Copy)
{
DataObject dragData = new DataObject();
dragData.Set(DataFormats.Text, $"You have dragged text {++DragCount} times");
var dragMe = this.Find<Border>("DragMe" + suffix);
var dragState = this.Find<TextBlock>("DragState"+suffix);
var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
switch(result)
async void DoDrag(object sender, Avalonia.Input.PointerPressedEventArgs e)
{
case DragDropEffects.Copy:
_DragState.Text = "The text was copied"; break;
case DragDropEffects.Link:
_DragState.Text = "The text was linked"; break;
case DragDropEffects.None:
_DragState.Text = "The drag operation was canceled"; break;
var dragData = new DataObject();
factory(dragData);
var result = await DragDrop.DoDragDrop(e, dragData, DragDropEffects.Copy);
switch (result)
{
case DragDropEffects.Copy:
dragState.Text = "Data was copied";
break;
case DragDropEffects.Link:
dragState.Text = "Data was linked";
break;
case DragDropEffects.None:
dragState.Text = "The drag operation was canceled";
break;
}
}
}
private void DragOver(object sender, DragEventArgs e)
{
// Only allow Copy or Link as Drop Operations.
e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link);
void DragOver(object sender, DragEventArgs e)
{
// Only allow Copy or Link as Drop Operations.
e.DragEffects = e.DragEffects & (DragDropEffects.Copy | DragDropEffects.Link);
// Only allow if the dragged data contains text or filenames.
if (!e.Data.Contains(DataFormats.Text) && !e.Data.Contains(DataFormats.FileNames))
e.DragEffects = DragDropEffects.None;
}
// Only allow if the dragged data contains text or filenames.
if (!e.Data.Contains(DataFormats.Text)
&& !e.Data.Contains(DataFormats.FileNames)
&& !e.Data.Contains(CustomFormat))
e.DragEffects = DragDropEffects.None;
}
private void Drop(object sender, DragEventArgs e)
{
if (e.Data.Contains(DataFormats.Text))
_DropState.Text = e.Data.GetText();
else if (e.Data.Contains(DataFormats.FileNames))
_DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames());
void Drop(object sender, DragEventArgs e)
{
if (e.Data.Contains(DataFormats.Text))
_DropState.Text = e.Data.GetText();
else if (e.Data.Contains(DataFormats.FileNames))
_DropState.Text = string.Join(Environment.NewLine, e.Data.GetFileNames());
else if (e.Data.Contains(CustomFormat))
_DropState.Text = "Custom: " + e.Data.Get(CustomFormat);
}
dragMe.PointerPressed += DoDrag;
AddHandler(DragDrop.DropEvent, Drop);
AddHandler(DragDrop.DragOverEvent, DragOver);
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
_DropState = this.Find<TextBlock>("DropState");
_DragState = this.Find<TextBlock>("DragState");
_DragMe = this.Find<Border>("DragMe");
}
}
}

2
samples/ControlCatalog/Pages/ListBoxPage.xaml

@ -10,7 +10,7 @@
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8">
<ListBox Items="{Binding Items}" SelectedItem="{Binding SelectedItem}" AutoScrollToSelectedItem="True" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350"></ListBox>
<ListBox Items="{Binding Items}" SelectedItem="{Binding SelectedItem}" AutoScrollToSelectedItem="True" SelectionMode="{Binding SelectionMode}" Width="250" Height="350"></ListBox>
<Button Command="{Binding AddItemCommand}">Add</Button>

29
samples/ControlCatalog/Pages/OpenGlPage.xaml

@ -0,0 +1,29 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.Pages.OpenGlPage"
xmlns:pages="clr-namespace:ControlCatalog.Pages">
<Grid>
<pages:OpenGlPageControl x:Name="GL"/>
<StackPanel>
<TextBlock Text="{Binding #GL.Info}"/>
</StackPanel>
<Grid ColumnDefinitions="*,Auto" Margin="20">
<StackPanel Grid.Column="1" MinWidth="300">
<TextBlock>Yaw</TextBlock>
<Slider Value="{Binding Yaw, Mode=TwoWay, ElementName=GL}" Maximum="10"/>
<TextBlock>Pitch</TextBlock>
<Slider Value="{Binding Pitch, Mode=TwoWay, ElementName=GL}" Maximum="10"/>
<TextBlock>Roll</TextBlock>
<Slider Value="{Binding Roll, Mode=TwoWay, ElementName=GL}" Maximum="10"/>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold" Foreground="#C000C0">D</TextBlock>
<TextBlock FontWeight="Bold" Foreground="#00C090">I</TextBlock>
<TextBlock FontWeight="Bold" Foreground="#90C000">S</TextBlock>
<TextBlock FontWeight="Bold" Foreground="#C09000">C</TextBlock>
<TextBlock FontWeight="Bold" Foreground="#00C090">O</TextBlock>
</StackPanel>
<Slider Value="{Binding Disco, Mode=TwoWay, ElementName=GL}" Maximum="1"/>
</StackPanel>
</Grid>
</Grid>
</UserControl>

401
samples/ControlCatalog/Pages/OpenGlPage.xaml.cs

@ -0,0 +1,401 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Runtime.InteropServices;
using Avalonia;
using Avalonia.Controls;
using Avalonia.OpenGL;
using Avalonia.Platform.Interop;
using Avalonia.Threading;
using static Avalonia.OpenGL.GlConsts;
// ReSharper disable StringLiteralTypo
namespace ControlCatalog.Pages
{
public class OpenGlPage : UserControl
{
}
public class OpenGlPageControl : OpenGlControlBase
{
private float _yaw;
public static readonly DirectProperty<OpenGlPageControl, float> YawProperty =
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Yaw", o => o.Yaw, (o, v) => o.Yaw = v);
public float Yaw
{
get => _yaw;
set => SetAndRaise(YawProperty, ref _yaw, value);
}
private float _pitch;
public static readonly DirectProperty<OpenGlPageControl, float> PitchProperty =
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Pitch", o => o.Pitch, (o, v) => o.Pitch = v);
public float Pitch
{
get => _pitch;
set => SetAndRaise(PitchProperty, ref _pitch, value);
}
private float _roll;
public static readonly DirectProperty<OpenGlPageControl, float> RollProperty =
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Roll", o => o.Roll, (o, v) => o.Roll = v);
public float Roll
{
get => _roll;
set => SetAndRaise(RollProperty, ref _roll, value);
}
private float _disco;
public static readonly DirectProperty<OpenGlPageControl, float> DiscoProperty =
AvaloniaProperty.RegisterDirect<OpenGlPageControl, float>("Disco", o => o.Disco, (o, v) => o.Disco = v);
public float Disco
{
get => _disco;
set => SetAndRaise(DiscoProperty, ref _disco, value);
}
private string _info;
public static readonly DirectProperty<OpenGlPageControl, string> InfoProperty =
AvaloniaProperty.RegisterDirect<OpenGlPageControl, string>("Info", o => o.Info, (o, v) => o.Info = v);
public string Info
{
get => _info;
private set => SetAndRaise(InfoProperty, ref _info, value);
}
static OpenGlPageControl()
{
AffectsRender<OpenGlPageControl>(YawProperty, PitchProperty, RollProperty, DiscoProperty);
}
private int _vertexShader;
private int _fragmentShader;
private int _shaderProgram;
private int _vertexBufferObject;
private int _indexBufferObject;
private int _vertexArrayObject;
private GlExtrasInterface _glExt;
private string GetShader(bool fragment, string shader)
{
var version = (GlVersion.Type == GlProfileType.OpenGL ?
RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ? 150 : 120 :
100);
var data = "#version " + version + "\n";
if (GlVersion.Type == GlProfileType.OpenGLES)
data += "precision mediump float;\n";
if (version >= 150)
{
shader = shader.Replace("attribute", "in");
if (fragment)
shader = shader
.Replace("varying", "in")
.Replace("//DECLAREGLFRAG", "out vec4 outFragColor;")
.Replace("gl_FragColor", "outFragColor");
else
shader = shader.Replace("varying", "out");
}
data += shader;
return data;
}
private string VertexShaderSource => GetShader(false, @"
attribute vec3 aPos;
attribute vec3 aNormal;
uniform mat4 uModel;
uniform mat4 uProjection;
uniform mat4 uView;
varying vec3 FragPos;
varying vec3 VecPos;
varying vec3 Normal;
uniform float uTime;
uniform float uDisco;
void main()
{
float discoScale = sin(uTime * 10.0) / 10.0;
float distortionX = 1.0 + uDisco * cos(uTime * 20.0) / 10.0;
float scale = 1.0 + uDisco * discoScale;
vec3 scaledPos = aPos;
scaledPos.x = scaledPos.x * distortionX;
scaledPos *= scale;
gl_Position = uProjection * uView * uModel * vec4(scaledPos, 1.0);
FragPos = vec3(uModel * vec4(aPos, 1.0));
VecPos = aPos;
Normal = normalize(vec3(uModel * vec4(aNormal, 1.0)));
}
");
private string FragmentShaderSource => GetShader(true, @"
varying vec3 FragPos;
varying vec3 VecPos;
varying vec3 Normal;
uniform float uMaxY;
uniform float uMinY;
uniform float uTime;
uniform float uDisco;
//DECLAREGLFRAG
void main()
{
float y = (VecPos.y - uMinY) / (uMaxY - uMinY);
float c = cos(atan(VecPos.x, VecPos.z) * 20.0 + uTime * 40.0 + y * 50.0);
float s = sin(-atan(VecPos.z, VecPos.x) * 20.0 - uTime * 20.0 - y * 30.0);
vec3 discoColor = vec3(
0.5 + abs(0.5 - y) * cos(uTime * 10.0),
0.25 + (smoothstep(0.3, 0.8, y) * (0.5 - c / 4.0)),
0.25 + abs((smoothstep(0.1, 0.4, y) * (0.5 - s / 4.0))));
vec3 objectColor = vec3((1.0 - y), 0.40 + y / 4.0, y * 0.75 + 0.25);
objectColor = objectColor * (1.0 - uDisco) + discoColor * uDisco;
float ambientStrength = 0.3;
vec3 lightColor = vec3(1.0, 1.0, 1.0);
vec3 lightPos = vec3(uMaxY * 2.0, uMaxY * 2.0, uMaxY * 2.0);
vec3 ambient = ambientStrength * lightColor;
vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);
float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;
vec3 result = (ambient + diffuse) * objectColor;
gl_FragColor = vec4(result, 1.0);
}
");
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct Vertex
{
public Vector3 Position;
public Vector3 Normal;
}
private readonly Vertex[] _points;
private readonly ushort[] _indices;
private readonly float _minY;
private readonly float _maxY;
public OpenGlPageControl()
{
var name = typeof(OpenGlPage).Assembly.GetManifestResourceNames().First(x => x.Contains("teapot.bin"));
using (var sr = new BinaryReader(typeof(OpenGlPage).Assembly.GetManifestResourceStream(name)))
{
var buf = new byte[sr.ReadInt32()];
sr.Read(buf, 0, buf.Length);
var points = new float[buf.Length / 4];
Buffer.BlockCopy(buf, 0, points, 0, buf.Length);
buf = new byte[sr.ReadInt32()];
sr.Read(buf, 0, buf.Length);
_indices = new ushort[buf.Length / 2];
Buffer.BlockCopy(buf, 0, _indices, 0, buf.Length);
_points = new Vertex[points.Length / 3];
for (var primitive = 0; primitive < points.Length / 3; primitive++)
{
var srci = primitive * 3;
_points[primitive] = new Vertex
{
Position = new Vector3(points[srci], points[srci + 1], points[srci + 2])
};
}
for (int i = 0; i < _indices.Length; i += 3)
{
Vector3 a = _points[_indices[i]].Position;
Vector3 b = _points[_indices[i + 1]].Position;
Vector3 c = _points[_indices[i + 2]].Position;
var normal = Vector3.Normalize(Vector3.Cross(c - b, a - b));
_points[_indices[i]].Normal += normal;
_points[_indices[i + 1]].Normal += normal;
_points[_indices[i + 2]].Normal += normal;
}
for (int i = 0; i < _points.Length; i++)
{
_points[i].Normal = Vector3.Normalize(_points[i].Normal);
_maxY = Math.Max(_maxY, _points[i].Position.Y);
_minY = Math.Min(_minY, _points[i].Position.Y);
}
}
}
private void CheckError(GlInterface gl)
{
int err;
while ((err = gl.GetError()) != GL_NO_ERROR)
Console.WriteLine(err);
}
protected unsafe override void OnOpenGlInit(GlInterface GL, int fb)
{
CheckError(GL);
_glExt = new GlExtrasInterface(GL);
Info = $"Renderer: {GL.GetString(GL_RENDERER)} Version: {GL.GetString(GL_VERSION)}";
// Load the source of the vertex shader and compile it.
_vertexShader = GL.CreateShader(GL_VERTEX_SHADER);
Console.WriteLine(GL.CompileShaderAndGetError(_vertexShader, VertexShaderSource));
// Load the source of the fragment shader and compile it.
_fragmentShader = GL.CreateShader(GL_FRAGMENT_SHADER);
Console.WriteLine(GL.CompileShaderAndGetError(_fragmentShader, FragmentShaderSource));
// Create the shader program, attach the vertex and fragment shaders and link the program.
_shaderProgram = GL.CreateProgram();
GL.AttachShader(_shaderProgram, _vertexShader);
GL.AttachShader(_shaderProgram, _fragmentShader);
const int positionLocation = 0;
const int normalLocation = 1;
GL.BindAttribLocationString(_shaderProgram, positionLocation, "aPos");
GL.BindAttribLocationString(_shaderProgram, normalLocation, "aNormal");
Console.WriteLine(GL.LinkProgramAndGetError(_shaderProgram));
CheckError(GL);
// Create the vertex buffer object (VBO) for the vertex data.
_vertexBufferObject = GL.GenBuffer();
// Bind the VBO and copy the vertex data into it.
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject);
CheckError(GL);
var vertexSize = Marshal.SizeOf<Vertex>();
fixed (void* pdata = _points)
GL.BufferData(GL_ARRAY_BUFFER, new IntPtr(_points.Length * vertexSize),
new IntPtr(pdata), GL_STATIC_DRAW);
_indexBufferObject = GL.GenBuffer();
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject);
CheckError(GL);
fixed (void* pdata = _indices)
GL.BufferData(GL_ELEMENT_ARRAY_BUFFER, new IntPtr(_indices.Length * sizeof(ushort)), new IntPtr(pdata),
GL_STATIC_DRAW);
CheckError(GL);
_vertexArrayObject = _glExt.GenVertexArray();
_glExt.BindVertexArray(_vertexArrayObject);
CheckError(GL);
GL.VertexAttribPointer(positionLocation, 3, GL_FLOAT,
0, vertexSize, IntPtr.Zero);
GL.VertexAttribPointer(normalLocation, 3, GL_FLOAT,
0, vertexSize, new IntPtr(12));
GL.EnableVertexAttribArray(positionLocation);
GL.EnableVertexAttribArray(normalLocation);
CheckError(GL);
}
protected override void OnOpenGlDeinit(GlInterface GL, int fb)
{
// Unbind everything
GL.BindBuffer(GL_ARRAY_BUFFER, 0);
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
_glExt.BindVertexArray(0);
GL.UseProgram(0);
// Delete all resources.
GL.DeleteBuffers(2, new[] { _vertexBufferObject, _indexBufferObject });
_glExt.DeleteVertexArrays(1, new[] { _vertexArrayObject });
GL.DeleteProgram(_shaderProgram);
GL.DeleteShader(_fragmentShader);
GL.DeleteShader(_vertexShader);
}
static Stopwatch St = Stopwatch.StartNew();
protected override unsafe void OnOpenGlRender(GlInterface gl, int fb)
{
gl.ClearColor(0, 0, 0, 0);
gl.Clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
gl.Enable(GL_DEPTH_TEST);
gl.Viewport(0, 0, (int)Bounds.Width, (int)Bounds.Height);
var GL = gl;
GL.BindBuffer(GL_ARRAY_BUFFER, _vertexBufferObject);
GL.BindBuffer(GL_ELEMENT_ARRAY_BUFFER, _indexBufferObject);
_glExt.BindVertexArray(_vertexArrayObject);
GL.UseProgram(_shaderProgram);
CheckError(GL);
var projection =
Matrix4x4.CreatePerspectiveFieldOfView((float)(Math.PI / 4), (float)(Bounds.Width / Bounds.Height),
0.01f, 1000);
var view = Matrix4x4.CreateLookAt(new Vector3(25, 25, 25), new Vector3(), new Vector3(0, -1, 0));
var model = Matrix4x4.CreateFromYawPitchRoll(_yaw, _pitch, _roll);
var modelLoc = GL.GetUniformLocationString(_shaderProgram, "uModel");
var viewLoc = GL.GetUniformLocationString(_shaderProgram, "uView");
var projectionLoc = GL.GetUniformLocationString(_shaderProgram, "uProjection");
var maxYLoc = GL.GetUniformLocationString(_shaderProgram, "uMaxY");
var minYLoc = GL.GetUniformLocationString(_shaderProgram, "uMinY");
var timeLoc = GL.GetUniformLocationString(_shaderProgram, "uTime");
var discoLoc = GL.GetUniformLocationString(_shaderProgram, "uDisco");
GL.UniformMatrix4fv(modelLoc, 1, false, &model);
GL.UniformMatrix4fv(viewLoc, 1, false, &view);
GL.UniformMatrix4fv(projectionLoc, 1, false, &projection);
GL.Uniform1f(maxYLoc, _maxY);
GL.Uniform1f(minYLoc, _minY);
GL.Uniform1f(timeLoc, (float)St.Elapsed.TotalSeconds);
GL.Uniform1f(discoLoc, _disco);
CheckError(GL);
GL.DrawElements(GL_TRIANGLES, _indices.Length, GL_UNSIGNED_SHORT, IntPtr.Zero);
CheckError(GL);
if (_disco > 0.01)
Dispatcher.UIThread.Post(InvalidateVisual, DispatcherPriority.Background);
}
class GlExtrasInterface : GlInterfaceBase<GlInterface.GlContextInfo>
{
public GlExtrasInterface(GlInterface gl) : base(gl.GetProcAddress, gl.ContextInfo)
{
}
public delegate void GlDeleteVertexArrays(int count, int[] buffers);
[GlMinVersionEntryPoint("glDeleteVertexArrays", 3,0)]
[GlExtensionEntryPoint("glDeleteVertexArraysOES", "GL_OES_vertex_array_object")]
public GlDeleteVertexArrays DeleteVertexArrays { get; }
public delegate void GlBindVertexArray(int array);
[GlMinVersionEntryPoint("glBindVertexArray", 3,0)]
[GlExtensionEntryPoint("glBindVertexArrayOES", "GL_OES_vertex_array_object")]
public GlBindVertexArray BindVertexArray { get; }
public delegate void GlGenVertexArrays(int n, int[] rv);
[GlMinVersionEntryPoint("glGenVertexArrays",3,0)]
[GlExtensionEntryPoint("glGenVertexArraysOES", "GL_OES_vertex_array_object")]
public GlGenVertexArrays GenVertexArrays { get; }
public int GenVertexArray()
{
var rv = new int[1];
GenVertexArrays(1, rv);
return rv[0];
}
}
}
}

3
samples/ControlCatalog/Pages/TreeViewPage.xaml

@ -10,7 +10,7 @@
HorizontalAlignment="Center"
Spacing="16">
<StackPanel Orientation="Vertical" Spacing="8">
<TreeView Items="{Binding Items}" SelectedItems="{Binding SelectedItems}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350">
<TreeView Items="{Binding Items}" Selection="{Binding Selection}" SelectionMode="{Binding SelectionMode}" Width="250" Height="350">
<TreeView.ItemTemplate>
<TreeDataTemplate ItemsSource="{Binding Children}">
<TextBlock Text="{Binding Header}"/>
@ -19,7 +19,6 @@
</TreeView>
<Button Command="{Binding AddItemCommand}">Add</Button>
<Button Command="{Binding RemoveItemCommand}">Remove</Button>
<ComboBox SelectedIndex="{Binding SelectionMode, Mode=TwoWay}">

99
samples/ControlCatalog/Pages/TreeViewPage.xaml.cs

@ -1,9 +1,6 @@
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using ReactiveUI;
using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
@ -12,104 +9,12 @@ namespace ControlCatalog.Pages
public TreeViewPage()
{
InitializeComponent();
DataContext = new PageViewModel();
DataContext = new TreeViewPageViewModel();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
private class PageViewModel : ReactiveObject
{
private SelectionMode _selectionMode;
public PageViewModel()
{
Node root = new Node();
Items = root.Children;
SelectedItems = new ObservableCollection<Node>();
AddItemCommand = ReactiveCommand.Create(() =>
{
Node parentItem = SelectedItems.Count > 0 ? SelectedItems[0] : root;
parentItem.AddNewItem();
});
RemoveItemCommand = ReactiveCommand.Create(() =>
{
while (SelectedItems.Count > 0)
{
Node lastItem = SelectedItems[0];
RecursiveRemove(Items, lastItem);
SelectedItems.Remove(lastItem);
}
bool RecursiveRemove(ObservableCollection<Node> items, Node selectedItem)
{
if (items.Remove(selectedItem))
{
return true;
}
foreach (Node item in items)
{
if (item.AreChildrenInitialized && RecursiveRemove(item.Children, selectedItem))
{
return true;
}
}
return false;
}
});
}
public ObservableCollection<Node> Items { get; }
public ObservableCollection<Node> SelectedItems { get; }
public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; }
public SelectionMode SelectionMode
{
get => _selectionMode;
set
{
SelectedItems.Clear();
this.RaiseAndSetIfChanged(ref _selectionMode, value);
}
}
}
private class Node
{
private int _counter;
private ObservableCollection<Node> _children;
public string Header { get; private set; }
public bool AreChildrenInitialized => _children != null;
public ObservableCollection<Node> Children
{
get
{
if (_children == null)
{
_children = new ObservableCollection<Node>(Enumerable.Range(1, 10).Select(i => CreateNewNode()));
}
return _children;
}
}
public void AddNewItem() => Children.Add(CreateNewNode());
public override string ToString() => Header;
private Node CreateNewNode() => new Node {Header = $"Item {_counter++}"};
}
}
}

BIN
samples/ControlCatalog/Pages/teapot.bin

Binary file not shown.

27
samples/ControlCatalog/ViewModels/MainWindowViewModel.cs

@ -1,4 +1,5 @@
using System.Reactive;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Controls.Notifications;
using Avalonia.Dialogs;
@ -11,6 +12,8 @@ namespace ControlCatalog.ViewModels
private IManagedNotificationManager _notificationManager;
private bool _isMenuItemChecked = true;
private WindowState _windowState;
private WindowState[] _windowStates;
public MainWindowViewModel(IManagedNotificationManager notificationManager)
{
@ -45,10 +48,32 @@ namespace ControlCatalog.ViewModels
(App.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime).Shutdown();
});
ToggleMenuItemCheckedCommand = ReactiveCommand.Create(() =>
ToggleMenuItemCheckedCommand = ReactiveCommand.Create(() =>
{
IsMenuItemChecked = !IsMenuItemChecked;
});
WindowState = WindowState.Normal;
WindowStates = new WindowState[]
{
WindowState.Minimized,
WindowState.Normal,
WindowState.Maximized,
WindowState.FullScreen,
};
}
public WindowState WindowState
{
get { return _windowState; }
set { this.RaiseAndSetIfChanged(ref _windowState, value); }
}
public WindowState[] WindowStates
{
get { return _windowStates; }
set { this.RaiseAndSetIfChanged(ref _windowStates, value); }
}
public IManagedNotificationManager NotificationManager

115
samples/ControlCatalog/ViewModels/TreeViewPageViewModel.cs

@ -0,0 +1,115 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reactive;
using Avalonia.Controls;
using ReactiveUI;
namespace ControlCatalog.ViewModels
{
public class TreeViewPageViewModel : ReactiveObject
{
private readonly Node _root;
private SelectionMode _selectionMode;
public TreeViewPageViewModel()
{
_root = new Node();
Items = _root.Children;
Selection = new SelectionModel();
Selection.SelectionChanged += SelectionChanged;
AddItemCommand = ReactiveCommand.Create(AddItem);
RemoveItemCommand = ReactiveCommand.Create(RemoveItem);
}
public ObservableCollection<Node> Items { get; }
public SelectionModel Selection { get; }
public ReactiveCommand<Unit, Unit> AddItemCommand { get; }
public ReactiveCommand<Unit, Unit> RemoveItemCommand { get; }
public SelectionMode SelectionMode
{
get => _selectionMode;
set
{
Selection.ClearSelection();
this.RaiseAndSetIfChanged(ref _selectionMode, value);
}
}
private void AddItem()
{
var parentItem = Selection.SelectedItems.Count > 0 ? (Node)Selection.SelectedItems[0] : _root;
parentItem.AddItem();
}
private void RemoveItem()
{
while (Selection.SelectedItems.Count > 0)
{
Node lastItem = (Node)Selection.SelectedItems[0];
RecursiveRemove(Items, lastItem);
Selection.DeselectAt(Selection.SelectedIndices[0]);
}
bool RecursiveRemove(ObservableCollection<Node> items, Node selectedItem)
{
if (items.Remove(selectedItem))
{
return true;
}
foreach (Node item in items)
{
if (item.AreChildrenInitialized && RecursiveRemove(item.Children, selectedItem))
{
return true;
}
}
return false;
}
}
private void SelectionChanged(object sender, SelectionModelSelectionChangedEventArgs e)
{
var selected = string.Join(",", e.SelectedIndices);
var deselected = string.Join(",", e.DeselectedIndices);
System.Diagnostics.Debug.WriteLine($"Selected '{selected}', Deselected '{deselected}'");
}
public class Node
{
private ObservableCollection<Node> _children;
private int _childIndex = 10;
public Node()
{
Header = "Item";
}
public Node(Node parent, int index)
{
Parent = parent;
Header = parent.Header + ' ' + index;
}
public Node Parent { get; }
public string Header { get; }
public bool AreChildrenInitialized => _children != null;
public ObservableCollection<Node> Children => _children ??= CreateChildren();
public void AddItem() => Children.Add(new Node(this, _childIndex++));
public void RemoveItem(Node child) => Children.Remove(child);
public override string ToString() => Header;
private ObservableCollection<Node> CreateChildren()
{
return new ObservableCollection<Node>(
Enumerable.Range(0, 10).Select(i => new Node(this, i)));
}
}
}
}

53
samples/RenderDemo/Controls/LineBoundsDemoControl.cs

@ -0,0 +1,53 @@
using System;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Media;
using Avalonia.Rendering.SceneGraph;
using Avalonia.Threading;
namespace RenderDemo.Controls
{
public class LineBoundsDemoControl : Control
{
static LineBoundsDemoControl()
{
AffectsRender<LineBoundsDemoControl>(AngleProperty);
}
public LineBoundsDemoControl()
{
var timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1 / 60.0);
timer.Tick += (sender, e) => Angle += Math.PI / 360;
timer.Start();
}
public static readonly StyledProperty<double> AngleProperty =
AvaloniaProperty.Register<LineBoundsDemoControl, double>(nameof(Angle));
public double Angle
{
get => GetValue(AngleProperty);
set => SetValue(AngleProperty, value);
}
public override void Render(DrawingContext drawingContext)
{
var lineLength = Math.Sqrt((100 * 100) + (100 * 100));
var diffX = LineBoundsHelper.CalculateAdjSide(Angle, lineLength);
var diffY = LineBoundsHelper.CalculateOppSide(Angle, lineLength);
var p1 = new Point(200, 200);
var p2 = new Point(p1.X + diffX, p1.Y + diffY);
var pen = new Pen(Brushes.Green, 20, lineCap: PenLineCap.Square);
var boundPen = new Pen(Brushes.Black);
drawingContext.DrawLine(pen, p1, p2);
drawingContext.DrawRectangle(boundPen, LineBoundsHelper.CalculateBounds(p1, p2, pen));
}
}
}

3
samples/RenderDemo/MainWindow.xaml

@ -44,6 +44,9 @@
<TabItem Header="GlyphRun">
<pages:GlyphRunPage/>
</TabItem>
<TabItem Header="LineBounds">
<pages:LineBoundsPage />
</TabItem>
</TabControl>
</DockPanel>
</Window>

28
samples/RenderDemo/Pages/AnimationsPage.xaml

@ -134,6 +134,32 @@
</Animation>
</Style.Animations>
</Style>
<Style Selector="Border.Shadow">
<Setter Property="BorderBrush" Value="Black"/>
<Setter Property="BorderThickness" Value="1"/>
<Style.Animations>
<Animation Duration="0:0:3"
IterationCount="Infinite"
PlaybackDirection="Alternate">
<KeyFrame Cue="0%">
<Setter Property="BoxShadow" Value="inset 0 0 0 2 Red, -15 -15 Green"/>
</KeyFrame>
<KeyFrame Cue="35%">
<Setter Property="BoxShadow" Value="inset 0 0 20 2 Blue, -15 20 0 0 Blue"/>
</KeyFrame>
<KeyFrame Cue="70%">
<Setter Property="BoxShadow" Value="inset 0 0 20 30 Green, 20 20 20 0 Red"/>
</KeyFrame>
<KeyFrame Cue="85%">
<Setter Property="BoxShadow" Value="inset 30 0 20 30 Green, 20 20 20 10 Red"/>
</KeyFrame>
<KeyFrame Cue="100%">
<Setter Property="BoxShadow" Value="inset 30 30 20 30 Green, 20 40 20 10 Red"/>
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Styles>
</UserControl.Styles>
<Grid>
@ -152,6 +178,8 @@
<Border Classes="Test Rect4" Background="Navy"/>
<Border Classes="Test Rect5" Background="SeaGreen"/>
<Border Classes="Test Rect6" Background="Red"/>
<Border Classes="Test Shadow" CornerRadius="10" Child="{x:Null}" />
<Border Classes="Test Shadow" CornerRadius="0 30 60 0" Child="{x:Null}" />
</WrapPanel>
</StackPanel>
</Grid>

9
samples/RenderDemo/Pages/LineBoundsPage.xaml

@ -0,0 +1,9 @@
<UserControl xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
xmlns:controls="clr-namespace:RenderDemo.Controls"
x:Class="RenderDemo.Pages.LineBoundsPage">
<controls:LineBoundsDemoControl />
</UserControl>

19
samples/RenderDemo/Pages/LineBoundsPage.xaml.cs

@ -0,0 +1,19 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace RenderDemo.Pages
{
public class LineBoundsPage : UserControl
{
public LineBoundsPage()
{
this.InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}
}

3
samples/RenderDemo/RenderDemo.csproj

@ -3,6 +3,9 @@
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\src\Avalonia.Visuals\Rendering\SceneGraph\LineBoundsHelper.cs" Link="LineBoundsHelper.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Diagnostics\Avalonia.Diagnostics.csproj" />
<ProjectReference Include="..\..\src\Linux\Avalonia.LinuxFramebuffer\Avalonia.LinuxFramebuffer.csproj" />

2
samples/VirtualizationDemo/MainWindow.xaml

@ -45,7 +45,7 @@
<ListBox Name="listBox"
Items="{Binding Items}"
SelectedItems="{Binding SelectedItems}"
Selection="{Binding Selection}"
SelectionMode="Multiple"
VirtualizationMode="{Binding VirtualizationMode}"
ScrollViewer.HorizontalScrollBarVisibility="{Binding HorizontalScrollBarVisibility, Mode=TwoWay}"

14
samples/VirtualizationDemo/ViewModels/MainWindowViewModel.cs

@ -48,8 +48,7 @@ namespace VirtualizationDemo.ViewModels
set { this.RaiseAndSetIfChanged(ref _itemCount, value); }
}
public AvaloniaList<ItemViewModel> SelectedItems { get; }
= new AvaloniaList<ItemViewModel>();
public SelectionModel Selection { get; } = new SelectionModel();
public AvaloniaList<ItemViewModel> Items
{
@ -138,9 +137,9 @@ namespace VirtualizationDemo.ViewModels
{
var index = Items.Count;
if (SelectedItems.Count > 0)
if (Selection.SelectedIndices.Count > 0)
{
index = Items.IndexOf(SelectedItems[0]);
index = Selection.SelectedIndex.GetAt(0);
}
Items.Insert(index, new ItemViewModel(_newItemIndex++, NewItemString));
@ -148,9 +147,9 @@ namespace VirtualizationDemo.ViewModels
private void Remove()
{
if (SelectedItems.Count > 0)
if (Selection.SelectedItems.Count > 0)
{
Items.RemoveAll(SelectedItems);
Items.RemoveAll(Selection.SelectedItems.Cast<ItemViewModel>().ToList());
}
}
@ -164,8 +163,7 @@ namespace VirtualizationDemo.ViewModels
private void SelectItem(int index)
{
SelectedItems.Clear();
SelectedItems.Add(Items[index]);
Selection.SelectedIndex = new IndexPath(index);
}
}
}

10
scripts/ReplaceNugetCache.ps1

@ -1,5 +1,5 @@
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp2.0\Avalonia.Skia.dll ~\.nuget\packages\avalonia.direct2d1\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Debug\netcoreapp3.1\Avalonia.Skia.dll ~\.nuget\packages\avalonia.direct2d1\$args\lib\netstandard2.0\

10
scripts/ReplaceNugetCacheRelease.ps1

@ -1,5 +1,5 @@
copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\
copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.gtk3\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore.\bin\Release\netcoreapp2.0\Avalonia**.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp3.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netcoreapp2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp3.1\Avalonia**.dll ~\.nuget\packages\avalonia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp3.1\Avalonia.Win32.dll ~\.nuget\packages\avalonia.win32\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp3.1\Avalonia.Skia.dll ~\.nuget\packages\avalonia.skia\$args\lib\netstandard2.0\
copy ..\samples\ControlCatalog.NetCore\bin\Release\netcoreapp3.1\Avalonia.Skia.dll ~\.nuget\packages\avalonia.direct2d1\$args\lib\netstandard2.0\

2
src/Avalonia.Animation/Animation.cs

@ -254,7 +254,7 @@ namespace Avalonia.Animation
cue = new Cue(keyframe.KeyTime.TotalSeconds / Duration.TotalSeconds);
}
var newKF = new AnimatorKeyFrame(handler, cue);
var newKF = new AnimatorKeyFrame(handler, cue, keyframe.KeySpline);
subscriptions.Add(newKF.BindSetter(setter, control));

9
src/Avalonia.Animation/AnimatorKeyFrame.cs

@ -24,11 +24,20 @@ namespace Avalonia.Animation
{
AnimatorType = animatorType;
Cue = cue;
KeySpline = null;
}
public AnimatorKeyFrame(Type animatorType, Cue cue, KeySpline keySpline)
{
AnimatorType = animatorType;
Cue = cue;
KeySpline = keySpline;
}
internal bool isNeutral;
public Type AnimatorType { get; }
public Cue Cue { get; }
public KeySpline KeySpline { get; }
public AvaloniaProperty Property { get; private set; }
private object _value;

3
src/Avalonia.Animation/Animators/Animator`1.cs

@ -89,6 +89,9 @@ namespace Avalonia.Animation.Animators
else
newValue = (T)lastKeyframe.Value;
if (lastKeyframe.KeySpline != null)
progress = lastKeyframe.KeySpline.GetSplineProgress(progress);
return Interpolate(progress, oldValue, newValue);
}

20
src/Avalonia.Animation/KeyFrame.cs

@ -19,6 +19,7 @@ namespace Avalonia.Animation
{
private TimeSpan _ktimeSpan;
private Cue _kCue;
private KeySpline _kKeySpline;
public KeyFrame()
{
@ -74,6 +75,25 @@ namespace Avalonia.Animation
}
}
/// <summary>
/// Gets or sets the KeySpline of this <see cref="KeyFrame"/>.
/// </summary>
/// <value>The key spline.</value>
public KeySpline KeySpline
{
get
{
return _kKeySpline;
}
set
{
_kKeySpline = value;
if (value != null && !value.IsValid())
{
throw new ArgumentException($"{nameof(KeySpline)} must have X coordinates >= 0.0 and <= 1.0.");
}
}
}
}

349
src/Avalonia.Animation/KeySpline.cs

@ -0,0 +1,349 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Text;
using Avalonia;
using Avalonia.Utilities;
// Ported from WPF open-source code.
// https://github.com/dotnet/wpf/blob/ae1790531c3b993b56eba8b1f0dd395a3ed7de75/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/Media/Animation/KeySpline.cs
namespace Avalonia.Animation
{
/// <summary>
/// Determines how an animation is used based on a cubic bezier curve.
/// X1 and X2 must be between 0.0 and 1.0, inclusive.
/// See https://docs.microsoft.com/en-us/dotnet/api/system.windows.media.animation.keyspline
/// </summary>
[TypeConverter(typeof(KeySplineTypeConverter))]
public class KeySpline : AvaloniaObject
{
// Control points
private double _controlPointX1;
private double _controlPointY1;
private double _controlPointX2;
private double _controlPointY2;
private bool _isSpecified;
private bool _isDirty;
// The parameter that corresponds to the most recent time
private double _parameter;
// Cached coefficients
private double _Bx; // 3*points[0].X
private double _Cx; // 3*points[1].X
private double _Cx_Bx; // 2*(Cx - Bx)
private double _three_Cx; // 3 - Cx
private double _By; // 3*points[0].Y
private double _Cy; // 3*points[1].Y
// constants
private const double _accuracy = .001; // 1/3 the desired accuracy in X
private const double _fuzz = .000001; // computational zero
/// <summary>
/// Create a <see cref="KeySpline"/> with X1 = Y1 = 0 and X2 = Y2 = 1.
/// </summary>
public KeySpline()
{
_controlPointX1 = 0.0;
_controlPointY1 = 0.0;
_controlPointX2 = 1.0;
_controlPointY2 = 1.0;
_isDirty = true;
}
/// <summary>
/// Create a <see cref="KeySpline"/> with the given parameters
/// </summary>
/// <param name="x1">X coordinate for the first control point</param>
/// <param name="y1">Y coordinate for the first control point</param>
/// <param name="x2">X coordinate for the second control point</param>
/// <param name="y2">Y coordinate for the second control point</param>
public KeySpline(double x1, double y1, double x2, double y2)
{
_controlPointX1 = x1;
_controlPointY1 = y1;
_controlPointX2 = x2;
_controlPointY2 = y2;
_isDirty = true;
}
/// <summary>
/// Parse a <see cref="KeySpline"/> from a string. The string
/// needs to contain 4 values in it for the 2 control points.
/// </summary>
/// <param name="value">string with 4 values in it</param>
/// <param name="culture">culture of the string</param>
/// <exception cref="FormatException">Thrown if the string does not have 4 values</exception>
/// <returns>A <see cref="KeySpline"/> with the appropriate values set</returns>
public static KeySpline Parse(string value, CultureInfo culture)
{
using (var tokenizer = new StringTokenizer((string)value, CultureInfo.InvariantCulture, exceptionMessage: "Invalid KeySpline."))
{
return new KeySpline(tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble(), tokenizer.ReadDouble());
}
}
/// <summary>
/// X coordinate of the first control point
/// </summary>
public double ControlPointX1
{
get => _controlPointX1;
set
{
if (IsValidXValue(value))
{
_controlPointX1 = value;
}
else
{
throw new ArgumentException("Invalid KeySpline X1 value. Must be >= 0.0 and <= 1.0.");
}
}
}
/// <summary>
/// Y coordinate of the first control point
/// </summary>
public double ControlPointY1
{
get => _controlPointY1;
set => _controlPointY1 = value;
}
/// <summary>
/// X coordinate of the second control point
/// </summary>
public double ControlPointX2
{
get => _controlPointX2;
set
{
if (IsValidXValue(value))
{
_controlPointX2 = value;
}
else
{
throw new ArgumentException("Invalid KeySpline X2 value. Must be >= 0.0 and <= 1.0.");
}
}
}
/// <summary>
/// Y coordinate of the second control point
/// </summary>
public double ControlPointY2
{
get => _controlPointY2;
set => _controlPointY2 = value;
}
/// <summary>
/// Calculates spline progress from a linear progress.
/// </summary>
/// <param name="linearProgress">the linear progress</param>
/// <returns>the spline progress</returns>
public double GetSplineProgress(double linearProgress)
{
if (_isDirty)
{
Build();
}
if (!_isSpecified)
{
return linearProgress;
}
else
{
SetParameterFromX(linearProgress);
return GetBezierValue(_By, _Cy, _parameter);
}
}
/// <summary>
/// Check to see whether the <see cref="KeySpline"/> is valid by looking
/// at its X values.
/// </summary>
/// <returns>true if the X values for this <see cref="KeySpline"/> fall in
/// acceptable range; false otherwise.</returns>
public bool IsValid()
{
return IsValidXValue(_controlPointX1) && IsValidXValue(_controlPointX2);
}
/// <summary>
///
/// </summary>
/// <param name="value"></param>
/// <returns></returns>
private bool IsValidXValue(double value)
{
return value >= 0.0 && value <= 1.0;
}
/// <summary>
/// Compute cached coefficients.
/// </summary>
private void Build()
{
if (_controlPointX1 == 0 && _controlPointY1 == 0 && _controlPointX2 == 1 && _controlPointY2 == 1)
{
// This KeySpline would have no effect on the progress.
_isSpecified = false;
}
else
{
_isSpecified = true;
_parameter = 0;
// X coefficients
_Bx = 3 * _controlPointX1;
_Cx = 3 * _controlPointX2;
_Cx_Bx = 2 * (_Cx - _Bx);
_three_Cx = 3 - _Cx;
// Y coefficients
_By = 3 * _controlPointY1;
_Cy = 3 * _controlPointY2;
}
_isDirty = false;
}
/// <summary>
/// Get an X or Y value with the Bezier formula.
/// </summary>
/// <param name="b">the second Bezier coefficient</param>
/// <param name="c">the third Bezier coefficient</param>
/// <param name="t">the parameter value to evaluate at</param>
/// <returns>the value of the Bezier function at the given parameter</returns>
static private double GetBezierValue(double b, double c, double t)
{
double s = 1.0 - t;
double t2 = t * t;
return b * t * s * s + c * t2 * s + t2 * t;
}
/// <summary>
/// Get X and dX/dt at a given parameter
/// </summary>
/// <param name="t">the parameter value to evaluate at</param>
/// <param name="x">the value of x there</param>
/// <param name="dx">the value of dx/dt there</param>
private void GetXAndDx(double t, out double x, out double dx)
{
double s = 1.0 - t;
double t2 = t * t;
double s2 = s * s;
x = _Bx * t * s2 + _Cx * t2 * s + t2 * t;
dx = _Bx * s2 + _Cx_Bx * s * t + _three_Cx * t2;
}
/// <summary>
/// Compute the parameter value that corresponds to a given X value, using a modified
/// clamped Newton-Raphson algorithm to solve the equation X(t) - time = 0. We make
/// use of some known properties of this particular function:
/// * We are only interested in solutions in the interval [0,1]
/// * X(t) is increasing, so we can assume that if X(t) > time t > solution. We use
/// that to clamp down the search interval with every probe.
/// * The derivative of X and Y are between 0 and 3.
/// </summary>
/// <param name="time">the time, scaled to fit in [0,1]</param>
private void SetParameterFromX(double time)
{
// Dynamic search interval to clamp with
double bottom = 0;
double top = 1;
if (time == 0)
{
_parameter = 0;
}
else if (time == 1)
{
_parameter = 1;
}
else
{
// Loop while improving the guess
while (top - bottom > _fuzz)
{
double x, dx, absdx;
// Get x and dx/dt at the current parameter
GetXAndDx(_parameter, out x, out dx);
absdx = Math.Abs(dx);
// Clamp down the search interval, relying on the monotonicity of X(t)
if (x > time)
{
top = _parameter; // because parameter > solution
}
else
{
bottom = _parameter; // because parameter < solution
}
// The desired accuracy is in ultimately in y, not in x, so the
// accuracy needs to be multiplied by dx/dy = (dx/dt) / (dy/dt).
// But dy/dt <=3, so we omit that
if (Math.Abs(x - time) < _accuracy * absdx)
{
break; // We're there
}
if (absdx > _fuzz)
{
// Nonzero derivative, use Newton-Raphson to obtain the next guess
double next = _parameter - (x - time) / dx;
// If next guess is out of the search interval then clamp it in
if (next >= top)
{
_parameter = (_parameter + top) / 2;
}
else if (next <= bottom)
{
_parameter = (_parameter + bottom) / 2;
}
else
{
// Next guess is inside the search interval, accept it
_parameter = next;
}
}
else // Zero derivative, halve the search interval
{
_parameter = (bottom + top) / 2;
}
}
}
}
}
/// <summary>
/// Converts string values to <see cref="KeySpline"/> values
/// </summary>
public class KeySplineTypeConverter : TypeConverter
{
public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
{
return sourceType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
return KeySpline.Parse((string)value, culture);
}
}
}

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

@ -0,0 +1,50 @@
using System;
using System.Threading;
namespace Avalonia.Utilities
{
public class DisposableLock
{
private readonly object _lock = new object();
/// <summary>
/// Tries to take a lock
/// </summary>
/// <returns>IDisposable if succeeded to obtain the lock</returns>
public IDisposable TryLock()
{
if (Monitor.TryEnter(_lock))
return new UnlockDisposable(_lock);
return null;
}
/// <summary>
/// Enters a waiting lock
/// </summary>
public IDisposable Lock()
{
Monitor.Enter(_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);
}
}
}
}
}

11
src/Avalonia.Build.Tasks/CompileAvaloniaXamlTask.cs

@ -12,6 +12,8 @@ namespace Avalonia.Build.Tasks
{
public bool Execute()
{
Enum.TryParse(ReportImportance, true, out MessageImportance outputImportance);
OutputPath = OutputPath ?? AssemblyFile;
var outputPdb = GetPdbPath(OutputPath);
var input = AssemblyFile;
@ -32,9 +34,12 @@ namespace Avalonia.Build.Tasks
}
}
var msg = $"CompileAvaloniaXamlTask -> AssemblyFile:{AssemblyFile}, ProjectDirectory:{ProjectDirectory}, OutputPath:{OutputPath}";
BuildEngine.LogMessage(msg, outputImportance < MessageImportance.Low ? MessageImportance.High : outputImportance);
var res = XamlCompilerTaskExecutor.Compile(BuildEngine, input,
File.ReadAllLines(ReferencesFilePath).Where(l => !string.IsNullOrWhiteSpace(l)).ToArray(),
ProjectDirectory, OutputPath, VerifyIl);
ProjectDirectory, OutputPath, VerifyIl, outputImportance);
if (!res.Success)
return false;
if (!res.WrittenFile)
@ -68,7 +73,9 @@ namespace Avalonia.Build.Tasks
public string OutputPath { get; set; }
public bool VerifyIl { get; set; }
public string ReportImportance { get; set; }
public IBuildEngine BuildEngine { get; set; }
public ITaskHost HostObject { get; set; }
}

9
src/Avalonia.Build.Tasks/Extensions.cs

@ -9,14 +9,19 @@ namespace Avalonia.Build.Tasks
public static void LogError(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message)
{
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message,
engine.LogErrorEvent(new BuildErrorEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message,
"", "Avalonia"));
}
public static void LogWarning(this IBuildEngine engine, BuildEngineErrorCode code, string file, string message)
{
engine.LogWarningEvent(new BuildWarningEventArgs("Avalonia", FormatErrorCode(code), file ?? "", 0, 0, 0, 0, message,
"", "Avalonia"));
}
public static void LogMessage(this IBuildEngine engine, string message, MessageImportance imp)
{
engine.LogMessageEvent(new BuildMessageEventArgs(message, "", "Avalonia", imp));
}
}
}

31
src/Avalonia.Build.Tasks/GenerateAvaloniaResourcesTask.cs

@ -22,6 +22,10 @@ namespace Avalonia.Build.Tasks
[Required]
public ITaskItem[] EmbeddedResources { get; set; }
public string ReportImportance { get; set; }
private MessageImportance _reportImportance;
class Source
{
public string Path { get; set; }
@ -29,15 +33,11 @@ namespace Avalonia.Build.Tasks
private byte[] _data;
private string _sourcePath;
public Source(string file, string root)
public Source(string relativePath, string root)
{
file = SPath.GetFullPath(file);
root = SPath.GetFullPath(root);
var fileUri = new Uri(file, UriKind.Absolute);
var rootUri = new Uri(root, UriKind.Absolute);
rootUri = new Uri(rootUri.ToString().TrimEnd('/') + '/');
Path = '/' + rootUri.MakeRelativeUri(fileUri).ToString().TrimStart('/');
_sourcePath = file;
Path = "/" + relativePath.Replace('\\', '/');
_sourcePath = SPath.Combine(root, relativePath);
Size = (int)new FileInfo(_sourcePath).Length;
}
@ -65,7 +65,14 @@ namespace Avalonia.Build.Tasks
}
}
List<Source> BuildResourceSources() => Resources.Select(r => new Source(r.ItemSpec, Root)).ToList();
List<Source> BuildResourceSources()
=> Resources.Select(r =>
{
var src = new Source(r.ItemSpec, Root);
BuildEngine.LogMessage($"avares -> name:{src.Path}, path: {src.SystemPath}, size:{src.Size}, ItemSpec:{r.ItemSpec}", _reportImportance);
return src;
}).ToList();
private void Pack(Stream output, List<Source> sources)
{
@ -136,10 +143,14 @@ namespace Avalonia.Build.Tasks
sources.Add(new Source("/!AvaloniaResourceXamlInfo", ms.ToArray()));
return true;
}
public bool Execute()
{
foreach(var r in EmbeddedResources.Where(r=>r.ItemSpec.EndsWith(".xaml")||r.ItemSpec.EndsWith(".paml")))
Enum.TryParse<MessageImportance>(ReportImportance, out _reportImportance);
BuildEngine.LogMessage($"GenerateAvaloniaResourcesTask -> Root: {Root}, {Resources?.Count()} resources, Output:{Output}", _reportImportance < MessageImportance.Low ? MessageImportance.High : _reportImportance);
foreach (var r in EmbeddedResources.Where(r => r.ItemSpec.EndsWith(".xaml") || r.ItemSpec.EndsWith(".paml")))
BuildEngine.LogWarning(BuildEngineErrorCode.LegacyResmScheme, r.ItemSpec,
"XAML file is packed using legacy EmbeddedResource/resm scheme, relative URIs won't work");
var resources = BuildResourceSources();

4
src/Avalonia.Build.Tasks/XamlCompilerTaskExecutor.cs

@ -40,7 +40,7 @@ namespace Avalonia.Build.Tasks
}
public static CompileResult Compile(IBuildEngine engine, string input, string[] references, string projectDirectory,
string output, bool verifyIl)
string output, bool verifyIl, MessageImportance logImportance)
{
var typeSystem = new CecilTypeSystem(references.Concat(new[] {input}), input);
var asm = typeSystem.TargetAssemblyDefinition;
@ -121,6 +121,8 @@ namespace Avalonia.Build.Tasks
{
try
{
engine.LogMessage($"XAMLIL: {res.Name} -> {res.Uri}", logImportance);
// StreamReader is needed here to handle BOM
var xaml = new StreamReader(new MemoryStream(res.FileContents)).ReadToEnd();
var parsed = XDocumentXamlIlParser.Parse(xaml);

7
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -1,5 +1,4 @@
// (c) Copyright Microsoft Corporation.
// This source is subject to the Microsoft Public License (Ms-PL).
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
@ -767,7 +766,7 @@ namespace Avalonia.Controls
/// <summary>
/// ItemsProperty property changed handler.
/// </summary>
/// <param name="e">AvaloniaPropertyChangedEventArgsdEventArgs.</param>
/// <param name="e">The event arguments.</param>
private void OnItemsPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
if (!_areHandlersSuspended)
@ -2245,7 +2244,7 @@ namespace Avalonia.Controls
/// Builds the visual tree for the column header when a new template is applied.
/// </summary>
//TODO Validation UI
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
// The template has changed, so we need to refresh the visuals
_measured = false;

2
src/Avalonia.Controls.DataGrid/DataGridBoundColumn.cs

@ -55,7 +55,7 @@ namespace Avalonia.Controls
binding.Mode = BindingMode.TwoWay;
}
if (binding.Converter == null)
if (binding.Converter == null && string.IsNullOrEmpty(binding.StringFormat))
{
binding.Converter = DataGridValueConverter.Instance;
}

6
src/Avalonia.Controls.DataGrid/DataGridCell.cs

@ -121,10 +121,8 @@ namespace Avalonia.Controls
/// <summary>
/// Builds the visual tree for the cell control when a new template is applied.
/// </summary>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
UpdatePseudoClasses();
_rightGridLine = e.NameScope.Find<Rectangle>(DATAGRIDCELL_elementRightGridLine);
if (_rightGridLine != null && OwningColumn == null)
@ -164,7 +162,7 @@ namespace Avalonia.Controls
if (OwningGrid != null)
{
OwningGrid.OnCellPointerPressed(new DataGridCellPointerPressedEventArgs(this, OwningRow, OwningColumn, e));
if (e.MouseButton == MouseButton.Left)
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)

2
src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs

@ -457,7 +457,7 @@ namespace Avalonia.Controls
private void DataGridColumnHeader_PointerPressed(object sender, PointerPressedEventArgs e)
{
if (OwningColumn == null || e.Handled || !IsEnabled || e.MouseButton != MouseButton.Left)
if (OwningColumn == null || e.Handled || !IsEnabled || e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
return;
}

6
src/Avalonia.Controls.DataGrid/DataGridRow.cs

@ -536,10 +536,8 @@ namespace Avalonia.Controls
/// <summary>
/// Builds the visual tree for the column header when a new template is applied.
/// </summary>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
RootElement = e.NameScope.Find<Panel>(DATAGRIDROW_elementRoot);
if (RootElement != null)
{
@ -786,7 +784,7 @@ namespace Avalonia.Controls
private void DataGridRow_PointerPressed(PointerPressedEventArgs e)
{
if(e.MouseButton != MouseButton.Left)
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
return;
}

6
src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs

@ -168,7 +168,7 @@ namespace Avalonia.Controls
private IDisposable _expanderButtonSubscription;
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
_rootElement = e.NameScope.Find<Panel>(DataGridRow.DATAGRIDROW_elementRoot);
@ -199,8 +199,6 @@ namespace Avalonia.Controls
_itemCountElement = e.NameScope.Find<TextBlock>(DATAGRIDROWGROUPHEADER_itemCountElement);
_propertyNameElement = e.NameScope.Find<TextBlock>(DATAGRIDROWGROUPHEADER_propertyNameElement);
UpdateTitleElements();
base.OnTemplateApplied(e);
}
internal void ApplyHeaderStatus()
@ -277,7 +275,7 @@ namespace Avalonia.Controls
//TODO TabStop
private void DataGridRowGroupHeader_PointerPressed(PointerPressedEventArgs e)
{
if (OwningGrid != null && e.MouseButton == MouseButton.Left)
if (OwningGrid != null && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (OwningGrid.IsDoubleClickRecordsClickOnCall(this) && !e.Handled)
{

6
src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs

@ -94,10 +94,8 @@ namespace Avalonia.Controls.Primitives
/// <summary>
/// Builds the visual tree for the row header when a new template is applied.
/// </summary>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
_rootElement = e.NameScope.Find<Control>(DATAGRIDROWHEADER_elementRootName);
if (_rootElement != null)
{
@ -164,7 +162,7 @@ namespace Avalonia.Controls.Primitives
//TODO TabStop
private void DataGridRowHeader_PointerPressed(object sender, PointerPressedEventArgs e)
{
if(e.MouseButton != MouseButton.Left)
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
return;
}

8
src/Avalonia.Controls/Application.cs

@ -254,8 +254,12 @@ namespace Avalonia
.Bind<IKeyboardNavigationHandler>().ToTransient<KeyboardNavigationHandler>()
.Bind<IStyler>().ToConstant(_styler)
.Bind<IScheduler>().ToConstant(AvaloniaScheduler.Instance)
.Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance)
.Bind<IPlatformDragSource>().ToTransient<InProcessDragSource>();
.Bind<IDragDropDevice>().ToConstant(DragDropDevice.Instance);
// TODO: Fix this, for now we keep this behavior since someone might be relying on it in 0.9.x
if (AvaloniaLocator.Current.GetService<IPlatformDragSource>() == null)
AvaloniaLocator.CurrentMutable
.Bind<IPlatformDragSource>().ToTransient<InProcessDragSource>();
var clock = new RenderLoopClock();
AvaloniaLocator.CurrentMutable

6
src/Avalonia.Controls/AutoCompleteBox.cs

@ -10,6 +10,7 @@ using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reactive.Linq;
using System.Threading;
using System.Threading.Tasks;
using Avalonia.Collections;
@ -1100,6 +1101,7 @@ namespace Avalonia.Controls
{
_textBoxSubscriptions =
_textBox.GetObservable(TextBox.TextProperty)
.Skip(1)
.Subscribe(_ => OnTextBoxTextChanged());
if (Text != null)
@ -1212,7 +1214,7 @@ namespace Avalonia.Controls
/// <see cref="T:Avalonia.Controls.AutoCompleteBox" /> control
/// when a new template is applied.
/// </summary>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (DropDownPopup != null)
@ -1240,7 +1242,7 @@ namespace Avalonia.Controls
OpeningDropDown(false);
}
base.OnTemplateApplied(e);
base.OnApplyTemplate(e);
}
/// <summary>

25
src/Avalonia.Controls/Border.cs

@ -33,6 +33,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
AvaloniaProperty.Register<Border, CornerRadius>(nameof(CornerRadius));
/// <summary>
/// Defines the <see cref="BoxShadow"/> property.
/// </summary>
public static readonly StyledProperty<BoxShadows> BoxShadowProperty =
AvaloniaProperty.Register<Border, BoxShadows>(nameof(BoxShadow));
private readonly BorderRenderHelper _borderRenderHelper = new BorderRenderHelper();
/// <summary>
@ -44,7 +50,8 @@ namespace Avalonia.Controls
BackgroundProperty,
BorderBrushProperty,
BorderThicknessProperty,
CornerRadiusProperty);
CornerRadiusProperty,
BoxShadowProperty);
AffectsMeasure<Border>(BorderThicknessProperty);
}
@ -83,14 +90,24 @@ namespace Avalonia.Controls
get { return GetValue(CornerRadiusProperty); }
set { SetValue(CornerRadiusProperty, value); }
}
/// <summary>
/// Gets or sets the box shadow effect parameters
/// </summary>
public BoxShadows BoxShadow
{
get => GetValue(BoxShadowProperty);
set => SetValue(BoxShadowProperty, value);
}
/// <summary>
/// Renders the control.
/// </summary>
/// <param name="context">The drawing context.</param>
public override void Render(DrawingContext context)
{
_borderRenderHelper.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush);
_borderRenderHelper.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush,
BoxShadow);
}
/// <summary>
@ -110,8 +127,6 @@ namespace Avalonia.Controls
/// <returns>The space taken.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
_borderRenderHelper.Update(finalSize, BorderThickness, CornerRadius);
return LayoutHelper.ArrangeChild(Child, finalSize, Padding, BorderThickness);
}
}

2
src/Avalonia.Controls/Button.cs

@ -278,7 +278,7 @@ namespace Avalonia.Controls
{
base.OnPointerPressed(e);
if (e.MouseButton == MouseButton.Left)
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
IsPressed = true;
e.Handled = true;

2
src/Avalonia.Controls/ButtonSpinner.cs

@ -121,7 +121,7 @@ namespace Avalonia.Controls
}
/// <inheritdoc />
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
IncreaseButton = e.NameScope.Find<Button>("PART_IncreaseButton");
DecreaseButton = e.NameScope.Find<Button>("PART_DecreaseButton");

4
src/Avalonia.Controls/Calendar/Calendar.cs

@ -2079,10 +2079,8 @@ namespace Avalonia.Controls
/// <see cref="T:System.Windows.Controls.Calendar" /> when a new
/// template is applied.
/// </summary>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
Root = e.NameScope.Find<Panel>(PART_ElementRoot);
SelectedMonth = DisplayDate;

6
src/Avalonia.Controls/Calendar/CalendarButton.cs

@ -98,9 +98,8 @@ namespace Avalonia.Controls.Primitives
/// <see cref="T:System.Windows.Controls.Primitives.CalendarButton" />
/// when a new template is applied.
/// </summary>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
SetPseudoClasses();
}
@ -149,7 +148,8 @@ namespace Avalonia.Controls.Primitives
protected override void OnPointerPressed(PointerPressedEventArgs e)
{
base.OnPointerPressed(e);
if (e.MouseButton == MouseButton.Left)
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
CalendarLeftMouseButtonDown?.Invoke(this, e);
}

6
src/Avalonia.Controls/Calendar/CalendarDayButton.cs

@ -150,11 +150,11 @@ namespace Avalonia.Controls.Primitives
}
}
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
SetPseudoClasses();
}
private void SetPseudoClasses()
{
if (_ignoringMouseOverState)
@ -206,7 +206,7 @@ namespace Avalonia.Controls.Primitives
{
base.OnPointerPressed(e);
if (e.MouseButton == MouseButton.Left)
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
CalendarDayButtonMouseDown?.Invoke(this, e);
}

4
src/Avalonia.Controls/Calendar/CalendarItem.cs

@ -268,10 +268,8 @@ namespace Avalonia.Controls.Primitives
/// <see cref="T:System.Windows.Controls.Primitives.CalendarItem" />
/// when a new template is applied.
/// </summary>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
HeaderButton = e.NameScope.Find<Button>(PART_ElementHeaderButton);
PreviousButton = e.NameScope.Find<Button>(PART_ElementPreviousButton);
NextButton = e.NameScope.Find<Button>(PART_ElementNextButton);

6
src/Avalonia.Controls/Calendar/DatePicker.cs

@ -413,7 +413,7 @@ namespace Avalonia.Controls
DisplayDate = DateTime.Today;
}
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_calendar != null)
{
@ -508,8 +508,6 @@ namespace Avalonia.Controls
SetSelectedDate();
}
}
base.OnTemplateApplied(e);
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
@ -835,7 +833,7 @@ namespace Avalonia.Controls
}
private void Calendar_PointerPressed(object sender, PointerPressedEventArgs e)
{
if (e.MouseButton == MouseButton.Left)
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
e.Handled = true;
}

28
src/Avalonia.Controls/ComboBox.cs

@ -219,7 +219,7 @@ namespace Avalonia.Controls
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (_popup != null)
{
@ -230,8 +230,23 @@ namespace Avalonia.Controls
_popup = e.NameScope.Get<Popup>("PART_Popup");
_popup.Opened += PopupOpened;
_popup.Closed += PopupClosed;
}
base.OnTemplateApplied(e);
/// <summary>
/// Called when the ComboBox popup is closed, with the <see cref="PopupClosedEventArgs"/>
/// that caused the popup to close.
/// </summary>
/// <param name="e">The event args.</param>
/// <remarks>
/// This method can be overridden to control whether the event that caused the popup to close
/// is swallowed or passed through.
/// </remarks>
protected virtual void PopupClosedOverride(PopupClosedEventArgs e)
{
if (e.CloseEvent is PointerEventArgs pointerEvent)
{
pointerEvent.Handled = true;
}
}
internal void ItemFocused(ComboBoxItem dropDownItem)
@ -247,10 +262,7 @@ namespace Avalonia.Controls
_subscriptionsOnOpen?.Dispose();
_subscriptionsOnOpen = null;
if (e.CloseEvent is PointerEventArgs pointerEvent)
{
pointerEvent.Handled = true;
}
PopupClosedOverride(e);
if (CanFocus(this))
{
@ -292,9 +304,9 @@ namespace Avalonia.Controls
{
var container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
if (container == null && SelectedItems.Count > 0)
if (container == null && SelectedIndex != -1)
{
ScrollIntoView(SelectedItems[0]);
ScrollIntoView(Selection.SelectedIndex);
container = ItemContainerGenerator.ContainerFromIndex(selectedIndex);
}

39
src/Avalonia.Controls/ContextMenu.cs

@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Avalonia.Controls.Generators;
@ -9,18 +10,19 @@ using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.LogicalTree;
using Avalonia.Styling;
namespace Avalonia.Controls
{
/// <summary>
/// A control context menu.
/// </summary>
public class ContextMenu : MenuBase
public class ContextMenu : MenuBase, ISetterValue
{
private static readonly ITemplate<IPanel> DefaultPanel =
new FuncTemplate<IPanel>(() => new StackPanel { Orientation = Orientation.Vertical });
private Popup _popup;
private Control _attachedControl;
private List<Control> _attachedControls;
private IInputElement _previousFocus;
/// <summary>
@ -74,13 +76,14 @@ namespace Avalonia.Controls
if (e.OldValue is ContextMenu oldMenu)
{
control.PointerReleased -= ControlPointerReleased;
oldMenu._attachedControl = null;
oldMenu._attachedControls?.Remove(control);
((ISetLogicalParent)oldMenu._popup)?.SetParent(null);
}
if (e.NewValue is ContextMenu newMenu)
{
newMenu._attachedControl = control;
newMenu._attachedControls ??= new List<Control>();
newMenu._attachedControls.Add(control);
control.PointerReleased += ControlPointerReleased;
}
}
@ -96,18 +99,22 @@ namespace Avalonia.Controls
/// <param name="control">The control.</param>
public void Open(Control control)
{
if (control is null && _attachedControl is null)
if (control is null && (_attachedControls is null || _attachedControls.Count == 0))
{
throw new ArgumentNullException(nameof(control));
}
if (control is object && _attachedControl is object && control != _attachedControl)
if (control is object &&
_attachedControls is object &&
!_attachedControls.Contains(control))
{
throw new ArgumentException(
"Cannot show ContentMenu on a different control to the one it is attached to.",
nameof(control));
}
control ??= _attachedControls[0];
if (IsOpen)
{
return;
@ -126,7 +133,12 @@ namespace Avalonia.Controls
_popup.Closed += PopupClosed;
}
((ISetLogicalParent)_popup).SetParent(control);
if (_popup.Parent != control)
{
((ISetLogicalParent)_popup).SetParent(null);
((ISetLogicalParent)_popup).SetParent(control);
}
_popup.Child = this;
_popup.IsOpen = true;
@ -155,6 +167,17 @@ namespace Avalonia.Controls
}
}
void ISetterValue.Initialize(ISetter setter)
{
// ContextMenu can be assigned to the ContextMenu property in a setter. This overrides
// the behavior defined in Control which requires controls to be wrapped in a <template>.
if (!(setter is Setter s && s.Property == ContextMenuProperty))
{
throw new InvalidOperationException(
"Cannot use a control as a Setter value. Wrap the control in a <Template>.");
}
}
protected override IItemContainerGenerator CreateItemContainerGenerator()
{
return new MenuItemContainerGenerator(this);
@ -179,7 +202,7 @@ namespace Avalonia.Controls
SelectedIndex = -1;
IsOpen = false;
if (_attachedControl is null)
if (_attachedControls is null || _attachedControls.Count == 0)
{
((ISetLogicalParent)_popup).SetParent(null);
}

20
src/Avalonia.Controls/Generators/TreeContainerIndex.cs

@ -94,9 +94,13 @@ namespace Avalonia.Controls.Generators
/// <returns>The container, or null of not found.</returns>
public IControl ContainerFromItem(object item)
{
IControl result;
_itemToContainer.TryGetValue(item, out result);
return result;
if (item != null)
{
_itemToContainer.TryGetValue(item, out var result);
return result;
}
return null;
}
/// <summary>
@ -106,9 +110,13 @@ namespace Avalonia.Controls.Generators
/// <returns>The item, or null of not found.</returns>
public object ItemFromContainer(IControl container)
{
object result;
_containerToItem.TryGetValue(container, out result);
return result;
if (container != null)
{
_containerToItem.TryGetValue(container, out var result);
return result;
}
return null;
}
}
}

249
src/Avalonia.Controls/ISelectionModel.cs

@ -0,0 +1,249 @@
// This source file is adapted from the WinUI project.
// (https://github.com/microsoft/microsoft-ui-xaml)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Collections.Generic;
using System.ComponentModel;
namespace Avalonia.Controls
{
/// <summary>
/// Holds the selected items for a control.
/// </summary>
public interface ISelectionModel : INotifyPropertyChanged
{
/// <summary>
/// Gets or sets the anchor index.
/// </summary>
IndexPath AnchorIndex { get; set; }
/// <summary>
/// Gets or set the index of the first selected item.
/// </summary>
IndexPath SelectedIndex { get; set; }
/// <summary>
/// Gets or set the indexes of the selected items.
/// </summary>
IReadOnlyList<IndexPath> SelectedIndices { get; }
/// <summary>
/// Gets the first selected item.
/// </summary>
object SelectedItem { get; }
/// <summary>
/// Gets the selected items.
/// </summary>
IReadOnlyList<object> SelectedItems { get; }
/// <summary>
/// Gets a value indicating whether the model represents a single or multiple selection.
/// </summary>
bool SingleSelect { get; set; }
/// <summary>
/// Gets a value indicating whether to always keep an item selected where possible.
/// </summary>
bool AutoSelect { get; set; }
/// <summary>
/// Gets or sets the collection that contains the items that can be selected.
/// </summary>
object Source { get; set; }
/// <summary>
/// Raised when the children of a selection are required.
/// </summary>
event EventHandler<SelectionModelChildrenRequestedEventArgs> ChildrenRequested;
/// <summary>
/// Raised when the selection has changed.
/// </summary>
event EventHandler<SelectionModelSelectionChangedEventArgs> SelectionChanged;
/// <summary>
/// Clears the selection.
/// </summary>
void ClearSelection();
/// <summary>
/// Deselects an item.
/// </summary>
/// <param name="index">The index of the item.</param>
void Deselect(int index);
/// <summary>
/// Deselects an item.
/// </summary>
/// <param name="groupIndex">The index of the item group.</param>
/// <param name="itemIndex">The index of the item in the group.</param>
void Deselect(int groupIndex, int itemIndex);
/// <summary>
/// Deselects an item.
/// </summary>
/// <param name="index">The index of the item.</param>
void DeselectAt(IndexPath index);
/// <summary>
/// Deselects a range of items.
/// </summary>
/// <param name="start">The start index of the range.</param>
/// <param name="end">The end index of the range.</param>
void DeselectRange(IndexPath start, IndexPath end);
/// <summary>
/// Deselects a range of items, starting at <see cref="AnchorIndex"/>.
/// </summary>
/// <param name="index">The end index of the range.</param>
void DeselectRangeFromAnchor(int index);
/// <summary>
/// Deselects a range of items, starting at <see cref="AnchorIndex"/>.
/// </summary>
/// <param name="endGroupIndex">
/// The index of the item group that represents the end of the selection.
/// </param>
/// <param name="endItemIndex">
/// The index of the item in the group that represents the end of the selection.
/// </param>
void DeselectRangeFromAnchor(int endGroupIndex, int endItemIndex);
/// <summary>
/// Deselects a range of items, starting at <see cref="AnchorIndex"/>.
/// </summary>
/// <param name="index">The end index of the range.</param>
void DeselectRangeFromAnchorTo(IndexPath index);
/// <summary>
/// Disposes the object and clears the selection.
/// </summary>
void Dispose();
/// <summary>
/// Checks whether an item is selected.
/// </summary>
/// <param name="index">The index of the item</param>
bool IsSelected(int index);
/// <summary>
/// Checks whether an item is selected.
/// </summary>
/// <param name="groupIndex">The index of the item group.</param>
/// <param name="itemIndex">The index of the item in the group.</param>
bool IsSelected(int groupIndex, int itemIndex);
/// <summary>
/// Checks whether an item is selected.
/// </summary>
/// <param name="index">The index of the item</param>
public bool IsSelectedAt(IndexPath index);
/// <summary>
/// Checks whether an item or its descendents are selected.
/// </summary>
/// <param name="index">The index of the item</param>
/// <returns>
/// True if the item and all its descendents are selected, false if the item and all its
/// descendents are deselected, or null if a combination of selected and deselected.
/// </returns>
bool? IsSelectedWithPartial(int index);
/// <summary>
/// Checks whether an item or its descendents are selected.
/// </summary>
/// <param name="groupIndex">The index of the item group.</param>
/// <param name="itemIndex">The index of the item in the group.</param>
/// <returns>
/// True if the item and all its descendents are selected, false if the item and all its
/// descendents are deselected, or null if a combination of selected and deselected.
/// </returns>
bool? IsSelectedWithPartial(int groupIndex, int itemIndex);
/// <summary>
/// Checks whether an item or its descendents are selected.
/// </summary>
/// <param name="index">The index of the item</param>
/// <returns>
/// True if the item and all its descendents are selected, false if the item and all its
/// descendents are deselected, or null if a combination of selected and deselected.
/// </returns>
bool? IsSelectedWithPartialAt(IndexPath index);
/// <summary>
/// Selects an item.
/// </summary>
/// <param name="index">The index of the item</param>
void Select(int index);
/// <summary>
/// Selects an item.
/// </summary>
/// <param name="groupIndex">The index of the item group.</param>
/// <param name="itemIndex">The index of the item in the group.</param>
void Select(int groupIndex, int itemIndex);
/// <summary>
/// Selects an item.
/// </summary>
/// <param name="index">The index of the item</param>
void SelectAt(IndexPath index);
/// <summary>
/// Selects all items.
/// </summary>
void SelectAll();
/// <summary>
/// Selects a range of items.
/// </summary>
/// <param name="start">The start index of the range.</param>
/// <param name="end">The end index of the range.</param>
void SelectRange(IndexPath start, IndexPath end);
/// <summary>
/// Selects a range of items, starting at <see cref="AnchorIndex"/>.
/// </summary>
/// <param name="index">The end index of the range.</param>
void SelectRangeFromAnchor(int index);
/// <summary>
/// Selects a range of items, starting at <see cref="AnchorIndex"/>.
/// </summary>
/// <param name="endGroupIndex">
/// The index of the item group that represents the end of the selection.
/// </param>
/// <param name="endItemIndex">
/// The index of the item in the group that represents the end of the selection.
/// </param>
void SelectRangeFromAnchor(int endGroupIndex, int endItemIndex);
/// <summary>
/// Selects a range of items, starting at <see cref="AnchorIndex"/>.
/// </summary>
/// <param name="index">The end index of the range.</param>
void SelectRangeFromAnchorTo(IndexPath index);
/// <summary>
/// Sets the <see cref="AnchorIndex"/>.
/// </summary>
/// <param name="index">The anchor index.</param>
void SetAnchorIndex(int index);
/// <summary>
/// Sets the <see cref="AnchorIndex"/>.
/// </summary>
/// <param name="groupIndex">The index of the item group.</param>
/// <param name="index">The index of the item in the group.</param>
void SetAnchorIndex(int groupIndex, int index);
/// <summary>
/// Begins a batch update of the selection.
/// </summary>
/// <returns>An <see cref="IDisposable"/> that finishes the batch update.</returns>
IDisposable Update();
}
}

3
src/Avalonia.Controls/Image.cs

@ -69,10 +69,11 @@ namespace Avalonia.Controls
{
var source = Source;
if (source != null)
if (source != null && Bounds.Width > 0 && Bounds.Height > 0)
{
Rect viewPort = new Rect(Bounds.Size);
Size sourceSize = source.Size;
Vector scale = Stretch.CalculateScaling(Bounds.Size, sourceSize, StretchDirection);
Size scaledSize = sourceSize * scale;
Rect destRect = viewPort

180
src/Avalonia.Controls/IndexPath.cs

@ -0,0 +1,180 @@
// This source file is adapted from the WinUI project.
// (https://github.com/microsoft/microsoft-ui-xaml)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Collections.Generic;
using System.Linq;
#nullable enable
namespace Avalonia.Controls
{
public readonly struct IndexPath : IComparable<IndexPath>, IEquatable<IndexPath>
{
public static readonly IndexPath Unselected = default;
private readonly int _index;
private readonly int[]? _path;
public IndexPath(int index)
{
_index = index + 1;
_path = null;
}
public IndexPath(int groupIndex, int itemIndex)
{
_index = 0;
_path = new[] { groupIndex, itemIndex };
}
public IndexPath(IEnumerable<int>? indices)
{
if (indices != null)
{
_index = 0;
_path = indices.ToArray();
}
else
{
_index = 0;
_path = null;
}
}
private IndexPath(int[] basePath, int index)
{
basePath = basePath ?? throw new ArgumentNullException(nameof(basePath));
_index = 0;
_path = new int[basePath.Length + 1];
Array.Copy(basePath, _path, basePath.Length);
_path[basePath.Length] = index;
}
public int GetSize() => _path?.Length ?? (_index == 0 ? 0 : 1);
public int GetAt(int index)
{
if (index >= GetSize())
{
throw new IndexOutOfRangeException();
}
return _path?[index] ?? (_index - 1);
}
public int CompareTo(IndexPath other)
{
var rhsPath = other;
int compareResult = 0;
int lhsCount = GetSize();
int rhsCount = rhsPath.GetSize();
if (lhsCount == 0 || rhsCount == 0)
{
// one of the paths are empty, compare based on size
compareResult = (lhsCount - rhsCount);
}
else
{
// both paths are non-empty, but can be of different size
for (int i = 0; i < Math.Min(lhsCount, rhsCount); i++)
{
if (GetAt(i) < rhsPath.GetAt(i))
{
compareResult = -1;
break;
}
else if (GetAt(i) > rhsPath.GetAt(i))
{
compareResult = 1;
break;
}
}
// if both match upto min(lhsCount, rhsCount), compare based on size
compareResult = compareResult == 0 ? (lhsCount - rhsCount) : compareResult;
}
if (compareResult != 0)
{
compareResult = compareResult > 0 ? 1 : -1;
}
return compareResult;
}
public IndexPath CloneWithChildIndex(int childIndex)
{
if (_path != null)
{
return new IndexPath(_path, childIndex);
}
else if (_index != 0)
{
return new IndexPath(_index - 1, childIndex);
}
else
{
return new IndexPath(childIndex);
}
}
public override string ToString()
{
if (_path != null)
{
return "R" + string.Join(".", _path);
}
else if (_index != 0)
{
return "R" + (_index - 1);
}
else
{
return "R";
}
}
public static IndexPath CreateFrom(int index) => new IndexPath(index);
public static IndexPath CreateFrom(int groupIndex, int itemIndex) => new IndexPath(groupIndex, itemIndex);
public static IndexPath CreateFromIndices(IList<int> indices) => new IndexPath(indices);
public override bool Equals(object obj) => obj is IndexPath other && Equals(other);
public bool Equals(IndexPath other) => CompareTo(other) == 0;
public override int GetHashCode()
{
var hashCode = -504981047;
if (_path != null)
{
foreach (var i in _path)
{
hashCode = hashCode * -1521134295 + i.GetHashCode();
}
}
else
{
hashCode = hashCode * -1521134295 + _index.GetHashCode();
}
return hashCode;
}
public static bool operator <(IndexPath x, IndexPath y) { return x.CompareTo(y) < 0; }
public static bool operator >(IndexPath x, IndexPath y) { return x.CompareTo(y) > 0; }
public static bool operator <=(IndexPath x, IndexPath y) { return x.CompareTo(y) <= 0; }
public static bool operator >=(IndexPath x, IndexPath y) { return x.CompareTo(y) >= 0; }
public static bool operator ==(IndexPath x, IndexPath y) { return x.CompareTo(y) == 0; }
public static bool operator !=(IndexPath x, IndexPath y) { return x.CompareTo(y) != 0; }
public static bool operator ==(IndexPath? x, IndexPath? y) { return (x ?? default).CompareTo(y ?? default) == 0; }
public static bool operator !=(IndexPath? x, IndexPath? y) { return (x ?? default).CompareTo(y ?? default) != 0; }
}
}

232
src/Avalonia.Controls/IndexRange.cs

@ -0,0 +1,232 @@
// This source file is adapted from the WinUI project.
// (https://github.com/microsoft/microsoft-ui-xaml)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using System.Collections.Generic;
#nullable enable
namespace Avalonia.Controls
{
internal readonly struct IndexRange : IEquatable<IndexRange>
{
private static readonly IndexRange s_invalid = new IndexRange(int.MinValue, int.MinValue);
public IndexRange(int begin, int end)
{
// Accept out of order begin/end pairs, just swap them.
if (begin > end)
{
int temp = begin;
begin = end;
end = temp;
}
Begin = begin;
End = end;
}
public int Begin { get; }
public int End { get; }
public int Count => (End - Begin) + 1;
public bool Contains(int index) => index >= Begin && index <= End;
public bool Split(int splitIndex, out IndexRange before, out IndexRange after)
{
bool afterIsValid;
before = new IndexRange(Begin, splitIndex);
if (splitIndex < End)
{
after = new IndexRange(splitIndex + 1, End);
afterIsValid = true;
}
else
{
after = new IndexRange();
afterIsValid = false;
}
return afterIsValid;
}
public bool Intersects(IndexRange other)
{
return (Begin <= other.End) && (End >= other.Begin);
}
public bool Adjacent(IndexRange other)
{
return Begin == other.End + 1 || End == other.Begin - 1;
}
public override bool Equals(object? obj)
{
return obj is IndexRange range && Equals(range);
}
public bool Equals(IndexRange other)
{
return Begin == other.Begin && End == other.End;
}
public override int GetHashCode()
{
var hashCode = 1903003160;
hashCode = hashCode * -1521134295 + Begin.GetHashCode();
hashCode = hashCode * -1521134295 + End.GetHashCode();
return hashCode;
}
public override string ToString() => $"[{Begin}..{End}]";
public static bool operator ==(IndexRange left, IndexRange right) => left.Equals(right);
public static bool operator !=(IndexRange left, IndexRange right) => !(left == right);
public static int Add(
IList<IndexRange> ranges,
IndexRange range,
IList<IndexRange>? added = null)
{
var result = 0;
for (var i = 0; i < ranges.Count && range != s_invalid; ++i)
{
var existing = ranges[i];
if (range.Intersects(existing) || range.Adjacent(existing))
{
if (range.Begin < existing.Begin)
{
var add = new IndexRange(range.Begin, existing.Begin - 1);
ranges[i] = new IndexRange(range.Begin, existing.End);
added?.Add(add);
result += add.Count;
}
range = range.End <= existing.End ?
s_invalid :
new IndexRange(existing.End + 1, range.End);
}
else if (range.End < existing.Begin)
{
ranges.Insert(i, range);
added?.Add(range);
result += range.Count;
range = s_invalid;
}
}
if (range != s_invalid)
{
ranges.Add(range);
added?.Add(range);
result += range.Count;
}
MergeRanges(ranges);
return result;
}
public static int Remove(
IList<IndexRange> ranges,
IndexRange range,
IList<IndexRange>? removed = null)
{
var result = 0;
for (var i = 0; i < ranges.Count; ++i)
{
var existing = ranges[i];
if (range.Intersects(existing))
{
if (range.Begin <= existing.Begin && range.End >= existing.End)
{
ranges.RemoveAt(i--);
removed?.Add(existing);
result += existing.Count;
}
else if (range.Begin > existing.Begin && range.End >= existing.End)
{
ranges[i] = new IndexRange(existing.Begin, range.Begin - 1);
removed?.Add(new IndexRange(range.Begin, existing.End));
result += existing.End - (range.Begin - 1);
}
else if (range.Begin > existing.Begin && range.End < existing.End)
{
ranges[i] = new IndexRange(existing.Begin, range.Begin - 1);
ranges.Insert(++i, new IndexRange(range.End + 1, existing.End));
removed?.Add(range);
result += range.Count;
}
else if (range.End <= existing.End)
{
var remove = new IndexRange(existing.Begin, range.End);
ranges[i] = new IndexRange(range.End + 1, existing.End);
removed?.Add(remove);
result += remove.Count;
}
}
}
return result;
}
public static IEnumerable<IndexRange> Subtract(
IndexRange lhs,
IEnumerable<IndexRange> rhs)
{
var result = new List<IndexRange> { lhs };
foreach (var range in rhs)
{
Remove(result, range);
}
return result;
}
public static IEnumerable<int> EnumerateIndices(IEnumerable<IndexRange> ranges)
{
foreach (var range in ranges)
{
for (var i = range.Begin; i <= range.End; ++i)
{
yield return i;
}
}
}
public static int GetCount(IEnumerable<IndexRange> ranges)
{
var result = 0;
foreach (var range in ranges)
{
result += (range.End - range.Begin) + 1;
}
return result;
}
private static void MergeRanges(IList<IndexRange> ranges)
{
for (var i = ranges.Count - 2; i >= 0; --i)
{
var r = ranges[i];
var r1 = ranges[i + 1];
if (r.Intersects(r1) || r.End == r1.Begin - 1)
{
ranges[i] = new IndexRange(r.Begin, r1.End);
ranges.RemoveAt(i + 1);
}
}
}
}
}

22
src/Avalonia.Controls/ListBox.cs

@ -31,6 +31,12 @@ namespace Avalonia.Controls
public static readonly new DirectProperty<SelectingItemsControl, IList> SelectedItemsProperty =
SelectingItemsControl.SelectedItemsProperty;
/// <summary>
/// Defines the <see cref="Selection"/> property.
/// </summary>
public static readonly new DirectProperty<SelectingItemsControl, ISelectionModel> SelectionProperty =
SelectingItemsControl.SelectionProperty;
/// <summary>
/// Defines the <see cref="SelectionMode"/> property.
/// </summary>
@ -70,6 +76,15 @@ namespace Avalonia.Controls
set => base.SelectedItems = value;
}
/// <summary>
/// Gets or sets a model holding the current selection.
/// </summary>
public new ISelectionModel Selection
{
get => base.Selection;
set => base.Selection = value;
}
/// <summary>
/// Gets or sets the selection mode.
/// </summary>
@ -95,12 +110,12 @@ namespace Avalonia.Controls
/// <summary>
/// Selects all items in the <see cref="ListBox"/>.
/// </summary>
public new void SelectAll() => base.SelectAll();
public void SelectAll() => Selection.SelectAll();
/// <summary>
/// Deselects all items in the <see cref="ListBox"/>.
/// </summary>
public new void UnselectAll() => base.UnselectAll();
public void UnselectAll() => Selection.ClearSelection();
/// <inheritdoc/>
protected override IItemContainerGenerator CreateItemContainerGenerator()
@ -146,10 +161,9 @@ namespace Avalonia.Controls
}
}
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
Scroll = e.NameScope.Find<IScrollable>("PART_ScrollViewer");
base.OnTemplateApplied(e);
}
}
}

4
src/Avalonia.Controls/MenuItem.cs

@ -417,10 +417,8 @@ namespace Avalonia.Controls
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
if (_popup != null)
{
_popup.Opened -= PopupOpened;

4
src/Avalonia.Controls/Notifications/WindowNotificationManager.cs

@ -77,10 +77,8 @@ namespace Avalonia.Controls.Notifications
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
var itemsControl = e.NameScope.Find<Panel>("PART_Items");
_items = itemsControl?.Children;
}

2
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -295,7 +295,7 @@ namespace Avalonia.Controls
}
/// <inheritdoc />
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
if (TextBox != null)
{

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

@ -5,6 +5,7 @@ using Avalonia.Interactivity;
using Avalonia.LogicalTree;
using Avalonia.Rendering;
using Avalonia.Threading;
using Avalonia.VisualTree;
#nullable enable
@ -338,8 +339,9 @@ namespace Avalonia.Controls.Platform
protected internal virtual void PointerPressed(object sender, PointerPressedEventArgs e)
{
var item = GetMenuItem(e.Source as IControl);
var visual = (IVisual)sender;
if (e.MouseButton == MouseButton.Left && item?.HasSubMenu == true)
if (e.GetCurrentPoint(visual).Properties.IsLeftButtonPressed && item?.HasSubMenu == true)
{
if (item.IsSubMenuOpen)
{
@ -392,7 +394,7 @@ namespace Avalonia.Controls.Platform
{
var control = e.Source as ILogical;
if (!Menu.IsLogicalParentOf(control))
if (!Menu.IsLogicalAncestorOf(control))
{
Menu.Close();
}

16
src/Avalonia.Controls/Platform/IWindowImpl.cs

@ -26,9 +26,21 @@ namespace Avalonia.Platform
void SetTitle(string title);
/// <summary>
/// Shows the window as a dialog.
/// Sets the parent of the window.
/// </summary>
void ShowDialog(IWindowImpl parent);
/// <param name="parent">The parent <see cref="IWindowImpl"/>.</param>
void SetParent(IWindowImpl parent);
/// <summary>
/// Disables the window for example when a modal dialog is open.
/// </summary>
/// <param name="enable">true if the window is enabled, or false if it is disabled.</param>
void SetEnabled(bool enable);
/// <summary>
/// Called when a disabled window received input. Can be used to activate child windows.
/// </summary>
Action GotInputWhenDisabled { get; set; }
/// <summary>
/// Enables or disables system window decorations (title bar, buttons, etc)

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

@ -40,7 +40,12 @@ namespace Avalonia.Controls.Presenters
public static readonly StyledProperty<CornerRadius> CornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="BoxShadow"/> property.
/// </summary>
public static readonly StyledProperty<BoxShadows> BoxShadowProperty =
Border.BoxShadowProperty.AddOwner<ContentPresenter>();
/// <summary>
/// Defines the <see cref="Child"/> property.
/// </summary>
@ -132,6 +137,15 @@ namespace Avalonia.Controls.Presenters
set { SetValue(CornerRadiusProperty, value); }
}
/// <summary>
/// Gets or sets the box shadow effect parameters
/// </summary>
public BoxShadows BoxShadow
{
get => GetValue(BoxShadowProperty);
set => SetValue(BoxShadowProperty, value);
}
/// <summary>
/// Gets the control displayed by the presenter.
/// </summary>
@ -274,7 +288,8 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
public override void Render(DrawingContext context)
{
_borderRenderer.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush);
_borderRenderer.Render(context, Bounds.Size, BorderThickness, CornerRadius, Background, BorderBrush,
BoxShadow);
}
/// <summary>
@ -321,8 +336,6 @@ namespace Avalonia.Controls.Presenters
/// <inheritdoc/>
protected override Size ArrangeOverride(Size finalSize)
{
_borderRenderer.Update(finalSize, BorderThickness, CornerRadius);
return ArrangeOverrideImpl(finalSize, new Vector());
}

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

@ -11,6 +11,6 @@ namespace Avalonia.Controls.Presenters
void ItemsChanged(NotifyCollectionChangedEventArgs e);
void ScrollIntoView(object item);
void ScrollIntoView(int index);
}
}

4
src/Avalonia.Controls/Presenters/ItemVirtualizer.cs

@ -275,8 +275,8 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Scrolls the specified item into view.
/// </summary>
/// <param name="item">The item.</param>
public virtual void ScrollIntoView(object item)
/// <param name="index">The index of the item.</param>
public virtual void ScrollIntoView(int index)
{
}

15
src/Avalonia.Controls/Presenters/ItemVirtualizerNone.cs

@ -64,18 +64,13 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Scrolls the specified item into view.
/// </summary>
/// <param name="item">The item.</param>
public override void ScrollIntoView(object item)
/// <param name="index">The index of the item.</param>
public override void ScrollIntoView(int index)
{
if (Items != null)
if (index != -1)
{
var index = Items.IndexOf(item);
if (index != -1)
{
var container = Owner.ItemContainerGenerator.ContainerFromIndex(index);
container?.BringIntoView();
}
var container = Owner.ItemContainerGenerator.ContainerFromIndex(index);
container?.BringIntoView();
}
}

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

@ -286,20 +286,15 @@ namespace Avalonia.Controls.Presenters
break;
}
return ScrollIntoView(newItemIndex);
return ScrollIntoViewCore(newItemIndex);
}
/// <inheritdoc/>
public override void ScrollIntoView(object item)
public override void ScrollIntoView(int index)
{
if (Items != null)
if (index != -1)
{
var index = Items.IndexOf(item);
if (index != -1)
{
ScrollIntoView(index);
}
ScrollIntoViewCore(index);
}
}
@ -511,7 +506,7 @@ namespace Avalonia.Controls.Presenters
/// </summary>
/// <param name="index">The item index.</param>
/// <returns>The container that was brought into view.</returns>
private IControl ScrollIntoView(int index)
private IControl ScrollIntoViewCore(int index)
{
var panel = VirtualizingPanel;
var generator = Owner.ItemContainerGenerator;

4
src/Avalonia.Controls/Presenters/ItemsPresenter.cs

@ -128,9 +128,9 @@ namespace Avalonia.Controls.Presenters
_scrollInvalidated?.Invoke(this, e);
}
public override void ScrollIntoView(object item)
public override void ScrollIntoView(int index)
{
Virtualizer?.ScrollIntoView(item);
Virtualizer?.ScrollIntoView(index);
}
/// <inheritdoc/>

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

@ -139,7 +139,7 @@ namespace Avalonia.Controls.Presenters
}
/// <inheritdoc/>
public virtual void ScrollIntoView(object item)
public virtual void ScrollIntoView(int index)
{
}

59
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -74,16 +74,18 @@ namespace Avalonia.Controls.Presenters
static TextPresenter()
{
AffectsRender<TextPresenter>(PasswordCharProperty,
SelectionBrushProperty, SelectionForegroundBrushProperty,
SelectionStartProperty, SelectionEndProperty);
Observable.Merge(
TextProperty.Changed,
SelectionStartProperty.Changed,
SelectionEndProperty.Changed,
PasswordCharProperty.Changed
).AddClassHandler<TextPresenter>((x,_) => x.InvalidateFormattedText());
AffectsRender<TextPresenter>(SelectionBrushProperty);
AffectsMeasure<TextPresenter>(TextProperty, PasswordCharProperty,
TextAlignmentProperty, TextWrappingProperty, TextBlock.FontSizeProperty,
TextBlock.FontStyleProperty, TextBlock.FontWeightProperty, TextBlock.FontFamilyProperty);
Observable.Merge(TextProperty.Changed, TextBlock.ForegroundProperty.Changed,
TextAlignmentProperty.Changed, TextWrappingProperty.Changed,
TextBlock.FontSizeProperty.Changed, TextBlock.FontStyleProperty.Changed,
TextBlock.FontWeightProperty.Changed, TextBlock.FontFamilyProperty.Changed,
SelectionStartProperty.Changed, SelectionEndProperty.Changed,
SelectionForegroundBrushProperty.Changed, PasswordCharProperty.Changed
).AddClassHandler<TextPresenter>((x, _) => x.InvalidateFormattedText());
CaretIndexProperty.Changed.AddClassHandler<TextPresenter>((x, e) => x.CaretIndexChanged((int)e.NewValue));
}
@ -184,7 +186,7 @@ namespace Avalonia.Controls.Presenters
{
get
{
return _formattedText ?? (_formattedText = CreateFormattedText(Bounds.Size, Text));
return _formattedText ?? (_formattedText = CreateFormattedText());
}
}
@ -219,7 +221,7 @@ namespace Avalonia.Controls.Presenters
get => GetValue(SelectionForegroundBrushProperty);
set => SetValue(SelectionForegroundBrushProperty, value);
}
public IBrush CaretBrush
{
get => GetValue(CaretBrushProperty);
@ -284,13 +286,7 @@ namespace Avalonia.Controls.Presenters
/// </summary>
protected void InvalidateFormattedText()
{
if (_formattedText != null)
{
_constraint = _formattedText.Constraint;
_formattedText = null;
}
InvalidateVisual();
_formattedText = null;
}
/// <summary>
@ -306,12 +302,15 @@ namespace Avalonia.Controls.Presenters
context.FillRectangle(background, new Rect(Bounds.Size));
}
FormattedText.Constraint = Bounds.Size;
context.DrawText(Foreground, new Point(), FormattedText);
}
public override void Render(DrawingContext context)
{
FormattedText.Constraint = Bounds.Size;
_constraint = Bounds.Size;
var selectionStart = SelectionStart;
var selectionEnd = SelectionEnd;
@ -320,10 +319,6 @@ namespace Avalonia.Controls.Presenters
var start = Math.Min(selectionStart, selectionEnd);
var length = Math.Max(selectionStart, selectionEnd) - start;
// issue #600: set constraint before any FormattedText manipulation
// see base.Render(...) implementation
FormattedText.Constraint = _constraint;
var rects = FormattedText.HitTestTextRange(start, length);
foreach (var rect in rects)
@ -424,20 +419,20 @@ namespace Avalonia.Controls.Presenters
/// <summary>
/// Creates the <see cref="FormattedText"/> used to render the text.
/// </summary>
/// <param name="constraint">The constraint of the text.</param>
/// <param name="text">The text to generated the <see cref="FormattedText"/> for.</param>
/// <returns>A <see cref="FormattedText"/> object.</returns>
protected virtual FormattedText CreateFormattedText(Size constraint, string text)
protected virtual FormattedText CreateFormattedText()
{
FormattedText result = null;
var text = Text;
if (PasswordChar != default(char))
{
result = CreateFormattedTextInternal(constraint, new string(PasswordChar, text?.Length ?? 0));
result = CreateFormattedTextInternal(_constraint, new string(PasswordChar, text?.Length ?? 0));
}
else
{
result = CreateFormattedTextInternal(constraint, text);
result = CreateFormattedTextInternal(_constraint, text);
}
var selectionStart = SelectionStart;
@ -467,13 +462,15 @@ namespace Avalonia.Controls.Presenters
{
if (TextWrapping == TextWrapping.Wrap)
{
FormattedText.Constraint = new Size(availableSize.Width, double.PositiveInfinity);
_constraint = new Size(availableSize.Width, double.PositiveInfinity);
}
else
{
FormattedText.Constraint = Size.Infinity;
_constraint = Size.Infinity;
}
_formattedText = null;
return FormattedText.Bounds.Size;
}

4
src/Avalonia.Controls/Primitives/ScrollBar.cs

@ -143,10 +143,8 @@ namespace Avalonia.Controls.Primitives
}
}
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnTemplateApplied(e);
if (_lineUpButton != null)
{
_lineUpButton.Click -= LineUpClick;

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

File diff suppressed because it is too large

11
src/Avalonia.Controls/Primitives/TemplatedControl.cs

@ -263,7 +263,10 @@ namespace Avalonia.Controls.Primitives
if (nameScope == null)
nameScope = new NameScope();
OnTemplateApplied(new TemplateAppliedEventArgs(nameScope));
var e = new TemplateAppliedEventArgs(nameScope);
OnApplyTemplate(e);
OnTemplateApplied(e);
RaiseEvent(e);
}
_appliedTemplate = template;
@ -306,13 +309,17 @@ namespace Avalonia.Controls.Primitives
base.OnDetachedFromLogicalTree(e);
}
protected virtual void OnApplyTemplate(TemplateAppliedEventArgs e)
{
}
/// <summary>
/// Called when the control's template is applied.
/// </summary>
/// <param name="e">The event args.</param>
[Obsolete("Use OnApplyTemplate")]
protected virtual void OnTemplateApplied(TemplateAppliedEventArgs e)
{
RaiseEvent(e);
}
/// <summary>

2
src/Avalonia.Controls/ProgressBar.cs

@ -98,7 +98,7 @@ namespace Avalonia.Controls
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
_indicator = e.NameScope.Get<Border>("PART_Indicator");

4
src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs

@ -115,7 +115,7 @@ namespace Avalonia.Controls.Remote.Server
{
lock (_lock)
{
_lastReceivedFrame = lastFrame.SequenceId;
_lastReceivedFrame = Math.Max(lastFrame.SequenceId, _lastReceivedFrame);
}
Dispatcher.UIThread.Post(RenderIfNeeded);
}
@ -298,6 +298,8 @@ namespace Avalonia.Controls.Remote.Server
Width = width,
Height = height,
Stride = width * bpp,
DpiX = _dpi.X,
DpiY = _dpi.Y
};
}

2
src/Avalonia.Controls/RepeatButton.cs

@ -88,7 +88,7 @@ namespace Avalonia.Controls
{
base.OnPointerPressed(e);
if (e.MouseButton == MouseButton.Left)
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
StartTimer();
}

28
src/Avalonia.Controls/Repeater/ItemsRepeater.cs

@ -10,6 +10,7 @@ using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Layout;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
@ -379,14 +380,19 @@ namespace Avalonia.Controls
{
if (change.Property == ItemsProperty)
{
var oldEnumerable = change.OldValue.GetValueOrDefault<IEnumerable>();
var newEnumerable = change.NewValue.GetValueOrDefault<IEnumerable>();
var newDataSource = newEnumerable as ItemsSourceView;
if (newEnumerable != null && newDataSource == null)
if (oldEnumerable != newEnumerable)
{
newDataSource = new ItemsSourceView(newEnumerable);
}
var newDataSource = newEnumerable as ItemsSourceView;
if (newEnumerable != null && newDataSource == null)
{
newDataSource = new ItemsSourceView(newEnumerable);
}
OnDataSourcePropertyChanged(ItemsSourceView, newDataSource);
OnDataSourcePropertyChanged(ItemsSourceView, newDataSource);
}
}
else if (change.Property == ItemTemplateProperty)
{
@ -435,8 +441,16 @@ namespace Avalonia.Controls
private int GetElementIndexImpl(IControl element)
{
var virtInfo = TryGetVirtualizationInfo(element);
return _viewManager.GetElementIndex(virtInfo);
// Verify that element is actually a child of this ItemsRepeater
var parent = element.GetVisualParent();
if (parent == this)
{
var virtInfo = TryGetVirtualizationInfo(element);
return _viewManager.GetElementIndex(virtInfo);
}
return -1;
}
private IControl GetElementFromIndexImpl(int index)

2
src/Avalonia.Controls/Repeater/ItemsSourceView.cs

@ -96,6 +96,8 @@ namespace Avalonia.Controls
/// <returns>the item.</returns>
public object GetAt(int index) => _inner[index];
public int IndexOf(object item) => _inner.IndexOf(item);
/// <summary>
/// Retrieves the index of the item that has the specified unique identifier (key).
/// </summary>

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

@ -388,19 +388,24 @@ namespace Avalonia.Controls
}
case NotifyCollectionChangedAction.Reset:
if (_owner.ItemsSourceView.HasKeyIndexMapping)
// If we get multiple resets back to back before
// running layout, we dont have to clear all the elements again.
if (!_isDataSourceStableResetPending)
{
_isDataSourceStableResetPending = true;
}
if (_owner.ItemsSourceView.HasKeyIndexMapping)
{
_isDataSourceStableResetPending = true;
}
// Walk through all the elements and make sure they are cleared, they will go into
// the stable id reset pool.
foreach (var element in _owner.Children)
{
var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
if (virtInfo.IsRealized && virtInfo.AutoRecycleCandidate)
// Walk through all the elements and make sure they are cleared, they will go into
// the stable id reset pool.
foreach (var element in _owner.Children)
{
_owner.ClearElementImpl(element);
var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
if (virtInfo.IsRealized && virtInfo.AutoRecycleCandidate)
{
_owner.ClearElementImpl(element);
}
}
}
@ -441,6 +446,9 @@ namespace Avalonia.Controls
}
_resetPool.Clear();
// Flush the realized indices once the stable reset pool is cleared to start fresh.
InvalidateRealizedIndicesHeldByLayout();
}
}
@ -498,6 +506,10 @@ namespace Avalonia.Controls
var virtInfo = ItemsRepeater.GetVirtualizationInfo(element);
virtInfo.MoveOwnershipToLayoutFromUniqueIdResetPool();
UpdateElementIndex(element, virtInfo, index);
// Update realized indices
_firstRealizedElementIndexHeldByLayout = Math.Min(_firstRealizedElementIndexHeldByLayout, index);
_lastRealizedElementIndexHeldByLayout = Math.Max(_lastRealizedElementIndexHeldByLayout, index);
}
}
@ -519,6 +531,10 @@ namespace Avalonia.Controls
_pinnedPool.RemoveAt(i);
element = elementInfo.PinnedElement;
elementInfo.VirtualizationInfo.MoveOwnershipToLayoutFromPinnedPool();
// Update realized indices
_firstRealizedElementIndexHeldByLayout = Math.Min(_firstRealizedElementIndexHeldByLayout, index);
_lastRealizedElementIndexHeldByLayout = Math.Max(_lastRealizedElementIndexHeldByLayout, index);
break;
}
}

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

Loading…
Cancel
Save