Browse Source

Merge branch 'master' into refactor/selectionmodel-rewrite

pull/4533/head
Steven Kirk 6 years ago
parent
commit
c7683854d9
  1. 1
      samples/BindingDemo/MainWindow.xaml
  2. 12
      samples/BindingDemo/ViewModels/MainWindowViewModel.cs
  3. 3
      samples/ControlCatalog/App.xaml
  4. 14
      samples/ControlCatalog/App.xaml.cs
  5. 8
      samples/ControlCatalog/Models/GDPValueConverter.cs
  6. 12
      samples/ControlCatalog/Models/Person.cs
  7. 5
      samples/ControlCatalog/Pages/DataGridPage.xaml
  8. 9
      samples/ControlCatalog/Pages/DataGridPage.xaml.cs
  9. 41
      src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs
  10. 2
      src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs
  11. 230
      src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
  12. 2
      src/Avalonia.Base/Metadata/DependsOnAttribute.cs
  13. 1
      src/Avalonia.Base/Properties/AssemblyInfo.cs
  14. 74
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  15. 13
      src/Avalonia.Controls.DataGrid/DataGridCell.cs
  16. 17
      src/Avalonia.Controls.DataGrid/DataGridColumnHeader.cs
  17. 2
      src/Avalonia.Controls.DataGrid/DataGridDataConnection.cs
  18. 10
      src/Avalonia.Controls.DataGrid/DataGridRow.cs
  19. 22
      src/Avalonia.Controls.DataGrid/DataGridRowGroupHeader.cs
  20. 25
      src/Avalonia.Controls.DataGrid/DataGridRowHeader.cs
  21. 2
      src/Avalonia.Controls.DataGrid/DataGridRows.cs
  22. 4
      src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs
  23. 12
      src/Avalonia.Controls.DataGrid/Themes/Default.xaml
  24. 645
      src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml
  25. 17
      src/Avalonia.Controls/AppBuilderBase.cs
  26. 22
      src/Avalonia.Controls/Presenters/ScrollContentPresenter.cs
  27. 126
      src/Avalonia.Controls/TextBox.cs
  28. 2
      src/Avalonia.Controls/Utils/BorderRenderHelper.cs
  29. 16
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  30. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs
  31. 9
      src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
  32. 71
      src/Avalonia.Diagnostics/Diagnostics/Views/TreePageView.xaml.cs
  33. 17
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  34. 27
      src/Avalonia.Native/ScreenImpl.cs
  35. 15
      src/Avalonia.Native/WindowImplBase.cs
  36. 2
      src/Avalonia.Themes.Default/Expander.xaml
  37. 9
      src/Avalonia.Themes.Default/TextBox.xaml
  38. 4
      src/Avalonia.Themes.Fluent/Expander.xaml
  39. 6
      src/Avalonia.Themes.Fluent/TextBox.xaml
  40. 4
      src/Avalonia.Visuals/Media/GlyphRun.cs
  41. 16
      src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs
  42. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs
  43. 6
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs
  44. 2
      src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
  45. 4
      src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt
  46. 12
      src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs
  47. 41
      tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs
  48. 35
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  49. 26
      tests/Avalonia.LeakTests/ControlTests.cs
  50. 144
      tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs
  51. 60
      tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

1
samples/BindingDemo/MainWindow.xaml

@ -116,6 +116,7 @@
<RadioButton Content="Radio Button" IsChecked="{Binding !!BooleanFlag, Mode=OneWay}" Command="{Binding StringValueCommand}" CommandParameter="RadioButton"/>
<TextBox Text="{Binding Path=StringValue}"/>
<Button Content="Nested View Model Button" Name="NestedTest" Command="{Binding NestedModel.Command}" />
<Button Content="Command Method Do" Command="{Binding Do}" x:Name="ToDo"/>
</StackPanel>
</TabItem>
</TabControl>

12
samples/BindingDemo/ViewModels/MainWindowViewModel.cs

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using System.Threading;
using ReactiveUI;
using Avalonia.Controls;
using Avalonia.Metadata;
using Avalonia.Controls.Selection;
namespace BindingDemo.ViewModels
@ -103,5 +104,16 @@ namespace BindingDemo.ViewModels
get { return _nested; }
private set { this.RaiseAndSetIfChanged(ref _nested, value); }
}
public void Do(object parameter)
{
}
[DependsOn(nameof(BooleanFlag))]
bool CanDo(object parameter)
{
return BooleanFlag;
}
}
}

3
samples/ControlCatalog/App.xaml

@ -1,8 +1,7 @@
<Application xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.App">
<Application.Styles>
<StyleInclude Source="avares://Avalonia.Controls.DataGrid/Themes/Default.xaml"/>
<Application.Styles>
<Style Selector="TextBlock.h1">
<Setter Property="FontSize" Value="16" />
<Setter Property="FontWeight" Value="Medium" />

14
samples/ControlCatalog/App.xaml.cs

@ -9,12 +9,23 @@ namespace ControlCatalog
{
public class App : Application
{
private static readonly StyleInclude DataGridFluent = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Fluent.xaml")
};
private static readonly StyleInclude DataGridDefault = new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Controls.DataGrid/Themes/Default.xaml")
};
public static Styles FluentDark = new Styles
{
new StyleInclude(new Uri("avares://ControlCatalog/Styles"))
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentDark.xaml")
},
DataGridFluent
};
public static Styles FluentLight = new Styles
@ -23,6 +34,7 @@ namespace ControlCatalog
{
Source = new Uri("avares://Avalonia.Themes.Fluent/Accents/FluentLight.xaml")
},
DataGridFluent
};
public static Styles DefaultLight = new Styles
@ -43,6 +55,7 @@ namespace ControlCatalog
{
Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml")
},
DataGridDefault
};
public static Styles DefaultDark = new Styles
@ -63,6 +76,7 @@ namespace ControlCatalog
{
Source = new Uri("avares://Avalonia.Themes.Default/DefaultTheme.xaml")
},
DataGridDefault
};
public override void Initialize()

8
samples/ControlCatalog/Models/GDPValueConverter.cs

@ -19,11 +19,11 @@ namespace ControlCatalog.Models
if (value is int gdp)
{
if (gdp <= 5000)
return Brushes.Orange;
return new SolidColorBrush(Colors.Orange, 0.6);
else if (gdp <= 10000)
return Brushes.Yellow;
return new SolidColorBrush(Colors.Yellow, 0.6);
else
return Brushes.LightGreen;
return new SolidColorBrush(Colors.LightGreen, 0.6);
}
return value;
@ -34,4 +34,4 @@ namespace ControlCatalog.Models
throw new NotImplementedException();
}
}
}
}

12
samples/ControlCatalog/Models/Person.cs

@ -15,6 +15,7 @@ namespace ControlCatalog.Models
{
string _firstName;
string _lastName;
bool _isBanned;
public string FirstName
{
@ -47,6 +48,17 @@ namespace ControlCatalog.Models
}
}
public bool IsBanned
{
get => _isBanned;
set
{
_isBanned = value;
OnPropertyChanged(nameof(_isBanned));
}
}
Dictionary<string, List<string>> _errorLookup = new Dictionary<string, List<string>>();
void SetError(string propertyName, string error)

5
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -18,7 +18,7 @@
</StackPanel>
<TabControl Grid.Row="1">
<TabItem Header="DataGrid">
<DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True">
<DataGrid Name="dataGrid1" Margin="12" CanUserResizeColumns="True" CanUserReorderColumns="True" CanUserSortColumns="True" HeadersVisibility="All">
<DataGrid.Columns>
<DataGridTextColumn Header="Country" Binding="{Binding Name}" Width="6*" />
<DataGridTextColumn Header="Region" Binding="{Binding Region}" Width="4*" />
@ -44,7 +44,8 @@
<DataGrid Name="dataGridEdit" Margin="12" Grid.Row="0">
<DataGrid.Columns>
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" Width="2*" />
<DataGridTextColumn Header="Last" Binding="{Binding LastName}" Width="*" />
<DataGridTextColumn Header="Last" Binding="{Binding LastName}" Width="2*" />
<DataGridCheckBoxColumn Header="Is Banned" Binding="{Binding IsBanned}" Width="*" />
</DataGrid.Columns>
</DataGrid>
<Button Grid.Row="1" Name="btnAdd" Margin="12,0,12,12" Content="Add" HorizontalAlignment="Right" />

9
samples/ControlCatalog/Pages/DataGridPage.xaml.cs

@ -13,7 +13,7 @@ namespace ControlCatalog.Pages
this.InitializeComponent();
var dg1 = this.FindControl<DataGrid>("dataGrid1");
dg1.IsReadOnly = true;
dg1.LoadingRow += Dg1_LoadingRow;
var collectionView1 = new DataGridCollectionView(Countries.All);
//collectionView.GroupDescriptions.Add(new PathGroupDescription("Region"));
@ -33,7 +33,7 @@ namespace ControlCatalog.Pages
var items = new List<Person>
{
new Person { FirstName = "John", LastName = "Doe" },
new Person { FirstName = "Elizabeth", LastName = "Thomas" },
new Person { FirstName = "Elizabeth", LastName = "Thomas", IsBanned = true },
new Person { FirstName = "Zack", LastName = "Ward" }
};
var collectionView3 = new DataGridCollectionView(items);
@ -44,6 +44,11 @@ namespace ControlCatalog.Pages
addButton.Click += (a, b) => collectionView3.AddNew();
}
private void Dg1_LoadingRow(object sender, DataGridRowEventArgs e)
{
e.Row.Header = e.Row.GetIndex() + 1;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);

41
src/Avalonia.Base/Data/Converters/AlwaysEnabledDelegateCommand.cs

@ -1,41 +0,0 @@
using System;
using System.Globalization;
using System.Reflection;
using System.Windows.Input;
using Avalonia.Utilities;
namespace Avalonia.Data.Converters
{
class AlwaysEnabledDelegateCommand : ICommand
{
private readonly Delegate action;
private ParameterInfo parameterInfo;
public AlwaysEnabledDelegateCommand(Delegate action)
{
this.action = action;
var parameters = action.Method.GetParameters();
parameterInfo = parameters.Length == 0 ? null : parameters[0];
}
#pragma warning disable 0067
public event EventHandler CanExecuteChanged;
#pragma warning restore 0067
public bool CanExecute(object parameter) => true;
public void Execute(object parameter)
{
if (parameterInfo == null)
{
action.DynamicInvoke();
}
else
{
TypeUtilities.TryConvert(parameterInfo.ParameterType, parameter, CultureInfo.CurrentCulture, out object convertedParameter);
action.DynamicInvoke(convertedParameter);
}
}
}
}

2
src/Avalonia.Base/Data/Converters/DefaultValueConverter.cs

@ -33,7 +33,7 @@ namespace Avalonia.Data.Converters
if (typeof(ICommand).IsAssignableFrom(targetType) && value is Delegate d && d.Method.GetParameters().Length <= 1)
{
return new AlwaysEnabledDelegateCommand(d);
return new MethodToCommandConverter(d);
}
if (TypeUtilities.TryConvert(targetType, value, culture, out object result))

230
src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs

@ -0,0 +1,230 @@
using System;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Windows.Input;
using Avalonia.Utilities;
namespace Avalonia.Data.Converters
{
class MethodToCommandConverter : ICommand
{
readonly static Func<object, bool> AlwaysEnabled = (_) => true;
readonly static MethodInfo tryConvert = typeof(TypeUtilities)
.GetMethod(nameof(TypeUtilities.TryConvert), BindingFlags.Public | BindingFlags.Static);
readonly static PropertyInfo currentCulture = typeof(CultureInfo)
.GetProperty(nameof(CultureInfo.CurrentCulture), BindingFlags.Public | BindingFlags.Static);
readonly Func<object, bool> canExecute;
readonly Action<object> execute;
readonly WeakPropertyChangedProxy weakPropertyChanged;
readonly PropertyChangedEventHandler propertyChangedEventHandler;
readonly string[] dependencyProperties;
public MethodToCommandConverter(Delegate action)
{
var target = action.Target;
var canExecuteMethodName = "Can" + action.Method.Name;
var parameters = action.Method.GetParameters();
var parameterInfo = parameters.Length == 0 ? null : parameters[0].ParameterType;
if (parameterInfo == null)
{
execute = CreateExecute(target, action.Method);
}
else
{
execute = CreateExecute(target, action.Method, parameterInfo);
}
var canExecuteMethod = action.Method.DeclaringType.GetRuntimeMethods()
.FirstOrDefault(m => m.Name == canExecuteMethodName
&& m.GetParameters().Length == 1
&& m.GetParameters()[0].ParameterType == typeof(object));
if (canExecuteMethod == null)
{
canExecute = AlwaysEnabled;
}
else
{
canExecute = CreateCanExecute(target, canExecuteMethod);
dependencyProperties = canExecuteMethod
.GetCustomAttributes(typeof(Metadata.DependsOnAttribute), true)
.OfType<Metadata.DependsOnAttribute>()
.Select(x => x.Name)
.ToArray();
if (dependencyProperties.Any()
&& target is INotifyPropertyChanged inpc)
{
propertyChangedEventHandler = OnPropertyChanged;
weakPropertyChanged = new WeakPropertyChangedProxy(inpc, propertyChangedEventHandler);
}
}
}
void OnPropertyChanged(object sender,PropertyChangedEventArgs args)
{
if (string.IsNullOrWhiteSpace(args.PropertyName)
|| dependencyProperties?.Contains(args.PropertyName) == true)
{
Threading.Dispatcher.UIThread.Post(() => CanExecuteChanged?.Invoke(this, EventArgs.Empty)
, Threading.DispatcherPriority.Input);
}
}
#pragma warning disable 0067
public event EventHandler CanExecuteChanged;
#pragma warning restore 0067
public bool CanExecute(object parameter) => canExecute(parameter);
public void Execute(object parameter) => execute(parameter);
static Action<object> CreateExecute(object target
, System.Reflection.MethodInfo method)
{
var parameter = Expression.Parameter(typeof(object), "parameter");
var instance = Expression.Convert
(
Expression.Constant(target),
method.DeclaringType
);
var call = Expression.Call
(
instance,
method
);
return Expression
.Lambda<Action<object>>(call, parameter)
.Compile();
}
static Action<object> CreateExecute(object target
, System.Reflection.MethodInfo method
, Type parameterType)
{
var parameter = Expression.Parameter(typeof(object), "parameter");
var instance = Expression.Convert
(
Expression.Constant(target),
method.DeclaringType
);
Expression body;
if (parameterType == typeof(object))
{
body = Expression.Call(instance,
method,
parameter
);
}
else
{
var arg0 = Expression.Variable(typeof(object), "argX");
var convertCall = Expression.Call(tryConvert,
Expression.Constant(parameterType),
parameter,
Expression.Property(null, currentCulture),
arg0
);
var call = Expression.Call(instance,
method,
Expression.Convert(arg0, parameterType)
);
body = Expression.Block(new[] { arg0 },
convertCall,
call
);
}
Action<object> action = null;
try
{
action = Expression
.Lambda<Action<object>>(body, parameter)
.Compile();
}
catch (Exception ex)
{
throw ex;
}
return action;
}
static Func<object, bool> CreateCanExecute(object target
, System.Reflection.MethodInfo method)
{
var parameter = Expression.Parameter(typeof(object), "parameter");
var instance = Expression.Convert
(
Expression.Constant(target),
method.DeclaringType
);
var call = Expression.Call
(
instance,
method,
parameter
);
return Expression
.Lambda<Func<object, bool>>(call, parameter)
.Compile();
}
internal class WeakPropertyChangedProxy
{
readonly WeakReference<PropertyChangedEventHandler> _listener = new WeakReference<PropertyChangedEventHandler>(null);
readonly PropertyChangedEventHandler _handler;
internal WeakReference<INotifyPropertyChanged> Source { get; } = new WeakReference<INotifyPropertyChanged>(null);
public WeakPropertyChangedProxy()
{
_handler = new PropertyChangedEventHandler(OnPropertyChanged);
}
public WeakPropertyChangedProxy(INotifyPropertyChanged source, PropertyChangedEventHandler listener) : this()
{
SubscribeTo(source, listener);
}
public void SubscribeTo(INotifyPropertyChanged source, PropertyChangedEventHandler listener)
{
source.PropertyChanged += _handler;
Source.SetTarget(source);
_listener.SetTarget(listener);
}
public void Unsubscribe()
{
if (Source.TryGetTarget(out INotifyPropertyChanged source) && source != null)
source.PropertyChanged -= _handler;
Source.SetTarget(null);
_listener.SetTarget(null);
}
void OnPropertyChanged(object sender, PropertyChangedEventArgs e)
{
if (_listener.TryGetTarget(out var handler) && handler != null)
handler(sender, e);
else
Unsubscribe();
}
}
}
}

2
src/Avalonia.Base/Metadata/DependsOnAttribute.cs

@ -5,7 +5,7 @@ namespace Avalonia.Metadata
/// <summary>
/// Indicates that the property depends on the value of another property in markup.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Method, Inherited = true, AllowMultiple = true)]
public class DependsOnAttribute : Attribute
{
/// <summary>

1
src/Avalonia.Base/Properties/AssemblyInfo.cs

@ -9,3 +9,4 @@ using Avalonia.Metadata;
[assembly: InternalsVisibleTo("Avalonia.UnitTests")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: InternalsVisibleTo("Avalonia.Controls.DataGrid")]
[assembly: InternalsVisibleTo("Avalonia.Markup.Xaml.UnitTests")]

74
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -39,6 +39,7 @@ namespace Avalonia.Controls
private const string DATAGRID_elementRowHeadersPresenterName = "PART_RowHeadersPresenter";
private const string DATAGRID_elementTopLeftCornerHeaderName = "PART_TopLeftCornerHeader";
private const string DATAGRID_elementTopRightCornerHeaderName = "PART_TopRightCornerHeader";
private const string DATAGRID_elementBottomRightCornerHeaderName = "PART_BottomRightCorner";
private const string DATAGRID_elementValidationSummary = "PART_ValidationSummary";
private const string DATAGRID_elementVerticalScrollbarName = "PART_VerticalScrollbar";
@ -79,6 +80,7 @@ namespace Avalonia.Controls
private INotifyCollectionChanged _topLevelGroup;
private ContentControl _clipboardContentControl;
private IVisual _bottomRightCorner;
private DataGridColumnHeadersPresenter _columnHeadersPresenter;
private DataGridRowsPresenter _rowsPresenter;
private ScrollBar _vScrollBar;
@ -1825,6 +1827,22 @@ namespace Avalonia.Controls
}
}
private bool IsHorizontalScrollBarOverCells
{
get
{
return _columnHeadersPresenter != null && Grid.GetColumnSpan(_columnHeadersPresenter) == 2;
}
}
private bool IsVerticalScrollBarOverCells
{
get
{
return _rowsPresenter != null && Grid.GetRowSpan(_rowsPresenter) == 2;
}
}
private int NoSelectionChangeCount
{
get
@ -2323,6 +2341,7 @@ namespace Avalonia.Controls
_topLeftCornerHeader = e.NameScope.Find<ContentControl>(DATAGRID_elementTopLeftCornerHeaderName);
EnsureTopLeftCornerHeader(); // EnsureTopLeftCornerHeader checks for a null _topLeftCornerHeader;
_topRightCornerHeader = e.NameScope.Find<ContentControl>(DATAGRID_elementTopRightCornerHeaderName);
_bottomRightCorner = e.NameScope.Find<IVisual>(DATAGRID_elementBottomRightCornerHeaderName);
}
/// <summary>
@ -2753,7 +2772,7 @@ namespace Avalonia.Controls
//We don't need to refresh the state of AutoGenerated column headers because they're up-to-date
if (!column.IsAutoGenerated && column.HasHeaderCell)
{
column.HeaderCell.ApplyState();
column.HeaderCell.UpdatePseudoClasses();
}
}
@ -3293,6 +3312,10 @@ namespace Avalonia.Controls
//
}
bool isHorizontalScrollBarOverCells = IsHorizontalScrollBarOverCells;
bool isVerticalScrollBarOverCells = IsVerticalScrollBarOverCells;
double cellsWidth = CellsWidth;
double cellsHeight = CellsHeight;
@ -3308,10 +3331,17 @@ namespace Avalonia.Controls
// Compensate if the horizontal scrollbar is already taking up space
if (!forceHorizScrollbar && _hScrollBar.IsVisible)
{
cellsHeight += _hScrollBar.DesiredSize.Height;
if (!isHorizontalScrollBarOverCells)
{
cellsHeight += _hScrollBar.DesiredSize.Height;
}
}
if (!isHorizontalScrollBarOverCells)
{
horizScrollBarHeight = _hScrollBar.Height + _hScrollBar.Margin.Top + _hScrollBar.Margin.Bottom;
}
horizScrollBarHeight = _hScrollBar.Height + _hScrollBar.Margin.Top + _hScrollBar.Margin.Bottom;
}
bool allowVertScrollbar = false;
bool forceVertScrollbar = false;
double vertScrollBarWidth = 0;
@ -3324,9 +3354,15 @@ namespace Avalonia.Controls
// Compensate if the vertical scrollbar is already taking up space
if (!forceVertScrollbar && _vScrollBar.IsVisible)
{
cellsWidth += _vScrollBar.DesiredSize.Width;
if (!isVerticalScrollBarOverCells)
{
cellsWidth += _vScrollBar.DesiredSize.Width;
}
}
if (!isVerticalScrollBarOverCells)
{
vertScrollBarWidth = _vScrollBar.Width + _vScrollBar.Margin.Left + _vScrollBar.Margin.Right;
}
vertScrollBarWidth = _vScrollBar.Width + _vScrollBar.Margin.Left + _vScrollBar.Margin.Right;
}
// Now cellsWidth is the width potentially available for displaying data cells.
@ -3354,7 +3390,9 @@ namespace Avalonia.Controls
cellsHeight -= horizScrollBarHeight;
Debug.Assert(cellsHeight >= 0);
needHorizScrollbarWithoutVertScrollbar = needHorizScrollbar = true;
if (allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) ||
if (vertScrollBarWidth > 0 &&
allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) ||
MathUtilities.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth)))
{
// Would we still need a horizontal scrollbar without the vertical one?
@ -3372,7 +3410,12 @@ namespace Avalonia.Controls
}
}
UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight);
// Store the current FirstScrollingSlot because removing the horizontal scrollbar could scroll
// the DataGrid up; however, if we realize later that we need to keep the horizontal scrollbar
// then we should use the first slot stored here which is not scrolled.
int firstScrollingSlot = DisplayData.FirstScrollingSlot;
UpdateDisplayedRows(firstScrollingSlot, cellsHeight);
if (allowVertScrollbar &&
MathUtilities.GreaterThan(cellsHeight, 0) &&
MathUtilities.LessThanOrClose(vertScrollBarWidth, cellsWidth) &&
@ -3384,10 +3427,12 @@ namespace Avalonia.Controls
}
DisplayData.FirstDisplayedScrollingCol = ComputeFirstVisibleScrollingColumn();
// we compute the number of visible columns only after we set up the vertical scroll bar.
ComputeDisplayedColumns();
if (allowHorizScrollbar &&
if ((vertScrollBarWidth > 0 || horizScrollBarHeight > 0) &&
allowHorizScrollbar &&
needVertScrollbar && !needHorizScrollbar &&
MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
MathUtilities.LessThan(totalVisibleFrozenWidth, cellsWidth) &&
@ -3398,7 +3443,7 @@ namespace Avalonia.Controls
Debug.Assert(cellsHeight >= 0);
needVertScrollbar = false;
UpdateDisplayedRows(DisplayData.FirstScrollingSlot, cellsHeight);
UpdateDisplayedRows(firstScrollingSlot, cellsHeight);
if (cellsHeight > 0 &&
vertScrollBarWidth <= cellsWidth &&
DisplayData.NumTotallyDisplayedScrollingElements != VisibleSlotCount)
@ -3479,6 +3524,15 @@ namespace Avalonia.Controls
_topRightCornerHeader.IsVisible = false;
}
}
if (_bottomRightCorner != null)
{
// Show the BottomRightCorner when both scrollbars are visible.
_bottomRightCorner.IsVisible =
_hScrollBar != null && _hScrollBar.IsVisible &&
_vScrollBar != null && _vScrollBar.IsVisible;
}
DisplayData.FullyRecycleElements();
}
@ -5417,7 +5471,7 @@ namespace Avalonia.Controls
}
else if (displayedElement is DataGridRowGroupHeader groupHeader)
{
groupHeader.ApplyState(useTransitions: true);
groupHeader.UpdatePseudoClasses();
if (AreRowHeadersVisible)
{
groupHeader.ApplyHeaderStatus();

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

@ -19,7 +19,7 @@ namespace Avalonia.Controls
private Rectangle _rightGridLine;
private DataGridColumn _owningColumn;
bool _isValid;
bool _isValid = true;
public static readonly DirectProperty<DataGridCell, bool> IsValidProperty =
AvaloniaProperty.RegisterDirect<DataGridCell, bool>(
@ -180,7 +180,18 @@ namespace Avalonia.Controls
internal void UpdatePseudoClasses()
{
if (OwningGrid == null || OwningColumn == null || OwningRow == null || !OwningRow.IsVisible || OwningRow.Slot == -1)
{
return;
}
PseudoClasses.Set(":selected", OwningRow.IsSelected);
PseudoClasses.Set(":current", IsCurrent);
PseudoClasses.Set(":edited", IsEdited);
PseudoClasses.Set(":invalid", !IsValid);
}
// Makes sure the right gridline has the proper stroke and visibility. If lastVisibleColumn is specified, the

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

@ -13,6 +13,7 @@ using System.Diagnostics;
using Avalonia.Utilities;
using System;
using Avalonia.Controls.Utils;
using Avalonia.Controls.Mixins;
namespace Avalonia.Controls
{
@ -39,7 +40,7 @@ namespace Avalonia.Controls
private static Cursor _originalCursor;
private static double _originalHorizontalOffset;
private static double _originalWidth;
private bool _desiredSeparatorVisibility;
private bool _desiredSeparatorVisibility = true;
private static Point? _dragStart;
private static DataGridColumn _dragColumn;
private static double _frozenColumnsWidth;
@ -68,6 +69,7 @@ namespace Avalonia.Controls
static DataGridColumnHeader()
{
AreSeparatorsVisibleProperty.Changed.AddClassHandler<DataGridColumnHeader>((x, e) => x.OnAreSeparatorsVisibleChanged(e));
PressedMixin.Attach<DataGridColumnHeader>();
}
/// <summary>
@ -149,8 +151,7 @@ namespace Avalonia.Controls
}
}
//TODO Implement
internal void ApplyState()
internal void UpdatePseudoClasses()
{
CurrentSortingState = null;
if (OwningGrid != null
@ -441,7 +442,7 @@ namespace Avalonia.Controls
Point mousePosition = e.GetPosition(this);
OnMouseEnter(mousePosition);
ApplyState();
UpdatePseudoClasses();
}
private void DataGridColumnHeader_PointerLeave(object sender, PointerEventArgs e)
@ -452,12 +453,12 @@ namespace Avalonia.Controls
}
OnMouseLeave();
ApplyState();
UpdatePseudoClasses();
}
private void DataGridColumnHeader_PointerPressed(object sender, PointerPressedEventArgs e)
{
if (OwningColumn == null || e.Handled || !IsEnabled || e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
if (OwningColumn == null || e.Handled || !IsEnabled || !e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
return;
}
@ -467,7 +468,7 @@ namespace Avalonia.Controls
OnMouseLeftButtonDown(ref handled, e, mousePosition);
e.Handled = handled;
ApplyState();
UpdatePseudoClasses();
}
private void DataGridColumnHeader_PointerReleased(object sender, PointerReleasedEventArgs e)
@ -483,7 +484,7 @@ namespace Avalonia.Controls
OnMouseLeftButtonUp(ref handled, e, mousePosition, mousePositionHeaders);
e.Handled = handled;
ApplyState();
UpdatePseudoClasses();
}
private void DataGridColumnHeader_PointerMove(object sender, PointerEventArgs e)

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

@ -610,7 +610,7 @@ namespace Avalonia.Controls
// refresh sort description
foreach (DataGridColumn column in _owner.ColumnsItemsInternal)
{
column.HeaderCell.ApplyState();
column.HeaderCell.UpdatePseudoClasses();
}
}

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

@ -624,17 +624,17 @@ namespace Avalonia.Controls
{
if (_headerElement != null && OwningGrid.AreRowHeadersVisible)
{
_headerElement.ApplyOwnerStatus();
_headerElement.UpdatePseudoClasses();
}
}
//TODO Implement
internal void UpdatePseudoClasses()
{
PseudoClasses.Set(":selected", IsSelected);
PseudoClasses.Set(":editing", IsEditing);
if (RootElement != null && OwningGrid != null && IsVisible)
{
PseudoClasses.Set(":selected", IsSelected);
PseudoClasses.Set(":editing", IsEditing);
PseudoClasses.Set(":invalid", !IsValid);
ApplyHeaderStatus();
}
}
@ -789,7 +789,7 @@ namespace Avalonia.Controls
private void DataGridRow_PointerPressed(PointerPressedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
return;
}

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

@ -3,6 +3,7 @@
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using Avalonia.Controls.Mixins;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Media;
@ -96,6 +97,7 @@ namespace Avalonia.Controls
static DataGridRowGroupHeader()
{
SublevelIndentProperty.Changed.AddClassHandler<DataGridRowGroupHeader>((x,e) => x.OnSublevelIndentChanged(e));
PressedMixin.Attach<DataGridRowGroupHeader>();
}
/// <summary>
@ -205,14 +207,18 @@ namespace Avalonia.Controls
{
if (_headerElement != null && OwningGrid.AreRowHeadersVisible)
{
_headerElement.ApplyOwnerStatus();
_headerElement.UpdatePseudoClasses();
}
}
//TODO Implement
internal void ApplyState(bool useTransitions)
internal void UpdatePseudoClasses()
{
PseudoClasses.Set(":current", IsCurrent);
if (RowGroupInfo?.CollectionViewGroup != null)
{
PseudoClasses.Set(":expanded", RowGroupInfo.IsVisible && RowGroupInfo.CollectionViewGroup.ItemCount > 0);
}
}
protected override Size ArrangeOverride(Size finalSize)
@ -328,7 +334,7 @@ namespace Avalonia.Controls
{
if (_headerElement != null && OwningGrid != null)
{
_headerElement.IsVisible = OwningGrid.AreColumnHeadersVisible;
_headerElement.IsVisible = OwningGrid.AreRowHeadersVisible;
}
}
@ -344,7 +350,7 @@ namespace Avalonia.Controls
{
EnsureExpanderButtonIsChecked();
EnsureHeaderVisibility();
ApplyState(useTransitions: false);
UpdatePseudoClasses();
ApplyHeaderStatus();
}
@ -353,7 +359,7 @@ namespace Avalonia.Controls
if (IsEnabled)
{
IsMouseOver = true;
ApplyState(useTransitions: true);
UpdatePseudoClasses();
}
base.OnPointerEnter(e);
@ -364,7 +370,7 @@ namespace Avalonia.Controls
if (IsEnabled)
{
IsMouseOver = false;
ApplyState(useTransitions: true);
UpdatePseudoClasses();
}
base.OnPointerLeave(e);
@ -402,7 +408,7 @@ namespace Avalonia.Controls
EnsureExpanderButtonIsChecked();
ApplyState(true /*useTransitions*/);
UpdatePseudoClasses();
}
}

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

@ -14,7 +14,7 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public class DataGridRowHeader : ContentControl
{
private const string DATAGRIDROWHEADER_elementRootName = "Root";
private const string DATAGRIDROWHEADER_elementRootName = "PART_Root";
private const double DATAGRIDROWHEADER_separatorThickness = 1;
private Control _rootElement;
@ -99,7 +99,7 @@ namespace Avalonia.Controls.Primitives
_rootElement = e.NameScope.Find<Control>(DATAGRIDROWHEADER_elementRootName);
if (_rootElement != null)
{
ApplyOwnerStatus();
UpdatePseudoClasses();
}
}
@ -131,12 +131,27 @@ namespace Avalonia.Controls.Primitives
return measuredSize;
}
//TODO Implement
internal void ApplyOwnerStatus()
internal void UpdatePseudoClasses()
{
if (_rootElement != null && Owner != null && Owner.IsVisible)
{
if (OwningRow != null)
{
PseudoClasses.Set(":invalid", !OwningRow.IsValid);
PseudoClasses.Set(":selected", OwningRow.IsSelected);
PseudoClasses.Set(":editing", OwningRow.IsEditing);
if (OwningGrid != null)
{
PseudoClasses.Set(":current", OwningRow.Slot == OwningGrid.CurrentSlot);
}
}
else if (OwningRowGroupHeader != null && OwningGrid != null)
{
PseudoClasses.Set(":current", OwningRowGroupHeader.RowGroupInfo.Slot == OwningGrid.CurrentSlot);
}
}
}
@ -162,7 +177,7 @@ namespace Avalonia.Controls.Primitives
//TODO TabStop
private void DataGridRowHeader_PointerPressed(object sender, PointerPressedEventArgs e)
{
if (e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
if (!e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
{
return;
}

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

@ -1912,7 +1912,7 @@ namespace Avalonia.Controls
{
// Assume it's a RowGroupHeader
DataGridRowGroupHeader groupHeader = element as DataGridRowGroupHeader;
groupHeader.ApplyState(useTransitions: true);
groupHeader.UpdatePseudoClasses();
}
}

4
src/Avalonia.Controls.DataGrid/DataGridTextColumn.cs

@ -9,6 +9,7 @@ using Avalonia.Media;
using System;
using System.ComponentModel;
using Avalonia.Layout;
using Avalonia.Markup.Xaml.MarkupExtensions;
namespace Avalonia.Controls
{
@ -17,6 +18,7 @@ namespace Avalonia.Controls
/// </summary>
public class DataGridTextColumn : DataGridBoundColumn
{
private const string DATAGRID_TextColumnCellTextBlockMarginKey = "DataGridTextColumnCellTextBlockMargin";
private double? _fontSize;
private FontStyle? _fontStyle;
@ -186,7 +188,7 @@ namespace Avalonia.Controls
{
TextBlock textBlockElement = new TextBlock
{
Margin = new Thickness(4),
[!Layoutable.MarginProperty] = new DynamicResourceExtension(DATAGRID_TextColumnCellTextBlockMarginKey),
VerticalAlignment = VerticalAlignment.Center
};

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

@ -1,5 +1,11 @@
<Styles xmlns="https://github.com/avaloniaui">
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!--TODO: Validation and Focus-->
<Styles.Resources>
<Thickness x:Key="DataGridTextColumnCellTextBlockMargin">4</Thickness>
</Styles.Resources>
<Style Selector="DataGridCell">
<Setter Property="Background" Value="Transparent"/>
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
@ -133,7 +139,7 @@
<Style Selector="DataGridRowHeader">
<Setter Property="Template">
<ControlTemplate>
<Grid
<Grid x:Name="PART_Root"
RowDefinitions="*,*,Auto"
ColumnDefinitions="Auto,*">
@ -218,7 +224,7 @@
<Rectangle Name="PART_ColumnHeadersAndRowsSeparator" Grid.ColumnSpan="3" VerticalAlignment="Bottom" StrokeThickness="1" Height="1" Fill="{DynamicResource ThemeControlMidHighBrush}"/>
<DataGridRowsPresenter Name="PART_RowsPresenter" Grid.ColumnSpan="2" Grid.Row="1" />
<Rectangle Name="BottomRightCorner" Fill="{DynamicResource ThemeControlMidHighBrush}" Grid.Column="2" Grid.Row="2" />
<Rectangle Name="PART_BottomRightCorner" Fill="{DynamicResource ThemeControlMidHighBrush}" Grid.Column="2" Grid.Row="2" />
<Rectangle Name="BottomLeftCorner" Fill="{DynamicResource ThemeControlMidHighBrush}" Grid.Row="2" Grid.ColumnSpan="2" />
<ScrollBar Name="PART_VerticalScrollbar" Orientation="Vertical" Grid.Column="2" Grid.Row="1" Width="{DynamicResource ScrollBarThickness}"/>

645
src/Avalonia.Controls.DataGrid/Themes/Fluent.xaml

@ -0,0 +1,645 @@
<Styles xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<Thickness x:Key="DataGridTextColumnCellTextBlockMargin">12,0,12,0</Thickness>
<x:Double x:Key="ListAccentLowOpacity">0.6</x:Double>
<x:Double x:Key="ListAccentMediumOpacity">0.8</x:Double>
<StreamGeometry x:Key="DataGridSortIconAscendingPath">M1875 1011l-787 787v-1798h-128v1798l-787 -787l-90 90l941 941l941 -941z</StreamGeometry>
<StreamGeometry x:Key="DataGridSortIconDescendingPath">M1965 947l-941 -941l-941 941l90 90l787 -787v1798h128v-1798l787 787z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconClosedPath">M515 93l930 931l-930 931l90 90l1022 -1021l-1022 -1021z</StreamGeometry>
<StreamGeometry x:Key="DataGridRowGroupHeaderIconOpenedPath">M1939 1581l90 -90l-1005 -1005l-1005 1005l90 90l915 -915z</StreamGeometry>
<SolidColorBrush x:Key="DataGridGridLinesBrush"
Color="{StaticResource SystemBaseMediumLowColor}"
Opacity="0.4" />
<SolidColorBrush x:Key="DataGridDropLocationIndicatorBackground"
Color="#3F4346" />
<SolidColorBrush x:Key="DataGridDisabledVisualElementBackground"
Color="#8CFFFFFF" />
<SolidColorBrush x:Key="DataGridFillerGridLinesBrush"
Color="Transparent" />
<SolidColorBrush x:Key="DataGridCurrencyVisualPrimaryBrush"
Color="Transparent" />
<StaticResource x:Key="DataGridColumnHeaderBackgroundColor"
ResourceKey="SystemAltHighColor" />
<SolidColorBrush x:Key="DataGridColumnHeaderBackgroundBrush"
Color="{StaticResource DataGridColumnHeaderBackgroundColor}" />
<StaticResource x:Key="DataGridScrollBarsSeparatorBackground"
ResourceKey="SystemChromeLowColor" />
<StaticResource x:Key="DataGridColumnHeaderForegroundBrush"
ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="DataGridColumnHeaderHoveredBackgroundColor"
ResourceKey="SystemListLowColor" />
<StaticResource x:Key="DataGridColumnHeaderPressedBackgroundColor"
ResourceKey="SystemListMediumColor" />
<StaticResource x:Key="DataGridColumnHeaderDraggedBackgroundBrush"
ResourceKey="SystemControlBackgroundChromeMediumLowBrush" />
<StaticResource x:Key="DataGridColumnHeaderPointerOverBrush"
ResourceKey="SystemControlHighlightListLowBrush" />
<StaticResource x:Key="DataGridColumnHeaderPressedBrush"
ResourceKey="SystemControlHighlightListMediumBrush" />
<StaticResource x:Key="DataGridDetailsPresenterBackgroundBrush"
ResourceKey="SystemControlBackgroundChromeMediumLowBrush" />
<StaticResource x:Key="DataGridFillerColumnGridLinesBrush"
ResourceKey="DataGridFillerGridLinesBrush" />
<StaticResource x:Key="DataGridRowSelectedBackgroundColor"
ResourceKey="SystemAccentColor" />
<StaticResource x:Key="DataGridRowSelectedBackgroundOpacity"
ResourceKey="ListAccentLowOpacity" />
<StaticResource x:Key="DataGridRowSelectedHoveredBackgroundColor"
ResourceKey="SystemAccentColor" />
<StaticResource x:Key="DataGridRowSelectedHoveredBackgroundOpacity"
ResourceKey="ListAccentMediumOpacity" />
<StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundColor"
ResourceKey="SystemAccentColor" />
<StaticResource x:Key="DataGridRowSelectedUnfocusedBackgroundOpacity"
ResourceKey="ListAccentLowOpacity" />
<StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundColor"
ResourceKey="SystemAccentColor" />
<StaticResource x:Key="DataGridRowSelectedHoveredUnfocusedBackgroundOpacity"
ResourceKey="ListAccentMediumOpacity" />
<StaticResource x:Key="DataGridRowHeaderForegroundBrush"
ResourceKey="SystemControlForegroundBaseMediumBrush" />
<StaticResource x:Key="DataGridRowHeaderBackgroundBrush"
ResourceKey="SystemControlBackgroundAltHighBrush" />
<StaticResource x:Key="DataGridRowGroupHeaderBackgroundBrush"
ResourceKey="SystemControlBackgroundChromeMediumBrush" />
<StaticResource x:Key="DataGridRowGroupHeaderHoveredBackgroundBrush"
ResourceKey="SystemControlBackgroundListLowBrush" />
<StaticResource x:Key="DataGridRowGroupHeaderPressedBackgroundBrush"
ResourceKey="SystemControlBackgroundListMediumBrush" />
<StaticResource x:Key="DataGridRowGroupHeaderForegroundBrush"
ResourceKey="SystemControlForegroundBaseHighBrush" />
<StaticResource x:Key="DataGridRowInvalidBrush"
ResourceKey="SystemErrorTextColor" />
<StaticResource x:Key="DataGridCellBackgroundBrush"
ResourceKey="SystemControlTransparentBrush" />
<StaticResource x:Key="DataGridCellFocusVisualPrimaryBrush"
ResourceKey="SystemControlFocusVisualPrimaryBrush" />
<StaticResource x:Key="DataGridCellFocusVisualSecondaryBrush"
ResourceKey="SystemControlFocusVisualSecondaryBrush" />
<StaticResource x:Key="DataGridCellInvalidBrush"
ResourceKey="SystemErrorTextColor" />
</Styles.Resources>
<Style Selector="DataGridCell">
<Setter Property="Background" Value="{DynamicResource DataGridCellBackgroundBrush}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Stretch" />
<Setter Property="FontSize" Value="15" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Focusable" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Grid x:Name="PART_CellRoot"
ColumnDefinitions="*,Auto"
Background="{TemplateBinding Background}">
<Rectangle x:Name="CurrencyVisual"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCurrencyVisualPrimaryBrush}"
StrokeThickness="1" />
<Grid x:Name="FocusVisual"
IsHitTestVisible="False">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
<ContentPresenter ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
Margin="{TemplateBinding Padding}"
TextBlock.Foreground="{TemplateBinding Foreground}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
<Rectangle x:Name="InvalidVisualElement"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellInvalidBrush}"
StrokeThickness="1" />
<Rectangle Name="PART_RightGridLine"
Grid.Column="1"
VerticalAlignment="Stretch"
Width="1"
Fill="{DynamicResource DataGridFillerColumnGridLinesBrush}" />
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridCell /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridCell /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridCell:current /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGrid:focus DataGridCell:current /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGridCell /template/ Rectangle#InvalidVisualElement">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridCell:invalid /template/ Rectangle#InvalidVisualElement">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGridColumnHeader">
<Setter Property="Foreground" Value="{DynamicResource DataGridColumnHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderBackgroundBrush}" />
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="VerticalContentAlignment" Value="Center" />
<Setter Property="Focusable" Value="False" />
<Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="Padding" Value="12,0,0,0" />
<Setter Property="FontSize" Value="12" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Template">
<ControlTemplate>
<Grid Name="PART_ColumnHeaderRoot"
ColumnDefinitions="*,Auto"
Background="{TemplateBinding Background}">
<Grid HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"
Margin="{TemplateBinding Padding}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition MinWidth="32"
Width="Auto" />
</Grid.ColumnDefinitions>
<ContentPresenter Content="{TemplateBinding Content}" />
<Path Name="SortIcon"
Grid.Column="1"
Fill="{TemplateBinding Foreground}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Stretch="Uniform"
Height="12" />
</Grid>
<Rectangle Name="VerticalSeparator"
Grid.Column="1"
Width="1"
VerticalAlignment="Stretch"
Fill="{TemplateBinding SeparatorBrush}"
IsVisible="{TemplateBinding AreSeparatorsVisible}" />
<Grid x:Name="FocusVisual"
IsHitTestVisible="False">
<Rectangle x:Name="FocusVisualPrimary"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle x:Name="FocusVisualSecondary"
Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridColumnHeader /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridColumnHeader:focus-visible /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGridColumnHeader:pointerover /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderHoveredBackgroundColor}" />
</Style>
<Style Selector="DataGridColumnHeader:pressed /template/ Grid#PART_ColumnHeaderRoot">
<Setter Property="Background" Value="{DynamicResource DataGridColumnHeaderPressedBackgroundColor}" />
</Style>
<Style Selector="DataGridColumnHeader:dragIndicator">
<Setter Property="Opacity" Value="0.5" />
</Style>
<Style Selector="DataGridColumnHeader /template/ Path#SortIcon">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridColumnHeader:sortascending /template/ Path#SortIcon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Data" Value="{StaticResource DataGridSortIconAscendingPath}" />
</Style>
<Style Selector="DataGridColumnHeader:sortdescending /template/ Path#SortIcon">
<Setter Property="IsVisible" Value="True" />
<Setter Property="Data" Value="{StaticResource DataGridSortIconDescendingPath}" />
</Style>
<Style Selector="DataGridRow">
<Setter Property="Focusable" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<DataGridFrozenGrid Name="PART_Root"
RowDefinitions="*,Auto,Auto"
ColumnDefinitions="Auto,*">
<Rectangle Name="BackgroundRectangle"
Grid.RowSpan="2"
Grid.ColumnSpan="2" />
<Rectangle x:Name="InvalidVisualElement"
Grid.ColumnSpan="2"
Fill="{DynamicResource DataGridRowInvalidBrush}" />
<DataGridRowHeader Name="PART_RowHeader"
Grid.RowSpan="3"
DataGridFrozenGrid.IsFrozen="True" />
<DataGridCellsPresenter Name="PART_CellsPresenter"
Grid.Column="1"
DataGridFrozenGrid.IsFrozen="True" />
<DataGridDetailsPresenter Name="PART_DetailsPresenter"
Grid.Row="1"
Grid.Column="1"
Background="{DynamicResource DataGridDetailsPresenterBackgroundBrush}" />
<Rectangle Name="PART_BottomGridLine"
Grid.Row="2"
Grid.Column="1"
HorizontalAlignment="Stretch"
Height="1" />
</DataGridFrozenGrid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridRow /template/ Rectangle#InvalidVisualElement">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="DataGridRow:invalid /template/ Rectangle#InvalidVisualElement">
<Setter Property="Opacity" Value="0.4" />
</Style>
<Style Selector="DataGridRow:invalid /template/ Rectangle#BackgroundRectangle">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="DataGridRow /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemControlTransparentBrush}" />
</Style>
<Style Selector="DataGridRow:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemListLowColor}" />
</Style>
<Style Selector="DataGridRow:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundColor}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:pointerover /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundColor}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundColor}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:selected:pointerover:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundColor}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRowHeader">
<Setter Property="Background" Value="{DynamicResource DataGridRowHeaderBackgroundBrush}" />
<Setter Property="Focusable" Value="False" />
<Setter Property="SeparatorBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="AreSeparatorsVisible" Value="False" />
<Setter Property="Template">
<ControlTemplate>
<Grid x:Name="PART_Root"
RowDefinitions="*,*,Auto"
ColumnDefinitions="Auto,*">
<Border Grid.RowSpan="3"
Grid.ColumnSpan="2"
BorderBrush="{TemplateBinding SeparatorBrush}"
BorderThickness="0,0,1,0">
<Grid Background="{TemplateBinding Background}">
<Rectangle x:Name="RowInvalidVisualElement"
Fill="{DynamicResource DataGridRowInvalidBrush}"
Stretch="Fill" />
<Rectangle x:Name="BackgroundRectangle"
Stretch="Fill" />
</Grid>
</Border>
<Rectangle x:Name="HorizontalSeparator"
Grid.Row="2"
Grid.ColumnSpan="2"
Height="1"
Margin="1,0,1,0"
HorizontalAlignment="Stretch"
Fill="{TemplateBinding SeparatorBrush}"
IsVisible="{TemplateBinding AreSeparatorsVisible}" />
<ContentPresenter Grid.RowSpan="2"
Grid.Column="1"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Content="{TemplateBinding Content}" />
</Grid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridRowHeader /template/ Rectangle#RowInvalidVisualElement">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="DataGridRowHeader:invalid /template/ Rectangle#RowInvalidVisualElement">
<Setter Property="Opacity" Value="0.4" />
</Style>
<Style Selector="DataGridRowHeader:invalid /template/ Rectangle#BackgroundRectangle">
<Setter Property="Opacity" Value="0" />
</Style>
<Style Selector="DataGridRowHeader /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemControlTransparentBrush}" />
</Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource SystemListLowColor}" />
</Style>
<Style Selector="DataGridRowHeader:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundColor}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader:selected /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundColor}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredUnfocusedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRowHeader:selected:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedBackgroundColor}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRow:pointerover DataGridRowHeader:selected:focus /template/ Rectangle#BackgroundRectangle">
<Setter Property="Fill" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundColor}" />
<Setter Property="Opacity" Value="{DynamicResource DataGridRowSelectedHoveredBackgroundOpacity}" />
</Style>
<Style Selector="DataGridRowGroupHeader">
<Setter Property="Focusable" Value="False" />
<Setter Property="Foreground" Value="{DynamicResource DataGridRowGroupHeaderForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderBackgroundBrush}" />
<Setter Property="FontSize" Value="15" />
<Setter Property="MinHeight" Value="32" />
<Setter Property="Template">
<ControlTemplate>
<DataGridFrozenGrid Name="PART_Root"
MinHeight="{TemplateBinding MinHeight}"
ColumnDefinitions="Auto,Auto,Auto,Auto,*"
RowDefinitions="*,Auto">
<Rectangle Name="IndentSpacer"
Grid.Column="1" />
<ToggleButton Name="ExpanderButton"
Grid.Column="2"
Width="12"
Height="12"
Margin="12,0,0,0"
Background="{TemplateBinding Background}"
Foreground="{TemplateBinding Foreground}"
Focusable="False" />
<StackPanel Grid.Column="3"
Orientation="Horizontal"
VerticalAlignment="Center"
Margin="12,0,0,0">
<TextBlock Name="PropertyNameElement"
Margin="4,0,0,0"
IsVisible="{TemplateBinding IsPropertyNameVisible}"
Foreground="{TemplateBinding Foreground}" />
<TextBlock Margin="4,0,0,0"
Text="{Binding Key}"
Foreground="{TemplateBinding Foreground}" />
<TextBlock Name="ItemCountElement"
Margin="4,0,0,0"
IsVisible="{TemplateBinding IsItemCountVisible}"
Foreground="{TemplateBinding Foreground}" />
</StackPanel>
<Rectangle x:Name="CurrencyVisual"
Grid.ColumnSpan="5"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCurrencyVisualPrimaryBrush}"
StrokeThickness="1" />
<Grid x:Name="FocusVisual"
Grid.ColumnSpan="5"
IsHitTestVisible="False">
<Rectangle HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualPrimaryBrush}"
StrokeThickness="2" />
<Rectangle Margin="2"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
Fill="Transparent"
IsHitTestVisible="False"
Stroke="{DynamicResource DataGridCellFocusVisualSecondaryBrush}"
StrokeThickness="1" />
</Grid>
<DataGridRowHeader Name="PART_RowHeader"
Grid.RowSpan="2"
DataGridFrozenGrid.IsFrozen="True" />
<Rectangle x:Name="PART_BottomGridLine"
Grid.Row="1"
Grid.ColumnSpan="5"
Height="1" />
</DataGridFrozenGrid>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton">
<Setter Property="Template">
<ControlTemplate>
<Border Grid.Column="0"
Width="12"
Height="12"
Background="Transparent"
HorizontalAlignment="Center"
VerticalAlignment="Center">
<Path Fill="{TemplateBinding Foreground}"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Stretch="Uniform" />
</Border>
</ControlTemplate>
</Setter>
</Style>
<Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton /template/ Path">
<Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconOpenedPath}" />
<Setter Property="Stretch" Value="Uniform" />
</Style>
<Style Selector="DataGridRowGroupHeader /template/ ToggleButton#ExpanderButton:checked /template/ Path">
<Setter Property="Data" Value="{StaticResource DataGridRowGroupHeaderIconClosedPath}" />
<Setter Property="Stretch" Value="UniformToFill" />
</Style>
<Style Selector="DataGridRowGroupHeader /template/ DataGridFrozenGrid#PART_Root">
<Setter Property="Background" Value="{Binding $parent[DataGridRowGroupHeader].Background}" />
</Style>
<Style Selector="DataGridRowGroupHeader:pointerover /template/ DataGridFrozenGrid#PART_Root">
<Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderHoveredBackgroundBrush}" />
</Style>
<Style Selector="DataGridRowGroupHeader:pressed /template/ DataGridFrozenGrid#PART_Root">
<Setter Property="Background" Value="{DynamicResource DataGridRowGroupHeaderPressedBackgroundBrush}" />
</Style>
<Style Selector="DataGridRowGroupHeader /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridRowGroupHeader /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="DataGridRowGroupHeader:current /template/ Rectangle#CurrencyVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGrid:focus DataGridRowGroupHeader:current /template/ Grid#FocusVisual">
<Setter Property="IsVisible" Value="True" />
</Style>
<Style Selector="DataGrid">
<Setter Property="RowBackground" Value="Transparent" />
<Setter Property="AlternatingRowBackground" Value="Transparent" />
<Setter Property="HeadersVisibility" Value="Column" />
<Setter Property="HorizontalScrollBarVisibility" Value="Auto" />
<Setter Property="VerticalScrollBarVisibility" Value="Auto" />
<Setter Property="SelectionMode" Value="Extended" />
<Setter Property="GridLinesVisibility" Value="None" />
<Setter Property="HorizontalGridLinesBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="VerticalGridLinesBrush" Value="{DynamicResource DataGridGridLinesBrush}" />
<Setter Property="DropLocationIndicatorTemplate">
<Template>
<Rectangle Fill="{DynamicResource DataGridDropLocationIndicatorBackground}"
Width="2" />
</Template>
</Setter>
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"
BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}">
<Grid RowDefinitions="Auto,*,Auto,Auto"
ColumnDefinitions="Auto,*,Auto">
<Grid.Resources>
<ControlTemplate x:Key="TopLeftHeaderTemplate"
TargetType="DataGridColumnHeader">
<Grid x:Name="TopLeftHeaderRoot"
RowDefinitions="*,*,Auto">
<Border Grid.RowSpan="2"
BorderThickness="0,0,1,0"
BorderBrush="{DynamicResource DataGridGridLinesBrush}" />
<Rectangle Grid.RowSpan="2"
VerticalAlignment="Bottom"
StrokeThickness="1"
Height="1"
Fill="{DynamicResource DataGridGridLinesBrush}" />
</Grid>
</ControlTemplate>
<ControlTemplate x:Key="TopRightHeaderTemplate"
TargetType="DataGridColumnHeader">
<Grid x:Name="RootElement" />
</ControlTemplate>
</Grid.Resources>
<DataGridColumnHeader Name="PART_TopLeftCornerHeader"
Template="{StaticResource TopLeftHeaderTemplate}" />
<DataGridColumnHeadersPresenter Name="PART_ColumnHeadersPresenter"
Grid.Column="1"
Grid.ColumnSpan="2" />
<!--<DataGridColumnHeader Name="PART_TopRightCornerHeader"
Grid.Column="2"
Template="{StaticResource TopRightHeaderTemplate}" />-->
<!--<Rectangle Name="PART_ColumnHeadersAndRowsSeparator"
Grid.ColumnSpan="3"
VerticalAlignment="Bottom"
StrokeThickness="1"
Height="1"
Fill="{DynamicResource DataGridGridLinesBrush}" />-->
<Border Name="PART_ColumnHeadersAndRowsSeparator"
Grid.ColumnSpan="3"
Height="2"
VerticalAlignment="Bottom"
BorderThickness="0,0,0,1"
BorderBrush="{DynamicResource DataGridGridLinesBrush}" />
<DataGridRowsPresenter Name="PART_RowsPresenter"
Grid.Row="1"
Grid.RowSpan="2"
Grid.ColumnSpan="3" />
<Rectangle Name="PART_BottomRightCorner"
Fill="{DynamicResource DataGridScrollBarsSeparatorBackground}"
Grid.Column="2"
Grid.Row="2" />
<!--<Rectangle Name="BottomLeftCorner"
Fill="{DynamicResource DataGridScrollBarsSeparatorBackground}"
Grid.Row="2"
Grid.ColumnSpan="2" />-->
<ScrollBar Name="PART_VerticalScrollbar"
Orientation="Vertical"
Grid.Column="2"
Grid.Row="1"
Width="{DynamicResource ScrollBarSize}" />
<Grid Grid.Column="1"
Grid.Row="2"
ColumnDefinitions="Auto,*">
<Rectangle Name="PART_FrozenColumnScrollBarSpacer" />
<ScrollBar Name="PART_HorizontalScrollbar"
Grid.Column="1"
Orientation="Horizontal"
Height="{DynamicResource ScrollBarSize}" />
</Grid>
<Border x:Name="PART_DisabledVisualElement"
Grid.ColumnSpan="3"
Grid.RowSpan="4"
IsHitTestVisible="False"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
CornerRadius="2"
Background="{DynamicResource DataGridDisabledVisualElementBackground}"
IsVisible="{Binding !$parent[DataGrid].IsEnabled}" />
</Grid>
</Border>
</ControlTemplate>
</Setter>
</Style>
</Styles>

17
src/Avalonia.Controls/AppBuilderBase.cs

@ -88,6 +88,23 @@ namespace Avalonia.Controls
};
}
/// <summary>
/// Begin configuring an <see cref="Application"/>.
/// </summary>
/// <param name="appFactory">Factory function for <typeparamref name="TApp"/>.</param>
/// <typeparam name="TApp">The subclass of <see cref="Application"/> to configure.</typeparam>
/// <remarks><paramref name="appFactory"/> is useful for passing of dependencies to <typeparamref name="TApp"/>.</remarks>
/// <returns>An <typeparamref name="TAppBuilder"/> instance.</returns>
public static TAppBuilder Configure<TApp>(Func<TApp> appFactory)
where TApp : Application
{
return new TAppBuilder()
{
ApplicationType = typeof(TApp),
_appFactory = appFactory
};
}
protected TAppBuilder Self => (TAppBuilder)this;
public TAppBuilder AfterSetup(Action<TAppBuilder> callback)

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

@ -257,15 +257,7 @@ namespace Avalonia.Controls.Presenters
return base.ArrangeOverride(finalSize);
}
try
{
_arranging = true;
return ArrangeWithAnchoring(finalSize);
}
finally
{
_arranging = false;
}
return ArrangeWithAnchoring(finalSize);
}
private Size ArrangeWithAnchoring(Size finalSize)
@ -316,7 +308,17 @@ namespace Avalonia.Controls.Presenters
}
Extent = newExtent;
Offset = newOffset;
try
{
_arranging = true;
Offset = newOffset;
}
finally
{
_arranging = false;
}
ArrangeOverrideImpl(size, -Offset);
}

126
src/Avalonia.Controls/TextBox.cs

@ -18,6 +18,15 @@ namespace Avalonia.Controls
{
public class TextBox : TemplatedControl, UndoRedoHelper<TextBox.UndoRedoState>.IUndoRedoHost
{
public static KeyGesture CutGesture { get; } = AvaloniaLocator.Current
.GetService<PlatformHotkeyConfiguration>()?.Cut.FirstOrDefault();
public static KeyGesture CopyGesture { get; } = AvaloniaLocator.Current
.GetService<PlatformHotkeyConfiguration>()?.Copy.FirstOrDefault();
public static KeyGesture PasteGesture { get; } = AvaloniaLocator.Current
.GetService<PlatformHotkeyConfiguration>()?.Paste.FirstOrDefault();
public static readonly StyledProperty<bool> AcceptsReturnProperty =
AvaloniaProperty.Register<TextBox, bool>(nameof(AcceptsReturn));
@ -103,6 +112,21 @@ namespace Avalonia.Controls
public static readonly StyledProperty<bool> RevealPasswordProperty =
AvaloniaProperty.Register<TextBox, bool>(nameof(RevealPassword));
public static readonly DirectProperty<TextBox, bool> CanCutProperty =
AvaloniaProperty.RegisterDirect<TextBox, bool>(
nameof(CanCut),
o => o.CanCut);
public static readonly DirectProperty<TextBox, bool> CanCopyProperty =
AvaloniaProperty.RegisterDirect<TextBox, bool>(
nameof(CanCopy),
o => o.CanCopy);
public static readonly DirectProperty<TextBox, bool> CanPasteProperty =
AvaloniaProperty.RegisterDirect<TextBox, bool>(
nameof(CanPaste),
o => o.CanPaste);
struct UndoRedoState : IEquatable<UndoRedoState>
{
@ -126,6 +150,9 @@ namespace Avalonia.Controls
private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _isUndoingRedoing;
private bool _ignoreTextChanges;
private bool _canCut;
private bool _canCopy;
private bool _canPaste;
private string _newLine = Environment.NewLine;
private static readonly string[] invalidCharacters = new String[1] { "\u007f" };
@ -369,6 +396,41 @@ namespace Avalonia.Controls
get { return _newLine; }
set { SetAndRaise(NewLineProperty, ref _newLine, value); }
}
/// <summary>
/// Clears the current selection, maintaining the <see cref="CaretIndex"/>
/// </summary>
public void ClearSelection()
{
SelectionStart = SelectionEnd = CaretIndex;
}
/// <summary>
/// Property for determining if the Cut command can be executed.
/// </summary>
public bool CanCut
{
get { return _canCut; }
private set { SetAndRaise(CanCutProperty, ref _canCut, value); }
}
/// <summary>
/// Property for determining if the Copy command can be executed.
/// </summary>
public bool CanCopy
{
get { return _canCopy; }
private set { SetAndRaise(CanCopyProperty, ref _canCopy, value); }
}
/// <summary>
/// Property for determining if the Paste command can be executed.
/// </summary>
public bool CanPaste
{
get { return _canPaste; }
private set { SetAndRaise(CanPasteProperty, ref _canPaste, value); }
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
@ -387,9 +449,19 @@ namespace Avalonia.Controls
if (change.Property == TextProperty)
{
UpdatePseudoclasses();
UpdateCommandStates();
}
}
private void UpdateCommandStates()
{
var text = GetSelection();
var isSelectionNullOrEmpty = string.IsNullOrEmpty(text);
CanCopy = !IsPasswordBox && !isSelectionNullOrEmpty;
CanCut = !IsPasswordBox && !isSelectionNullOrEmpty && !IsReadOnly;
CanPaste = !IsReadOnly;
}
protected override void OnGotFocus(GotFocusEventArgs e)
{
base.OnGotFocus(e);
@ -404,6 +476,8 @@ namespace Avalonia.Controls
SelectAll();
}
UpdateCommandStates();
_presenter?.ShowCaret();
}
@ -413,11 +487,12 @@ namespace Avalonia.Controls
if (ContextMenu == null || !ContextMenu.IsOpen)
{
SelectionStart = 0;
SelectionEnd = 0;
ClearSelection();
RevealPassword = false;
}
UpdateCommandStates();
_presenter?.HideCaret();
}
@ -444,7 +519,7 @@ namespace Avalonia.Controls
text = Text ?? string.Empty;
SetTextInternal(text.Substring(0, caretIndex) + input + text.Substring(caretIndex));
CaretIndex += input.Length;
SelectionStart = SelectionEnd = CaretIndex;
ClearSelection();
_undoRedoHelper.DiscardRedo();
}
}
@ -460,19 +535,31 @@ namespace Avalonia.Controls
return text;
}
private async void Copy()
public async void Cut()
{
var text = GetSelection();
if (text is null) return;
_undoRedoHelper.Snapshot();
Copy();
DeleteSelection();
_undoRedoHelper.Snapshot();
}
public async void Copy()
{
var text = GetSelection();
if (text is null) return;
await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard)))
.SetTextAsync(GetSelection());
.SetTextAsync(text);
}
private async void Paste()
public async void Paste()
{
var text = await ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard))).GetTextAsync();
if (text == null)
{
return;
}
if (text is null) return;
_undoRedoHelper.Snapshot();
HandleTextInput(text);
@ -511,23 +598,18 @@ namespace Avalonia.Controls
{
if (!IsPasswordBox)
{
_undoRedoHelper.Snapshot();
Copy();
DeleteSelection();
_undoRedoHelper.Snapshot();
Cut();
}
handled = true;
}
else if (Match(keymap.Paste))
{
Paste();
handled = true;
}
else if (Match(keymap.Undo))
{
try
{
_isUndoingRedoing = true;
@ -662,7 +744,7 @@ namespace Avalonia.Controls
SetTextInternal(text.Substring(0, caretIndex - removedCharacters) +
text.Substring(caretIndex));
CaretIndex -= removedCharacters;
SelectionStart = SelectionEnd = CaretIndex;
ClearSelection();
}
_undoRedoHelper.Snapshot();
@ -735,7 +817,7 @@ namespace Avalonia.Controls
}
else if (movement)
{
SelectionStart = SelectionEnd = CaretIndex;
ClearSelection();
}
if (handled || movement)
@ -1042,7 +1124,8 @@ namespace Avalonia.Controls
var end = Math.Max(selectionStart, selectionEnd);
var text = Text;
SetTextInternal(text.Substring(0, start) + text.Substring(end));
SelectionStart = SelectionEnd = CaretIndex = start;
CaretIndex = start;
ClearSelection();
return true;
}
else
@ -1131,7 +1214,8 @@ namespace Avalonia.Controls
set
{
Text = value.Text;
SelectionStart = SelectionEnd = CaretIndex = value.CaretPosition;
CaretIndex = value.CaretPosition;
ClearSelection();
}
}
}

2
src/Avalonia.Controls/Utils/BorderRenderHelper.cs

@ -141,7 +141,7 @@ namespace Avalonia.Controls.Utils
var radiusY = keypoints.RightTop.Y - boundRect.TopRight.Y;
if (radiusX != 0 || radiusY != 0)
{
context.ArcTo(keypoints.RightTop, new Size(radiusY, radiusY), 0, false, SweepDirection.Clockwise);
context.ArcTo(keypoints.RightTop, new Size(radiusX, radiusY), 0, false, SweepDirection.Clockwise);
}
// Right

16
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@ -18,12 +18,13 @@ namespace Avalonia.Diagnostics.ViewModels
private int _selectedTab;
private string _focusedControl;
private string _pointerOverElement;
private bool _shouldVisualizeMarginPadding = true;
public MainViewModel(IControl root)
{
_root = root;
_logicalTree = new TreePageViewModel(LogicalTreeNode.Create(root));
_visualTree = new TreePageViewModel(VisualTreeNode.Create(root));
_logicalTree = new TreePageViewModel(this, LogicalTreeNode.Create(root));
_visualTree = new TreePageViewModel(this, VisualTreeNode.Create(root));
_events = new EventsPageViewModel(root);
UpdateFocusedControl();
@ -34,6 +35,17 @@ namespace Avalonia.Diagnostics.ViewModels
Console = new ConsoleViewModel(UpdateConsoleContext);
}
public bool ShouldVisualizeMarginPadding
{
get => _shouldVisualizeMarginPadding;
set => RaiseAndSetIfChanged(ref _shouldVisualizeMarginPadding, value);
}
public void ToggleVisualizeMarginPadding()
{
ShouldVisualizeMarginPadding = !ShouldVisualizeMarginPadding;
}
public ConsoleViewModel Console { get; }
public ViewModelBase Content

8
src/Avalonia.Diagnostics/Diagnostics/ViewModels/TreePageViewModel.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Controls;
using Avalonia.Controls.Selection;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics.ViewModels
@ -10,10 +11,13 @@ namespace Avalonia.Diagnostics.ViewModels
private ControlDetailsViewModel _details;
private string _propertyFilter;
public TreePageViewModel(TreeNode[] nodes)
public TreePageViewModel(MainViewModel mainView, TreeNode[] nodes)
{
MainView = mainView;
Nodes = nodes;
}
}
public MainViewModel MainView { get; }
public TreeNode[] Nodes { get; protected set; }

9
src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml

@ -16,6 +16,15 @@
</MenuItem.Icon>
</MenuItem>
</MenuItem>
<MenuItem Header="_Options">
<MenuItem Header="Visualize margin/padding" Command="{Binding ToggleVisualizeMarginPadding}">
<MenuItem.Icon>
<CheckBox BorderThickness="0"
IsChecked="{Binding ShouldVisualizeMarginPadding}"
IsEnabled="False"/>
</MenuItem.Icon>
</MenuItem>
</MenuItem>
</Menu>
<TabStrip Grid.Row="1" SelectedIndex="{Binding SelectedTab, Mode=TwoWay}">

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

@ -1,7 +1,7 @@
using System.Linq;
using Avalonia.Controls;
using Avalonia.Controls.Generators;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Shapes;
using Avalonia.Diagnostics.ViewModels;
using Avalonia.Input;
using Avalonia.Markup.Xaml;
@ -11,45 +11,78 @@ namespace Avalonia.Diagnostics.Views
{
internal class TreePageView : UserControl
{
private Control _adorner;
private readonly Panel _adorner;
private AdornerLayer _currentLayer;
private TreeView _tree;
public TreePageView()
{
this.InitializeComponent();
InitializeComponent();
_tree.ItemContainerGenerator.Index.Materialized += TreeViewItemMaterialized;
_adorner = new Panel
{
ClipToBounds = false,
Children =
{
//Padding frame
new Border { BorderBrush = new SolidColorBrush(Colors.Green, 0.5) },
//Content frame
new Border { Background = new SolidColorBrush(Color.FromRgb(160, 197, 232), 0.5) },
//Margin frame
new Border { BorderBrush = new SolidColorBrush(Colors.Yellow, 0.5) }
},
};
}
protected void AddAdorner(object sender, PointerEventArgs e)
{
var node = (TreeNode)((Control)sender).DataContext;
var layer = AdornerLayer.GetAdornerLayer(node.Visual);
var visual = (Visual)node.Visual;
_currentLayer = AdornerLayer.GetAdornerLayer(visual);
if (layer != null)
if (_currentLayer == null ||
_currentLayer.Children.Contains(_adorner))
{
if (_adorner != null)
{
((Panel)_adorner.Parent).Children.Remove(_adorner);
_adorner = null;
}
return;
}
_adorner = new Rectangle
{
Fill = new SolidColorBrush(0x80a0c5e8),
[AdornerLayer.AdornedElementProperty] = node.Visual,
};
_currentLayer.Children.Add(_adorner);
AdornerLayer.SetAdornedElement(_adorner, visual);
var vm = (TreePageViewModel) DataContext;
layer.Children.Add(_adorner);
if (vm.MainView.ShouldVisualizeMarginPadding)
{
var paddingBorder = (Border)_adorner.Children[0];
paddingBorder.BorderThickness = visual.GetValue(PaddingProperty);
var contentBorder = (Border)_adorner.Children[1];
contentBorder.Margin = visual.GetValue(PaddingProperty);
var marginBorder = (Border)_adorner.Children[2];
marginBorder.BorderThickness = visual.GetValue(MarginProperty);
marginBorder.Margin = InvertThickness(visual.GetValue(MarginProperty));
}
}
private static Thickness InvertThickness(Thickness input)
{
return new Thickness(-input.Left, -input.Top, -input.Right, -input.Bottom);
}
protected void RemoveAdorner(object sender, PointerEventArgs e)
{
if (_adorner != null)
foreach (var border in _adorner.Children.OfType<Border>())
{
((Panel)_adorner.Parent).Children.Remove(_adorner);
_adorner = null;
border.Margin = default;
border.Padding = default;
border.BorderThickness = default;
}
_currentLayer?.Children.Remove(_adorner);
_currentLayer = null;
}
private void InitializeComponent()

17
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -110,11 +110,20 @@ namespace Avalonia.Native
.Bind<ISystemDialogImpl>().ToConstant(new SystemDialogs(_factory.CreateSystemDialogs()))
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(KeyModifiers.Meta))
.Bind<IMountedVolumeInfoProvider>().ToConstant(new MacOSMountedVolumeInfoProvider())
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory))
;
.Bind<IPlatformDragSource>().ToConstant(new AvaloniaNativeDragSource(_factory));
if (_options.UseGpu)
AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>()
.ToConstant(_glFeature = new GlPlatformFeature(_factory.ObtainGlDisplay()));
{
try
{
AvaloniaLocator.CurrentMutable.Bind<IWindowingPlatformGlFeature>()
.ToConstant(_glFeature = new GlPlatformFeature(_factory.ObtainGlDisplay()));
}
catch (Exception)
{
// ignored
}
}
}
public IWindowImpl CreateWindow()

27
src/Avalonia.Native/ScreenImpl.cs

@ -20,21 +20,26 @@ namespace Avalonia.Native
{
get
{
var count = ScreenCount;
var result = new Screen[count];
for(int i = 0; i < count; i++)
if (_native != null)
{
var screen = _native.GetScreen(i);
var count = ScreenCount;
var result = new Screen[count];
for (int i = 0; i < count; i++)
{
var screen = _native.GetScreen(i);
result[i] = new Screen(
screen.PixelDensity,
screen.Bounds.ToAvaloniaPixelRect(),
screen.WorkingArea.ToAvaloniaPixelRect(),
screen.Primary);
}
result[i] = new Screen(
screen.PixelDensity,
screen.Bounds.ToAvaloniaPixelRect(),
screen.WorkingArea.ToAvaloniaPixelRect(),
screen.Primary);
return result;
}
return result;
return Array.Empty<Screen>();
}
}

15
src/Avalonia.Native/WindowImplBase.cs

@ -94,8 +94,13 @@ namespace Avalonia.Native
{
get
{
var s = _native.GetClientSize();
return new Size(s.Width, s.Height);
if (_native != null)
{
var s = _native.GetClientSize();
return new Size(s.Width, s.Height);
}
return default;
}
}
@ -144,7 +149,6 @@ namespace Avalonia.Native
void IAvnWindowBaseEvents.Closed()
{
var n = _parent._native;
_parent._native = null;
try
{
_parent?.Closed?.Invoke();
@ -153,6 +157,7 @@ namespace Avalonia.Native
{
n?.Dispose();
}
_parent._mouse.Dispose();
}
@ -351,12 +356,12 @@ namespace Avalonia.Native
public Point PointToClient(PixelPoint point)
{
return _native.PointToClient(point.ToAvnPoint()).ToAvaloniaPoint();
return _native?.PointToClient(point.ToAvnPoint()).ToAvaloniaPoint() ?? default;
}
public PixelPoint PointToScreen(Point point)
{
return _native.PointToScreen(point.ToAvnPoint()).ToAvaloniaPixelPoint();
return _native?.PointToScreen(point.ToAvnPoint()).ToAvaloniaPixelPoint() ?? default;
}
public void Hide()

2
src/Avalonia.Themes.Default/Expander.xaml

@ -86,7 +86,7 @@
<Style Selector="Expander /template/ ToggleButton#PART_toggle">
<Setter Property="Template">
<ControlTemplate>
<Border BorderThickness="1">
<Border BorderThickness="1" Background="Transparent">
<Grid ColumnDefinitions="Auto,Auto">
<Border Grid.Column="0" Width="20" Height="20" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Fill="{DynamicResource ThemeForegroundBrush}"

9
src/Avalonia.Themes.Default/TextBox.xaml

@ -1,11 +1,20 @@
<Styles xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Styles.Resources>
<ContextMenu x:Key="DefaultTextBoxContextMenu" x:Name="TextBoxContextMenu">
<MenuItem x:Name="TextBoxContextMenuCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" />
<MenuItem x:Name="TextBoxContextMenuCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/>
<MenuItem x:Name="TextBoxContextMenuPasteItem" Header="Paste" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}" InputGesture="{x:Static TextBox.PasteGesture}"/>
</ContextMenu>
</Styles.Resources>
<Style Selector="TextBox">
<Setter Property="CaretBrush" Value="{DynamicResource ThemeForegroundBrush}" />
<Setter Property="Background" Value="{DynamicResource ThemeBackgroundBrush}"/>
<Setter Property="BorderBrush" Value="{DynamicResource ThemeBorderMidBrush}"/>
<Setter Property="BorderThickness" Value="{DynamicResource ThemeBorderThickness}"/>
<Setter Property="SelectionBrush" Value="{DynamicResource HighlightBrush}"/>
<Setter Property="SelectionForegroundBrush" Value="{DynamicResource HighlightForegroundBrush}"/>
<Setter Property="Padding" Value="4"/>
<Setter Property="ContextMenu" Value="{StaticResource DefaultTextBoxContextMenu}" />
<Setter Property="Template">
<ControlTemplate>
<Border Name="border"

4
src/Avalonia.Themes.Fluent/Expander.xaml

@ -86,10 +86,10 @@
<Style Selector="Expander /template/ ToggleButton#PART_toggle">
<Setter Property="Template">
<ControlTemplate>
<Border BorderThickness="1">
<Border BorderThickness="1" Background="Transparent">
<Grid ColumnDefinitions="Auto,Auto">
<Border Grid.Column="0" Width="20" Height="20" HorizontalAlignment="Center" VerticalAlignment="Center">
<Path Fill="{DynamicResource SystemControlForegroundAltMediumHighBrush}"
<Path Fill="{DynamicResource SystemControlForegroundBaseHighBrush}"
HorizontalAlignment="Center"
VerticalAlignment="Center"
Data="M 0 2 L 4 6 L 0 10 Z" />

6
src/Avalonia.Themes.Fluent/TextBox.xaml

@ -14,6 +14,11 @@
<StreamGeometry x:Key="TextBoxClearButtonData">M 11.416016,10 20,1.4160156 18.583984,0 10,8.5839846 1.4160156,0 0,1.4160156 8.5839844,10 0,18.583985 1.4160156,20 10,11.416015 18.583984,20 20,18.583985 Z</StreamGeometry>
<StreamGeometry x:Key="PasswordBoxRevealButtonData">m10.051 7.0032c2.215 0 4.0105 1.7901 4.0105 3.9984s-1.7956 3.9984-4.0105 3.9984c-2.215 0-4.0105-1.7901-4.0105-3.9984s1.7956-3.9984 4.0105-3.9984zm0 1.4994c-1.3844 0-2.5066 1.1188-2.5066 2.499s1.1222 2.499 2.5066 2.499 2.5066-1.1188 2.5066-2.499-1.1222-2.499-2.5066-2.499zm0-5.0026c4.6257 0 8.6188 3.1487 9.7267 7.5613 0.10085 0.40165-0.14399 0.80877-0.54686 0.90931-0.40288 0.10054-0.81122-0.14355-0.91208-0.54521-0.94136-3.7492-4.3361-6.4261-8.2678-6.4261-3.9334 0-7.3292 2.6792-8.2689 6.4306-0.10063 0.40171-0.50884 0.64603-0.91177 0.54571s-0.648-0.5073-0.54737-0.90901c1.106-4.4152 5.1003-7.5667 9.728-7.5667z</StreamGeometry>
<StreamGeometry x:Key="PasswordBoxHideButtonData">m0.21967 0.21965c-0.26627 0.26627-0.29047 0.68293-0.07262 0.97654l0.07262 0.08412 4.0346 4.0346c-1.922 1.3495-3.3585 3.365-3.9554 5.7495-0.10058 0.4018 0.14362 0.8091 0.54543 0.9097 0.40182 0.1005 0.80909-0.1436 0.90968-0.5455 0.52947-2.1151 1.8371-3.8891 3.5802-5.0341l1.8096 1.8098c-0.70751 0.7215-1.1438 1.71-1.1438 2.8003 0 2.2092 1.7909 4 4 4 1.0904 0 2.0788-0.4363 2.8004-1.1438l5.9193 5.9195c0.2929 0.2929 0.7677 0.2929 1.0606 0 0.2663-0.2662 0.2905-0.6829 0.0726-0.9765l-0.0726-0.0841-6.1135-6.1142 0.0012-0.0015-1.2001-1.1979-2.8699-2.8693 2e-3 -8e-4 -2.8812-2.8782 0.0012-0.0018-1.1333-1.1305-4.3064-4.3058c-0.29289-0.29289-0.76777-0.29289-1.0607 0zm7.9844 9.0458 3.5351 3.5351c-0.45 0.4358-1.0633 0.704-1.7392 0.704-1.3807 0-2.5-1.1193-2.5-2.5 0-0.6759 0.26824-1.2892 0.7041-1.7391zm1.7959-5.7655c-1.0003 0-1.9709 0.14807-2.8889 0.425l1.237 1.2362c0.5358-0.10587 1.0883-0.16119 1.6519-0.16119 3.9231 0 7.3099 2.6803 8.2471 6.4332 0.1004 0.4018 0.5075 0.6462 0.9094 0.5459 0.4019-0.1004 0.6463-0.5075 0.5459-0.9094-1.103-4.417-5.0869-7.5697-9.7024-7.5697zm0.1947 3.5093 3.8013 3.8007c-0.1018-2.0569-1.7488-3.7024-3.8013-3.8007z</StreamGeometry>
<ContextMenu x:Key="DefaultTextBoxContextMenu" x:Name="TextBoxContextMenu">
<MenuItem x:Name="TextBoxContextMenuCutItem" Header="Cut" Command="{Binding $parent[TextBox].Cut}" IsEnabled="{Binding $parent[TextBox].CanCut}" InputGesture="{x:Static TextBox.CutGesture}" />
<MenuItem x:Name="TextBoxContextMenuCopyItem" Header="Copy" Command="{Binding $parent[TextBox].Copy}" IsEnabled="{Binding $parent[TextBox].CanCopy}" InputGesture="{x:Static TextBox.CopyGesture}"/>
<MenuItem x:Name="TextBoxContextMenuPasteItem" Header="Paste" Command="{Binding $parent[TextBox].Paste}" IsEnabled="{Binding $parent[TextBox].CanPaste}" InputGesture="{x:Static TextBox.PasteGesture}"/>
</ContextMenu>
</Styles.Resources>
<Style Selector="TextBox">
@ -28,6 +33,7 @@
<Setter Property="MinWidth" Value="{DynamicResource TextControlThemeMinWidth}" />
<Setter Property="Padding" Value="{DynamicResource TextControlThemePadding}" />
<Setter Property="FocusAdorner" Value="{x:Null}" />
<Setter Property="ContextMenu" Value="{StaticResource DefaultTextBoxContextMenu}" />
<Setter Property="Template">
<ControlTemplate>
<DockPanel>

4
src/Avalonia.Visuals/Media/GlyphRun.cs

@ -555,7 +555,7 @@ namespace Avalonia.Media
}
}
return new Rect(0, 0, width, height);
return new Rect(0, GlyphTypeface.Ascent * Scale, width, height);
}
private void Set<T>(ref T field, T value)
@ -595,8 +595,6 @@ namespace Avalonia.Media
_glyphRunImpl = platformRenderInterface.CreateGlyphRun(this, out var width);
var height = (GlyphTypeface.Descent - GlyphTypeface.Ascent + GlyphTypeface.LineGap) * Scale;
_bounds = new Rect(0, 0, width, height);
}
void IDisposable.Dispose()

16
src/Avalonia.Visuals/Media/TextFormatting/TextLineImpl.cs

@ -181,6 +181,17 @@ namespace Avalonia.Media.TextFormatting
return nextCharacterHit;
}
if (characterHit.FirstCharacterIndex + characterHit.TrailingLength <= TextRange.Start + TextRange.Length)
{
return characterHit; // Can't move, we're after the last character
}
var runIndex = GetRunIndexAtCodepointIndex(TextRange.End);
var textRun = _textRuns[runIndex];
characterHit = textRun.GlyphRun.GetNextCaretCharacterHit(characterHit);
return characterHit; // Can't move, we're after the last character
}
@ -192,6 +203,11 @@ namespace Avalonia.Media.TextFormatting
return previousCharacterHit;
}
if (characterHit.FirstCharacterIndex < TextRange.Start)
{
characterHit = new CharacterHit(TextRange.Start);
}
return characterHit; // Can't move, we're before the first character
}

2
src/Avalonia.Visuals/Rendering/SceneGraph/GlyphRunNode.cs

@ -25,7 +25,7 @@ namespace Avalonia.Rendering.SceneGraph
GlyphRun glyphRun,
Point baselineOrigin,
IDictionary<IVisual, Scene> childScenes = null)
: base(glyphRun.Bounds, transform)
: base(glyphRun.Bounds.Translate(baselineOrigin), transform)
{
Transform = transform;
Foreground = foreground?.ToImmutable();

6
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlDataContextTypeTransformer.cs

@ -24,7 +24,6 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
AvaloniaXamlIlDataContextTypeMetadataNode inferredDataContextTypeNode = null;
AvaloniaXamlIlDataContextTypeMetadataNode directiveDataContextTypeNode = null;
bool isDataTemplate = on.Type.GetClrType().Equals(context.GetAvaloniaTypes().DataTemplate);
for (int i = 0; i < on.Children.Count; ++i)
{
@ -57,7 +56,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
inferredDataContextTypeNode = ParseDataContext(context, on, obj);
}
else if(isDataTemplate
else if(context.GetAvaloniaTypes().DataTemplate.IsAssignableFrom(on.Type.GetClrType())
&& pa.Property.Name == "DataType"
&& pa.Values[0] is XamlTypeExtensionNode dataTypeNode)
{
@ -70,7 +69,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
// do more specialized inference
if (directiveDataContextTypeNode is null)
{
if (isDataTemplate && inferredDataContextTypeNode is null)
if (context.GetAvaloniaTypes().IDataTemplate.IsAssignableFrom(on.Type.GetClrType())
&& inferredDataContextTypeNode is null)
{
// Infer data type from collection binding on a control that displays items.
var parentObject = context.ParentNodes().OfType<XamlAstConstructableObjectNode>().FirstOrDefault();

2
src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs

@ -41,6 +41,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType ResolveByNameExtension { get; }
public IXamlType DataTemplate { get; }
public IXamlType IDataTemplate { get; }
public IXamlType IItemsPresenterHost { get; }
public IXamlType ItemsRepeater { get; }
public IXamlType ReflectionBindingExtension { get; }
@ -98,6 +99,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
CompiledBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindingExtension");
ResolveByNameExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ResolveByNameExtension");
DataTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.DataTemplate");
IDataTemplate = cfg.TypeSystem.GetType("Avalonia.Controls.Templates.IDataTemplate");
IItemsPresenterHost = cfg.TypeSystem.GetType("Avalonia.Controls.Presenters.IItemsPresenterHost");
ItemsRepeater = cfg.TypeSystem.GetType("Avalonia.Controls.ItemsRepeater");
ReflectionBindingExtension = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.ReflectionBindingExtension");

4
src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt

@ -0,0 +1,4 @@
Compat issues with assembly Avalonia.Markup.Xaml:
MembersMustExist : Member 'public Avalonia.Data.Binding Avalonia.Markup.Xaml.Templates.TreeDataTemplate.ItemsSource.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Markup.Xaml.Templates.TreeDataTemplate.ItemsSource.set(Avalonia.Data.Binding)' does not exist in the implementation but it does exist in the contract.
Total Issues: 2

12
src/Markup/Avalonia.Markup.Xaml/Templates/TreeDataTemplate.cs

@ -2,7 +2,9 @@ using System;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Data.Core;
using Avalonia.Markup.Parsers;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Metadata;
namespace Avalonia.Markup.Xaml.Templates
@ -16,7 +18,7 @@ namespace Avalonia.Markup.Xaml.Templates
public object Content { get; set; }
[AssignBinding]
public Binding ItemsSource { get; set; }
public BindingBase ItemsSource { get; set; }
public bool Match(object data)
{
@ -34,7 +36,13 @@ namespace Avalonia.Markup.Xaml.Templates
{
if (ItemsSource != null)
{
var obs = ExpressionObserverBuilder.Build(item, ItemsSource.Path);
var obs = ItemsSource switch
{
Binding reflection => ExpressionObserverBuilder.Build(item, reflection.Path),
CompiledBindingExtension compiled => new ExpressionObserver(item, compiled.Path.BuildExpression(false)),
_ => throw new InvalidOperationException("TreeDataTemplate currently only supports Binding and CompiledBindingExtension!")
};
return InstancedBinding.OneWay(obs, BindingPriority.Style);
}

41
tests/Avalonia.Controls.UnitTests/AppBuilderTests.cs

@ -1,4 +1,5 @@
using Avalonia.Controls.UnitTests;
using System;
using Avalonia.Controls.UnitTests;
using Avalonia.Platform;
using Xunit;
@ -18,6 +19,19 @@ namespace Avalonia.Controls.UnitTests
{
}
public class AppWithDependencies : Application
{
public AppWithDependencies(object dependencyA, object dependencyB)
{
DependencyA = dependencyA;
DependencyB = dependencyB;
}
public object DependencyA { get; }
public object DependencyB { get; }
}
public class DefaultModule
{
public static bool IsLoaded = false;
@ -53,7 +67,30 @@ namespace Avalonia.Controls.UnitTests
IsLoaded = true;
}
}
[Fact]
public void UseAppFactory()
{
using (AvaloniaLocator.EnterScope())
{
ResetModuleLoadStates();
Func<AppWithDependencies> appFactory = () => new AppWithDependencies(dependencyA: new object(), dependencyB: new object());
var builder = AppBuilder.Configure<AppWithDependencies>(appFactory)
.UseWindowingSubsystem(() => { })
.UseRenderingSubsystem(() => { })
.UseAvaloniaModules()
.SetupWithoutStarting();
AppWithDependencies app = (AppWithDependencies)builder.Instance;
Assert.NotNull(app.DependencyA);
Assert.NotNull(app.DependencyB);
Assert.True(DefaultModule.IsLoaded);
}
}
[Fact]
public void LoadsDefaultModule()
{

35
tests/Avalonia.Controls.UnitTests/TextBoxTests.cs

@ -562,6 +562,41 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void TextBox_CaretIndex_Persists_When_Focus_Lost()
{
using (UnitTestApplication.Start(FocusServices))
{
var target1 = new TextBox
{
Template = CreateTemplate(),
Text = "1234"
};
var target2 = new TextBox
{
Template = CreateTemplate(),
Text = "5678"
};
var sp = new StackPanel();
sp.Children.Add(target1);
sp.Children.Add(target2);
target1.ApplyTemplate();
target2.ApplyTemplate();
var root = new TestRoot { Child = sp };
target2.Focus();
target2.CaretIndex = 2;
Assert.False(target1.IsFocused);
Assert.True(target2.IsFocused);
target1.Focus();
Assert.Equal(2, target2.CaretIndex);
}
}
[Fact]
public void TextBox_Reveal_Password_Reset_When_Lost_Focus()
{

26
tests/Avalonia.LeakTests/ControlTests.cs

@ -449,13 +449,22 @@ namespace Avalonia.LeakTests
Assert.Same(window, FocusManager.Instance.Current);
// Context menu in resources means the baseline may not be 0.
var initialMenuCount = 0;
var initialMenuItemCount = 0;
dotMemory.Check(memory =>
{
initialMenuCount = memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount;
initialMenuItemCount = memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount;
});
AttachShowAndDetachContextMenu(window);
Mock.Get(window.PlatformImpl).ResetCalls();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
Assert.Equal(initialMenuItemCount, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
}
}
@ -484,14 +493,23 @@ namespace Avalonia.LeakTests
Assert.Same(window, FocusManager.Instance.Current);
// Context menu in resources means the baseline may not be 0.
var initialMenuCount = 0;
var initialMenuItemCount = 0;
dotMemory.Check(memory =>
{
initialMenuCount = memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount;
initialMenuItemCount = memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount;
});
BuildAndShowContextMenu(window);
BuildAndShowContextMenu(window);
Mock.Get(window.PlatformImpl).ResetCalls();
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
Assert.Equal(initialMenuCount, memory.GetObjects(where => where.Type.Is<ContextMenu>()).ObjectsCount));
dotMemory.Check(memory =>
Assert.Equal(0, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
Assert.Equal(initialMenuItemCount, memory.GetObjects(where => where.Type.Is<MenuItem>()).ObjectsCount));
}
}

144
tests/Avalonia.Markup.Xaml.UnitTests/Data/BindingTests_Method.cs

@ -1,5 +1,5 @@
using System.Reactive.Subjects;
using System.Windows.Input;
using System;
using System.ComponentModel;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.UnitTests;
@ -56,7 +56,7 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
Assert.Equal("Called 5", vm.Value);
}
}
[Fact]
public void Binding_Method_To_TextBlock_Text_Works()
{
@ -79,6 +79,111 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
}
}
[Theory]
[InlineData(null, "Not called")]
[InlineData("A", "Do A")]
public void Binding_Method_With_Parameter_To_Command_CanExecute(object commandParameter, string result)
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<Button Name='button' Command='{Binding Do}' CommandParameter='{Binding Parameter, Mode=OneTime}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
var vm = new ViewModel()
{
Parameter = commandParameter
};
button.DataContext = vm;
window.ApplyTemplate();
Assert.NotNull(button.Command);
PerformClick(button);
Assert.Equal(vm.Value, result);
}
}
[Fact]
public void Binding_Method_With_Parameter_To_Command_CanExecute_DependsOn()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml'
xmlns:local='clr-namespace:Avalonia.Markup.Xaml.UnitTests.Xaml;assembly=Avalonia.Markup.Xaml.UnitTests'>
<Button Name='button' Command='{Binding Do}' CommandParameter='{Binding Parameter, Mode=OneWay}'/>
</Window>";
var window = (Window)AvaloniaRuntimeXamlLoader.Load(xaml);
var button = window.FindControl<Button>("button");
var vm = new ViewModel()
{
Parameter = null,
};
button.DataContext = vm;
window.ApplyTemplate();
Assert.NotNull(button.Command);
Assert.Equal(button.IsEffectivelyEnabled, false);
vm.Parameter = true;
Threading.Dispatcher.UIThread.RunJobs();
Assert.Equal(button.IsEffectivelyEnabled, true);
}
}
[Fact]
public void Binding_Method_To_Command_Collected()
{
WeakReference<ViewModel> MakeRef()
{
var weakVm = new WeakReference<ViewModel>(null);
{
var vm = new ViewModel()
{
Parameter = null,
};
weakVm.SetTarget(vm);
var canExecuteCount = 0;
var action = new Action<object>(vm.Do);
var command = new Avalonia.Data.Converters.MethodToCommandConverter(action);
command.CanExecuteChanged += (s, e) => canExecuteCount++;
vm.Parameter = 0;
Threading.Dispatcher.UIThread.RunJobs();
vm.Parameter = null;
Threading.Dispatcher.UIThread.RunJobs();
Assert.Equal(2, canExecuteCount);
}
return weakVm;
}
bool IsAlive(WeakReference<ViewModel> @ref)
{
return @ref.TryGetTarget(out var instance)
&& instance is null == false;
}
var vmref = MakeRef();
var beforeCollect = IsAlive(vmref);
GC.Collect();
GC.WaitForPendingFinalizers();
var afterCollect = IsAlive(vmref);
Assert.True(beforeCollect, "Invalid ViewModel instance, it is already collected.");
Assert.False(afterCollect, "ViewModel instance was not collected");
}
static void PerformClick(Button button)
{
button.RaiseEvent(new KeyEventArgs
@ -88,12 +193,43 @@ namespace Avalonia.Markup.Xaml.UnitTests.Data
});
}
private class ViewModel
private class ViewModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public string Method() => Value = "Called";
public string Method1(int i) => Value = $"Called {i}";
public string Method2(int i, int j) => Value = $"Called {i},{j}";
public string Value { get; private set; } = "Not called";
object _parameter;
public object Parameter
{
get
{
return _parameter;
}
set
{
if (_parameter == value)
{
return;
}
_parameter = value;
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Parameter)));
}
}
public void Do(object parameter)
{
Value = $"Do {parameter}";
}
[Metadata.DependsOn(nameof(Parameter))]
public bool CanDo(object parameter)
{
return ReferenceEquals(null, parameter) == false;
}
}
}
}

60
tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
@ -10,6 +9,65 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
{
public class TextLineTests
{
private static readonly string s_multiLineText = "012345678\r\r0123456789";
[Fact]
public void Should_Get_First_CharacterHit()
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new SingleBufferTextSource(s_multiLineText, defaultProperties);
var formatter = new TextFormatterImpl();
var currentIndex = 0;
while (currentIndex < s_multiLineText.Length)
{
var textLine =
formatter.FormatLine(textSource, currentIndex, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var firstCharacterHit = textLine.GetPreviousCaretCharacterHit(new CharacterHit(int.MinValue));
Assert.Equal(textLine.TextRange.Start, firstCharacterHit.FirstCharacterIndex);
currentIndex += textLine.TextRange.Length;
}
}
}
[Fact]
public void Should_Get_Last_CharacterHit()
{
using (Start())
{
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var textSource = new SingleBufferTextSource(s_multiLineText, defaultProperties);
var formatter = new TextFormatterImpl();
var currentIndex = 0;
while (currentIndex < s_multiLineText.Length)
{
var textLine =
formatter.FormatLine(textSource, currentIndex, double.PositiveInfinity,
new GenericTextParagraphProperties(defaultProperties));
var lastCharacterHit = textLine.GetNextCaretCharacterHit(new CharacterHit(int.MaxValue));
Assert.Equal(textLine.TextRange.Start + textLine.TextRange.Length,
lastCharacterHit.FirstCharacterIndex + lastCharacterHit.TrailingLength);
currentIndex += textLine.TextRange.Length;
}
}
}
[InlineData("𐐷𐐷𐐷𐐷𐐷")]
[InlineData("𐐷1234")]
[Theory]

Loading…
Cancel
Save