diff --git a/Avalonia.sln.DotSettings b/Avalonia.sln.DotSettings
index 2c0a6b9dc8c7..b0692905e7b7 100644
--- a/Avalonia.sln.DotSettings
+++ b/Avalonia.sln.DotSettings
@@ -1,5 +1,4 @@
- True
ExplicitlyExcluded
ExplicitlyExcluded
ExplicitlyExcluded
@@ -39,4 +38,4 @@
<Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" />
True
True
- True
\ No newline at end of file
+ True
diff --git a/Documentation/build.md b/Documentation/build.md
index a7d68eb59907..9f5436e68e91 100644
--- a/Documentation/build.md
+++ b/Documentation/build.md
@@ -6,6 +6,7 @@ Avalonia requires at least Visual Studio 2019 and .NET Core SDK 3.1 to build on
```
git clone https://github.com/AvaloniaUI/Avalonia.git
+cd Avalonia
git submodule update --init
```
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index fbd85071931d..11ef36d43ffb 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -1,7 +1,7 @@
jobs:
- job: Linux
pool:
- vmImage: 'ubuntu-16.04'
+ vmImage: 'ubuntu-20.04'
steps:
- task: CmdLine@2
displayName: 'Install Nuke'
diff --git a/native/Avalonia.Native/src/OSX/window.mm b/native/Avalonia.Native/src/OSX/window.mm
index 14fe60ab0b9d..7a6e7dc72fe5 100644
--- a/native/Avalonia.Native/src/OSX/window.mm
+++ b/native/Avalonia.Native/src/OSX/window.mm
@@ -641,6 +641,7 @@ virtual void OnResized ()
[Window setCanBecomeKeyAndMain];
[Window disableCursorRects];
[Window setTabbingMode:NSWindowTabbingModeDisallowed];
+ [Window setCollectionBehavior:NSWindowCollectionBehaviorFullScreenPrimary];
}
void HideOrShowTrafficLights ()
@@ -1091,14 +1092,7 @@ void EnterFullScreenMode ()
{
_fullScreenActive = true;
- [Window setHasShadow:YES];
- [Window setTitleVisibility:NSWindowTitleVisible];
- [Window setTitlebarAppearsTransparent:NO];
[Window setTitle:_lastTitle];
-
- Window.styleMask = Window.styleMask | NSWindowStyleMaskTitled | NSWindowStyleMaskResizable;
- Window.styleMask = Window.styleMask & ~NSWindowStyleMaskFullSizeContentView;
-
[Window toggleFullScreen:nullptr];
}
@@ -1672,6 +1666,7 @@ - (void)otherMouseDown:(NSEvent *)event
switch(event.buttonNumber)
{
+ case 2:
case 3:
_isMiddlePressed = true;
[self mouseEvent:event withType:MiddleButtonDown];
@@ -1704,6 +1699,7 @@ - (void)otherMouseUp:(NSEvent *)event
{
switch(event.buttonNumber)
{
+ case 2:
case 3:
_isMiddlePressed = false;
[self mouseEvent:event withType:MiddleButtonUp];
diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index 45a7f1aa4492..de3830ffea62 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -42,12 +42,24 @@
- $(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences
+ $(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences;_GenerateAvaloniaResourcesDependencyCache
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml
index 1ac447ea6937..f631c40eb1b7 100644
--- a/samples/ControlCatalog/Pages/TextBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml
@@ -18,6 +18,7 @@
Watermark="Floating Watermark"
UseFloatingWatermark="True"
Text="Lorem ipsum dolor sit amet, consectetur adipiscing elit."/>
+
diff --git a/src/Avalonia.Base/Collections/AvaloniaList.cs b/src/Avalonia.Base/Collections/AvaloniaList.cs
index 2c7f34c5be1b..2f1cb2888ecf 100644
--- a/src/Avalonia.Base/Collections/AvaloniaList.cs
+++ b/src/Avalonia.Base/Collections/AvaloniaList.cs
@@ -280,8 +280,8 @@ public Enumerator GetEnumerator()
///
/// Gets a range of items from the collection.
///
- /// The first index to remove.
- /// The number of items to remove.
+ /// The zero-based index at which the range starts.
+ /// The number of elements in the range.
public IEnumerable GetRange(int index, int count)
{
return _inner.GetRange(index, count);
@@ -455,7 +455,7 @@ public void MoveRange(int oldIndex, int count, int newIndex)
}
///
- /// Ensures that the capacity of the list is at least .
+ /// Ensures that the capacity of the list is at least .
///
/// The capacity.
public void EnsureCapacity(int capacity)
diff --git a/src/Avalonia.Base/Data/Converters/FuncValueConverter.cs b/src/Avalonia.Base/Data/Converters/FuncValueConverter.cs
index 9ec600d2bc95..2385d4981c4a 100644
--- a/src/Avalonia.Base/Data/Converters/FuncValueConverter.cs
+++ b/src/Avalonia.Base/Data/Converters/FuncValueConverter.cs
@@ -26,7 +26,7 @@ public FuncValueConverter(Func convert)
///
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
- if (value is TIn || (value == null && TypeUtilities.AcceptsNull(typeof(TIn))))
+ if (TypeUtilities.CanCast(value))
{
return _convert((TIn)value);
}
diff --git a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
index 1a7879217366..326d1a3f5350 100644
--- a/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
+++ b/src/Avalonia.Base/Threading/AvaloniaSynchronizationContext.cs
@@ -39,7 +39,7 @@ public override void Send(SendOrPostCallback d, object state)
if (Dispatcher.UIThread.CheckAccess())
d(state);
else
- Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).Wait();
+ Dispatcher.UIThread.InvokeAsync(() => d(state), DispatcherPriority.Send).GetAwaiter().GetResult();
}
diff --git a/src/Avalonia.Base/Utilities/TypeUtilities.cs b/src/Avalonia.Base/Utilities/TypeUtilities.cs
index 179ded35495d..0978308ef6cc 100644
--- a/src/Avalonia.Base/Utilities/TypeUtilities.cs
+++ b/src/Avalonia.Base/Utilities/TypeUtilities.cs
@@ -3,6 +3,7 @@
using System.Globalization;
using System.Linq;
using System.Reflection;
+using System.Runtime.CompilerServices;
namespace Avalonia.Utilities
{
@@ -93,6 +94,17 @@ public static bool AcceptsNull(Type type)
return !type.IsValueType || IsNullableType(type);
}
+ ///
+ /// Returns a value indicating whether null can be assigned to the specified type.
+ ///
+ /// The type
+ /// True if the type accepts null values; otherwise false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static bool AcceptsNull()
+ {
+ return default(T) is null;
+ }
+
///
/// Returns a value indicating whether value can be casted to the specified type.
/// If value is null, checks if instances of that type can be null.
@@ -102,7 +114,7 @@ public static bool AcceptsNull(Type type)
/// True if the cast is possible, otherwise false.
public static bool CanCast(object value)
{
- return value is T || (value is null && AcceptsNull(typeof(T)));
+ return value is T || (value is null && AcceptsNull());
}
///
diff --git a/src/Avalonia.Controls.DataGrid/Collections/DataGridGroupDescription.cs b/src/Avalonia.Controls.DataGrid/Collections/DataGridGroupDescription.cs
index 9d8ebbfac144..587dd228a3a4 100644
--- a/src/Avalonia.Controls.DataGrid/Collections/DataGridGroupDescription.cs
+++ b/src/Avalonia.Controls.DataGrid/Collections/DataGridGroupDescription.cs
@@ -83,8 +83,9 @@ object GetKey(object o)
if (key == null)
key = item;
- if (_valueConverter != null)
- key = _valueConverter.Convert(key, typeof(object), level, culture);
+ var valueConverter = ValueConverter;
+ if (valueConverter != null)
+ key = valueConverter.Convert(key, typeof(object), level, culture);
return key;
}
@@ -99,6 +100,8 @@ public override bool KeysMatch(object groupKey, object itemKey)
}
public override string PropertyName => _propertyPath;
+ public IValueConverter ValueConverter { get => _valueConverter; set => _valueConverter = value; }
+
private Type GetPropertyType(object o)
{
return o.GetType().GetNestedPropertyType(_propertyPath);
diff --git a/src/Avalonia.Controls.DataGrid/DataGrid.cs b/src/Avalonia.Controls.DataGrid/DataGrid.cs
index ab1aff9220f1..fea02dabf46f 100644
--- a/src/Avalonia.Controls.DataGrid/DataGrid.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGrid.cs
@@ -3039,6 +3039,12 @@ internal void UpdateStateOnCurrentChanged(object currentItem, int currentPositio
}
}
+ //TODO: Ensure right button is checked for
+ internal bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit)
+ {
+ KeyboardHelper.GetMetaKeyState(pointerPressedEventArgs.KeyModifiers, out bool ctrl, out bool shift);
+ return UpdateStateOnMouseRightButtonDown(pointerPressedEventArgs, columnIndex, slot, allowEdit, shift, ctrl);
+ }
//TODO: Ensure left button is checked for
internal bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit)
{
@@ -4489,17 +4495,27 @@ private void PopulateCellContent(bool isCellEdited,
element = dataGridColumn.GenerateEditingElementInternal(dataGridCell, dataGridRow.DataContext);
if (element != null)
{
- // Subscribe to the new element's events
- element.Initialized += EditingElement_Initialized;
+
+ dataGridCell.Content = element;
+ if (element.IsInitialized)
+ {
+ PreparingCellForEditPrivate(element as Control);
+ }
+ else
+ {
+ // Subscribe to the new element's events
+ element.Initialized += EditingElement_Initialized;
+ }
}
}
else
{
// Generate Element and apply column style if available
element = dataGridColumn.GenerateElementInternal(dataGridCell, dataGridRow.DataContext);
+ dataGridCell.Content = element;
}
- dataGridCell.Content = element;
+
}
private void PreparingCellForEditPrivate(Control editingElement)
@@ -5711,6 +5727,35 @@ private void VerticalScrollBar_Scroll(object sender, ScrollEventArgs e)
VerticalScroll?.Invoke(sender, e);
}
+ //TODO: Ensure right button is checked for
+ private bool UpdateStateOnMouseRightButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit, bool shift, bool ctrl)
+ {
+ Debug.Assert(slot >= 0);
+
+ if (shift || ctrl)
+ {
+ return true;
+ }
+ if (IsSlotOutOfBounds(slot))
+ {
+ return true;
+ }
+ if (GetRowSelection(slot))
+ {
+ return true;
+ }
+ // Unselect everything except the row that was clicked on
+ try
+ {
+ UpdateSelectionAndCurrency(columnIndex, slot, DataGridSelectionAction.SelectCurrent, scrollIntoView: false);
+ }
+ finally
+ {
+ NoSelectionChangeCount--;
+ }
+ return true;
+ }
+
//TODO: Ensure left button is checked for
private bool UpdateStateOnMouseLeftButtonDown(PointerPressedEventArgs pointerPressedEventArgs, int columnIndex, int slot, bool allowEdit, bool shift, bool ctrl)
{
diff --git a/src/Avalonia.Controls.DataGrid/DataGridCell.cs b/src/Avalonia.Controls.DataGrid/DataGridCell.cs
index 7dda93631711..e3f150f5c477 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridCell.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridCell.cs
@@ -161,29 +161,42 @@ protected override void OnPointerLeave(PointerEventArgs e)
private void DataGridCell_PointerPressed(PointerPressedEventArgs e)
{
// OwningGrid is null for TopLeftHeaderCell and TopRightHeaderCell because they have no OwningRow
- if (OwningGrid != null)
+ if (OwningGrid == null)
{
- OwningGrid.OnCellPointerPressed(new DataGridCellPointerPressedEventArgs(this, OwningRow, OwningColumn, e));
- if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+ return;
+ }
+ OwningGrid.OnCellPointerPressed(new DataGridCellPointerPressedEventArgs(this, OwningRow, OwningColumn, e));
+ if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+ {
+ if (!e.Handled)
+ //if (!e.Handled && OwningGrid.IsTabStop)
+ {
+ OwningGrid.Focus();
+ }
+ if (OwningRow != null)
{
- if (!e.Handled)
- //if (!e.Handled && OwningGrid.IsTabStop)
+ var handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
+
+ // Do not handle PointerPressed with touch,
+ // so we can start scroll gesture on the same event.
+ if (e.Pointer.Type != PointerType.Touch)
{
- OwningGrid.Focus();
+ e.Handled = handled;
}
- if (OwningRow != null)
- {
- var handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
-
- // Do not handle PointerPressed with touch,
- // so we can start scroll gesture on the same event.
- if (e.Pointer.Type != PointerType.Touch)
- {
- e.Handled = handled;
- }
- OwningGrid.UpdatedStateOnMouseLeftButtonDown = true;
- }
+ OwningGrid.UpdatedStateOnMouseLeftButtonDown = true;
+ }
+ }
+ else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
+ {
+ if (!e.Handled)
+ //if (!e.Handled && OwningGrid.IsTabStop)
+ {
+ OwningGrid.Focus();
+ }
+ if (OwningRow != null)
+ {
+ e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, ColumnIndex, OwningRow.Slot, !e.Handled);
}
}
}
diff --git a/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs b/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs
index a94acdec57d9..fade597ca137 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs
@@ -233,7 +233,7 @@ public bool BeginEdit(object dataItem)
else
{
editableCollectionView.EditItem(dataItem);
- return editableCollectionView.IsEditingItem;
+ return editableCollectionView.IsEditingItem || editableCollectionView.IsAddingNew;
}
}
@@ -314,7 +314,14 @@ public bool EndEdit(object dataItem)
CommittingEdit = true;
try
{
- editableCollectionView.CommitEdit();
+ if (editableCollectionView.IsAddingNew)
+ {
+ editableCollectionView.CommitNew();
+ }
+ else
+ {
+ editableCollectionView.CommitEdit();
+ }
}
finally
{
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRow.cs b/src/Avalonia.Controls.DataGrid/DataGridRow.cs
index 754697049896..1efce7c0b872 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRow.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridRow.cs
@@ -378,13 +378,13 @@ internal int? MouseOverColumnIndex
}
}
}
- }
+ }
internal Panel RootElement
{
get;
private set;
- }
+ }
internal int Slot
{
@@ -638,7 +638,7 @@ internal void UpdatePseudoClasses()
PseudoClasses.Set(":editing", IsEditing);
PseudoClasses.Set(":invalid", !IsValid);
ApplyHeaderStatus();
- }
+ }
}
//TODO Animation
@@ -896,7 +896,7 @@ internal void EnsureDetailsContentHeight()
_detailsElement.ContentHeight = _detailsDesiredHeight;
}
}
- }
+ }
// Makes sure the _detailsDesiredHeight is initialized. We need to measure it to know what
// height we want to animate to. Subsequently, we just update that height in response to SizeChanged
@@ -919,7 +919,7 @@ private void EnsureDetailsDesiredHeight()
//TODO Cleanup
double? _previousDetailsHeight = null;
-
+
//TODO Animation
private void DetailsContent_HeightChanged(double newValue)
{
@@ -1022,7 +1022,7 @@ internal void SetDetailsVisibilityInternal(bool isVisible, bool raiseNotificatio
}
}
}
-
+
internal void ApplyDetailsTemplate(bool initializeDetailsPreferredHeight)
{
if (_detailsElement != null && AreDetailsVisible)
@@ -1066,7 +1066,7 @@ internal void ApplyDetailsTemplate(bool initializeDetailsPreferredHeight)
.Subscribe(DetailsContent_MarginChanged);
}
-
+
_detailsElement.Children.Add(_detailsContent);
}
}
@@ -1090,6 +1090,28 @@ internal void ApplyDetailsTemplate(bool initializeDetailsPreferredHeight)
}
}
}
+
+
+ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change)
+ {
+ if (change.Property == DataContextProperty)
+ {
+ var owner = OwningGrid;
+ if (owner != null && this.IsRecycled)
+ {
+ var columns = owner.ColumnsItemsInternal;
+ var nc = columns.Count;
+ for (int ci = 0; ci < nc; ci++)
+ {
+ if (columns[ci] is DataGridTemplateColumn column)
+ {
+ column.RefreshCellContent((Control)this.Cells[column.Index].Content, nameof(DataGridTemplateColumn.CellTemplate));
+ }
+ }
+ }
+ }
+ base.OnPropertyChanged(change);
+ }
}
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
index 1e03b134b1bb..49ca23d34c3a 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
@@ -283,7 +283,11 @@ internal void ClearFrozenStates()
//TODO TabStop
private void DataGridRowGroupHeader_PointerPressed(PointerPressedEventArgs e)
{
- if (OwningGrid != null && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+ if (OwningGrid == null)
+ {
+ return;
+ }
+ if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (OwningGrid.IsDoubleClickRecordsClickOnCall(this) && !e.Handled)
{
@@ -300,6 +304,15 @@ private void DataGridRowGroupHeader_PointerPressed(PointerPressedEventArgs e)
e.Handled = OwningGrid.UpdateStateOnMouseLeftButtonDown(e, OwningGrid.CurrentColumnIndex, RowGroupInfo.Slot, allowEdit: false);
}
}
+ else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
+ {
+ if (!e.Handled)
+ {
+ OwningGrid.Focus();
+ }
+ e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, OwningGrid.CurrentColumnIndex, RowGroupInfo.Slot, allowEdit: false);
+ }
+
}
private void EnsureChildClip(Visual child, double frozenLeftEdge)
diff --git a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
index 0cd3589a5768..510072174fe9 100644
--- a/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
+++ b/src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
@@ -179,12 +179,12 @@ protected override void OnPointerLeave(PointerEventArgs e)
//TODO TabStop
private void DataGridRowHeader_PointerPressed(object sender, PointerPressedEventArgs e)
{
- if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+ if (OwningGrid == null)
{
return;
}
- if (OwningGrid != null)
+ if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
if (!e.Handled)
//if (!e.Handled && OwningGrid.IsTabStop)
@@ -199,6 +199,19 @@ private void DataGridRowHeader_PointerPressed(object sender, PointerPressedEvent
OwningGrid.UpdatedStateOnMouseLeftButtonDown = true;
}
}
+ else if (e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
+ {
+ if (!e.Handled)
+ {
+ OwningGrid.Focus();
+ }
+ if (OwningRow != null)
+ {
+ Debug.Assert(sender is DataGridRowHeader);
+ Debug.Assert(sender == this);
+ e.Handled = OwningGrid.UpdateStateOnMouseRightButtonDown(e, -1, Slot, false);
+ }
+ }
}
}
diff --git a/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs b/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs
index 6ac77fbb9993..1d1a595ccf7e 100644
--- a/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs
+++ b/src/Avalonia.Controls.DataGrid/Utils/CellEditBinding.cs
@@ -1,10 +1,8 @@
using Avalonia.Data;
using Avalonia.Reactive;
using System;
-using System.ComponentModel.DataAnnotations;
using System.Collections.Generic;
using System.Reactive.Subjects;
-using System.Text;
namespace Avalonia.Controls.Utils
{
@@ -67,11 +65,14 @@ public SubjectWrapper(ISubject