Browse Source

Merge branch 'master' into fixes/selectableTextBlock

pull/10821/head
Tim 4 years ago
committed by GitHub
parent
commit
def3b74217
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      samples/ControlCatalog/Pages/ClipboardPage.xaml.cs
  2. 2
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  3. 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  4. 30
      src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
  5. 19
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  6. 6
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  7. 7
      src/Avalonia.Controls/Application.cs
  8. 12
      src/Avalonia.Controls/ItemsControl.cs
  9. 2
      src/Avalonia.Controls/MaskedTextBox.cs
  10. 16
      src/Avalonia.Controls/Primitives/HeaderedItemsControl.cs
  11. 63
      src/Avalonia.Controls/Primitives/HeaderedSelectingItemsControl.cs
  12. 7
      src/Avalonia.Controls/SelectableTextBlock.cs
  13. 23
      src/Avalonia.Controls/TextBox.cs
  14. 8
      src/Avalonia.Controls/TopLevel.cs
  15. 6
      src/Avalonia.Controls/TreeView.cs
  16. 1
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  17. 6
      src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs
  18. 3
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs
  19. 8
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ControlDetailsViewModel.cs
  20. 5
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs
  21. 12
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs
  22. 10
      src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj
  23. 21
      src/Avalonia.FreeDesktop/DBusMenuExporter.cs
  24. 13
      src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs
  25. 8
      src/Avalonia.Headless/HeadlessWindowImpl.cs
  26. 6
      src/Avalonia.Native/WindowImplBase.cs
  27. 1
      src/Avalonia.Themes.Fluent/Controls/Menu.xaml
  28. 2
      src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
  29. 3
      src/Avalonia.Themes.Simple/Controls/Menu.xaml
  30. 2
      src/Avalonia.Themes.Simple/Controls/MenuItem.xaml
  31. 6
      src/Avalonia.X11/X11Window.cs
  32. 9
      src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs
  33. 1
      src/Browser/Avalonia.Browser/WindowingPlatform.cs
  34. 2
      src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
  35. 6
      src/Windows/Avalonia.Win32/WindowImpl.cs
  36. 9
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  37. 1
      src/iOS/Avalonia.iOS/Platform.cs
  38. 29
      src/tools/DevGenerators/CompositionGenerator/Generator.cs
  39. 69
      tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs
  40. 45
      tests/Avalonia.Controls.UnitTests/MenuItemTests.cs
  41. 83
      tests/Avalonia.Controls.UnitTests/TextBoxTests.cs
  42. 2
      tests/Avalonia.Controls.UnitTests/TopLevelTests.cs
  43. 29
      tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

16
samples/ControlCatalog/Pages/ClipboardPage.xaml.cs

@ -32,13 +32,13 @@ namespace ControlCatalog.Pages
private async void CopyText(object? sender, RoutedEventArgs args)
{
if (Application.Current!.Clipboard is { } clipboard && ClipboardContent is { } clipboardContent)
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard && ClipboardContent is { } clipboardContent)
await clipboard.SetTextAsync(clipboardContent.Text ?? String.Empty);
}
private async void PasteText(object? sender, RoutedEventArgs args)
{
if (Application.Current!.Clipboard is { } clipboard)
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{
ClipboardContent.Text = await clipboard.GetTextAsync();
}
@ -46,7 +46,7 @@ namespace ControlCatalog.Pages
private async void CopyTextDataObject(object? sender, RoutedEventArgs args)
{
if (Application.Current!.Clipboard is { } clipboard)
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{
var dataObject = new DataObject();
dataObject.Set(DataFormats.Text, ClipboardContent.Text ?? string.Empty);
@ -56,7 +56,7 @@ namespace ControlCatalog.Pages
private async void PasteTextDataObject(object? sender, RoutedEventArgs args)
{
if (Application.Current!.Clipboard is { } clipboard)
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{
ClipboardContent.Text = await clipboard.GetDataAsync(DataFormats.Text) as string ?? string.Empty;
}
@ -64,7 +64,7 @@ namespace ControlCatalog.Pages
private async void CopyFilesDataObject(object? sender, RoutedEventArgs args)
{
if (Application.Current!.Clipboard is { } clipboard)
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{
var storageProvider = TopLevel.GetTopLevel(this)!.StorageProvider;
var filesPath = (ClipboardContent.Text ?? string.Empty)
@ -110,7 +110,7 @@ namespace ControlCatalog.Pages
private async void PasteFilesDataObject(object? sender, RoutedEventArgs args)
{
if (Application.Current!.Clipboard is { } clipboard)
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{
var files = await clipboard.GetDataAsync(DataFormats.Files) as IEnumerable<Avalonia.Platform.Storage.IStorageItem>;
@ -120,7 +120,7 @@ namespace ControlCatalog.Pages
private async void GetFormats(object sender, RoutedEventArgs args)
{
if (Application.Current!.Clipboard is { } clipboard)
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{
var formats = await clipboard.GetFormatsAsync();
ClipboardContent.Text = string.Join(Environment.NewLine, formats);
@ -129,7 +129,7 @@ namespace ControlCatalog.Pages
private async void Clear(object sender, RoutedEventArgs args)
{
if (Application.Current!.Clipboard is { } clipboard)
if (TopLevel.GetTopLevel(this)?.Clipboard is { } clipboard)
{
await clipboard.ClearAsync();
}

2
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -5,9 +5,7 @@ using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls.Presenters;
using Avalonia.Input;
using Avalonia.Input.TextInput;
using Avalonia.Reactive;
namespace Avalonia.Android
{

1
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -38,7 +38,6 @@ namespace Avalonia.Android
Options = AvaloniaLocator.Current.GetService<AndroidPlatformOptions>() ?? new AndroidPlatformOptions();
AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToTransient<ClipboardImpl>()
.Bind<ICursorFactory>().ToTransient<CursorFactory>()
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
.Bind<IKeyboardDevice>().ToSingleton<AndroidKeyboardDevice>()

30
src/Android/Avalonia.Android/Platform/ClipboardImpl.cs

@ -2,32 +2,26 @@ using System;
using System.Threading.Tasks;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;
namespace Avalonia.Android.Platform
{
internal class ClipboardImpl : IClipboard
{
private Context context = (AvaloniaLocator.Current.GetService<IWindowImpl>() as View).Context;
private ClipboardManager? _clipboardManager;
private ClipboardManager ClipboardManager
internal ClipboardImpl(ClipboardManager? value)
{
get
{
return this.context.GetSystemService(Context.ClipboardService).JavaCast<ClipboardManager>();
}
_clipboardManager = value;
}
public Task<string> GetTextAsync()
{
if (ClipboardManager.HasPrimaryClip)
if (_clipboardManager?.HasPrimaryClip == true)
{
return Task.FromResult<string>(ClipboardManager.PrimaryClip.GetItemAt(0).Text);
return Task.FromResult<string>(_clipboardManager.PrimaryClip.GetItemAt(0).Text);
}
return Task.FromResult<string>(null);
@ -35,15 +29,25 @@ namespace Avalonia.Android.Platform
public Task SetTextAsync(string text)
{
if(_clipboardManager == null)
{
return Task.CompletedTask;
}
ClipData clip = ClipData.NewPlainText("text", text);
ClipboardManager.PrimaryClip = clip;
_clipboardManager.PrimaryClip = clip;
return Task.FromResult<object>(null);
}
public Task ClearAsync()
{
ClipboardManager.PrimaryClip = null;
if (_clipboardManager == null)
{
return Task.CompletedTask;
}
_clipboardManager.PrimaryClip = null;
return Task.FromResult<object>(null);
}

19
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -3,7 +3,10 @@ using System.Collections.Generic;
using Android.App;
using Android.Content;
using Android.Graphics;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Runtime;
using Android.Text;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.Specific;
@ -13,6 +16,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.OpenGL.Egl;
@ -22,13 +26,7 @@ using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Java.Lang;
using Java.Util;
using Math = System.Math;
using AndroidRect = Android.Graphics.Rect;
using Window = Android.Views.Window;
using Android.Graphics.Drawables;
using Android.OS;
using Android.Text;
using ClipboardManager = Android.Content.ClipboardManager;
namespace Avalonia.Android.Platform.SkiaPlatform
{
@ -44,6 +42,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
private readonly IStorageProvider _storageProvider;
private readonly ISystemNavigationManagerImpl _systemNavigationManager;
private readonly AndroidInsetsManager _insetsManager;
private readonly ClipboardImpl _clipboard;
private ViewImpl _view;
public TopLevelImpl(AvaloniaView avaloniaView, bool placeOnTop = false)
@ -54,6 +53,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_pointerHelper = new AndroidMotionEventsHelper(this);
_gl = new EglGlPlatformSurface(this);
_framebuffer = new FramebufferManager(this);
_clipboard = new ClipboardImpl(avaloniaView.Context?.GetSystemService(Context.ClipboardService).JavaCast<ClipboardManager>());
RenderScaling = _view.Scaling;
@ -408,6 +408,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
return _insetsManager;
}
if(featureType == typeof(IClipboard))
{
return _clipboard;
}
return null;
}
}

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

@ -6133,8 +6133,10 @@ namespace Avalonia.Controls
private async void CopyToClipboard(string text)
{
var clipboard = ((IClipboard)AvaloniaLocator.Current.GetService(typeof(IClipboard)));
await clipboard.SetTextAsync(text);
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard != null)
await clipboard.SetTextAsync(text);
}
/// <summary>

7
src/Avalonia.Controls/Application.cs

@ -36,8 +36,6 @@ namespace Avalonia
/// </summary>
private DataTemplates? _dataTemplates;
private readonly Lazy<IClipboard?> _clipboard =
new Lazy<IClipboard?>(() => (IClipboard?)AvaloniaLocator.Current.GetService(typeof(IClipboard)));
private Styles? _styles;
private IResourceDictionary? _resources;
private bool _notifyingResourcesChanged;
@ -141,11 +139,6 @@ namespace Avalonia
private set;
}
/// <summary>
/// Gets the application clipboard.
/// </summary>
public IClipboard? Clipboard => _clipboard.Value;
/// <summary>
/// Gets the application's global resource dictionary.
/// </summary>

12
src/Avalonia.Controls/ItemsControl.cs

@ -460,13 +460,19 @@ namespace Avalonia.Controls
ic.ItemContainerTheme = ict;
}
// This condition is separate because HeaderedItemsControl needs to also run the
// ItemsControl preparation.
// These conditions are separate because HeaderedItemsControl and
// HeaderedSelectingItemsControl also need to run the ItemsControl preparation.
if (container is HeaderedItemsControl hic)
{
hic.Header = item;
hic.HeaderTemplate = itemTemplate;
hic.PrepareItemContainer();
hic.PrepareItemContainer(this);
}
else if (container is HeaderedSelectingItemsControl hsic)
{
hsic.Header = item;
hsic.HeaderTemplate = itemTemplate;
hsic.PrepareItemContainer(this);
}
}

2
src/Avalonia.Controls/MaskedTextBox.cs

@ -210,7 +210,7 @@ namespace Avalonia.Controls
if (keymap is not null && Match(keymap.Paste))
{
var clipboard = (IClipboard?)AvaloniaLocator.Current.GetService(typeof(IClipboard));
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard is null)
return;

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

@ -13,7 +13,7 @@ namespace Avalonia.Controls.Primitives
public class HeaderedItemsControl : ItemsControl, IContentPresenterHost
{
private IDisposable? _itemsBinding;
private bool _prepareItemContainerOnAttach;
private ItemsControl? _prepareItemContainerOnAttach;
/// <summary>
/// Defines the <see cref="Header"/> property.
@ -69,10 +69,10 @@ namespace Avalonia.Controls.Primitives
{
base.OnAttachedToLogicalTree(e);
if (_prepareItemContainerOnAttach)
if (_prepareItemContainerOnAttach is not null)
{
PrepareItemContainer();
_prepareItemContainerOnAttach = false;
PrepareItemContainer(_prepareItemContainerOnAttach);
_prepareItemContainerOnAttach = null;
}
}
@ -97,7 +97,7 @@ namespace Avalonia.Controls.Primitives
return false;
}
internal void PrepareItemContainer()
internal void PrepareItemContainer(ItemsControl parent)
{
_itemsBinding?.Dispose();
_itemsBinding = null;
@ -106,18 +106,18 @@ namespace Avalonia.Controls.Primitives
if (item is null)
{
_prepareItemContainerOnAttach = false;
_prepareItemContainerOnAttach = null;
return;
}
var headerTemplate = HeaderTemplate;
var headerTemplate = HeaderTemplate ?? parent.ItemTemplate;
if (headerTemplate is null)
{
if (((ILogical)this).IsAttachedToLogicalTree)
headerTemplate = this.FindDataTemplate(item);
else
_prepareItemContainerOnAttach = true;
_prepareItemContainerOnAttach = parent;
}
if (headerTemplate is ITreeDataTemplate treeTemplate &&

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

@ -1,5 +1,8 @@
using System;
using Avalonia.Collections;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.LogicalTree;
namespace Avalonia.Controls.Primitives
@ -9,12 +12,21 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public class HeaderedSelectingItemsControl : SelectingItemsControl, IContentPresenterHost
{
private IDisposable? _itemsBinding;
private ItemsControl? _prepareItemContainerOnAttach;
/// <summary>
/// Defines the <see cref="Header"/> property.
/// </summary>
public static readonly StyledProperty<object?> HeaderProperty =
HeaderedContentControl.HeaderProperty.AddOwner<HeaderedSelectingItemsControl>();
/// <summary>
/// Defines the <see cref="HeaderTemplate"/> property.
/// </summary>
public static readonly StyledProperty<IDataTemplate?> HeaderTemplateProperty =
HeaderedItemsControl.HeaderTemplateProperty.AddOwner<HeaderedSelectingItemsControl>();
/// <summary>
/// Initializes static members of the <see cref="ContentControl"/> class.
/// </summary>
@ -32,6 +44,15 @@ namespace Avalonia.Controls.Primitives
set { SetValue(HeaderProperty, value); }
}
/// <summary>
/// Gets or sets the data template used to display the header content of the control.
/// </summary>
public IDataTemplate? HeaderTemplate
{
get => GetValue(HeaderTemplateProperty);
set => SetValue(HeaderTemplateProperty, value);
}
/// <summary>
/// Gets the header presenter from the control's template.
/// </summary>
@ -50,6 +71,17 @@ namespace Avalonia.Controls.Primitives
return RegisterContentPresenter(presenter);
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
if (_prepareItemContainerOnAttach is not null)
{
PrepareItemContainer(_prepareItemContainerOnAttach);
_prepareItemContainerOnAttach = null;
}
}
/// <summary>
/// Called when an <see cref="IContentPresenter"/> is registered with the control.
/// </summary>
@ -65,6 +97,37 @@ namespace Avalonia.Controls.Primitives
return false;
}
internal void PrepareItemContainer(ItemsControl parent)
{
_itemsBinding?.Dispose();
_itemsBinding = null;
var item = Header;
if (item is null)
{
_prepareItemContainerOnAttach = null;
return;
}
var headerTemplate = HeaderTemplate ?? parent.ItemTemplate;
if (headerTemplate is null)
{
if (((ILogical)this).IsAttachedToLogicalTree)
headerTemplate = this.FindDataTemplate(item);
else
_prepareItemContainerOnAttach = parent;
}
if (headerTemplate is ITreeDataTemplate treeTemplate &&
treeTemplate.Match(item) &&
treeTemplate.ItemsSelector(item) is { } itemsBinding)
{
_itemsBinding = BindingOperations.Apply(this, ItemsSourceProperty, itemsBinding, null);
}
}
private void HeaderChanged(AvaloniaPropertyChangedEventArgs e)
{
if (e.OldValue is ILogical oldChild)

7
src/Avalonia.Controls/SelectableTextBlock.cs

@ -8,6 +8,7 @@ using Avalonia.Input.Platform;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Media.TextFormatting;
using Avalonia.Platform;
using Avalonia.Utilities;
namespace Avalonia.Controls
@ -120,8 +121,10 @@ namespace Avalonia.Controls
if (!eventArgs.Handled)
{
await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard)))
.SetTextAsync(text);
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard != null)
await clipboard.SetTextAsync(text);
}
}

23
src/Avalonia.Controls/TextBox.cs

@ -18,6 +18,7 @@ using Avalonia.Media.TextFormatting;
using Avalonia.Media.TextFormatting.Unicode;
using Avalonia.Automation.Peers;
using Avalonia.Threading;
using Avalonia.Platform;
namespace Avalonia.Controls
{
@ -1044,8 +1045,13 @@ namespace Avalonia.Controls
if (!eventArgs.Handled)
{
SnapshotUndoRedo();
await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard)))
.SetTextAsync(text);
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard == null)
return;
await clipboard.SetTextAsync(text);
DeleteSelection();
}
}
@ -1066,8 +1072,10 @@ namespace Avalonia.Controls
RaiseEvent(eventArgs);
if (!eventArgs.Handled)
{
await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard)))
.SetTextAsync(text);
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard != null)
await clipboard.SetTextAsync(text);
}
}
@ -1083,7 +1091,12 @@ namespace Avalonia.Controls
return;
}
var text = await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard))).GetTextAsync();
string? text = null;
var clipboard = TopLevel.GetTopLevel(this)?.Clipboard;
if (clipboard != null)
text = await clipboard.GetTextAsync();
if (string.IsNullOrEmpty(text))
{

8
src/Avalonia.Controls/TopLevel.cs

@ -394,6 +394,11 @@ namespace Avalonia.Controls
public IInsetsManager? InsetsManager => PlatformImpl?.TryGetFeature<IInsetsManager>();
/// <summary>
/// Gets the platform's clipboard implementation
/// </summary>
public IClipboard? Clipboard => PlatformImpl?.TryGetFeature<IClipboard>();
/// <inheritdoc/>
Point IRenderRoot.PointToClient(PixelPoint p)
{
@ -454,7 +459,8 @@ namespace Avalonia.Controls
}
else if (change.Property == ActualThemeVariantProperty)
{
PlatformImpl?.SetFrameThemeVariant((PlatformThemeVariant?)change.GetNewValue<ThemeVariant>() ?? PlatformThemeVariant.Light);
var newThemeVariant = change.GetNewValue<ThemeVariant?>() ?? ThemeVariant.Default;
PlatformImpl?.SetFrameThemeVariant((PlatformThemeVariant?)newThemeVariant ?? PlatformThemeVariant.Light);
}
}

6
src/Avalonia.Controls/TreeView.cs

@ -307,12 +307,14 @@ namespace Avalonia.Controls
private void SelectSingleItem(object item)
{
var oldValue = _selectedItem;
_syncingSelectedItems = true;
SelectedItems.Clear();
SelectedItems.Clear();
_selectedItem = item;
SelectedItems.Add(item);
_syncingSelectedItems = false;
SetAndRaise(SelectedItemProperty, ref _selectedItem, item);
RaisePropertyChanged(SelectedItemProperty, oldValue, _selectedItem);
}
/// <summary>

1
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -48,7 +48,6 @@ namespace Avalonia.DesignerSupport.Remote
s_transport = transport;
var instance = new PreviewerWindowingPlatform();
AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToSingleton<ClipboardStub>()
.Bind<ICursorFactory>().ToSingleton<CursorFactoryStub>()
.Bind<IKeyboardDevice>().ToConstant(Keyboard)
.Bind<IPlatformSettings>().ToSingleton<DefaultPlatformSettings>()

6
src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs

@ -76,12 +76,6 @@ namespace Avalonia.Diagnostics.Controls
public Input.InputManager? InputManager =>
_application.InputManager;
/// <summary>
/// Gets the application clipboard.
/// </summary>
public Input.Platform.IClipboard? Clipboard =>
_application.Clipboard;
/// <summary>
/// Gets the application's global resource dictionary.
/// </summary>

3
src/Avalonia.Diagnostics/Diagnostics/ViewModels/BindingSetterViewModel.cs

@ -1,5 +1,6 @@
using System;
using Avalonia.Data;
using Avalonia.Input.Platform;
using Avalonia.Markup.Xaml.MarkupExtensions;
using Avalonia.Media;
@ -7,7 +8,7 @@ namespace Avalonia.Diagnostics.ViewModels
{
internal class BindingSetterViewModel : SetterViewModel
{
public BindingSetterViewModel(AvaloniaProperty property, object? value) : base(property, value)
public BindingSetterViewModel(AvaloniaProperty property, object? value, IClipboard? clipboard) : base(property, value, clipboard)
{
switch (value)
{

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

@ -59,6 +59,8 @@ namespace Avalonia.Diagnostics.ViewModels
var styleDiagnostics = styledElement.GetStyleDiagnostics();
var clipboard = TopLevel.GetTopLevel(_avaloniaObject as Visual)?.Clipboard;
// We need to place styles without activator first, such styles will be overwritten by ones with activators.
foreach (var appliedStyle in styleDiagnostics.AppliedStyles.OrderBy(s => s.HasActivator))
{
@ -91,7 +93,7 @@ namespace Avalonia.Diagnostics.ViewModels
var resourceKey = resourceInfo.Value.resourceKey;
var resourceValue = styledElement.FindResource(resourceKey);
setterVm = new ResourceSetterViewModel(regularSetter.Property, resourceKey, resourceValue, resourceInfo.Value.isDynamic);
setterVm = new ResourceSetterViewModel(regularSetter.Property, resourceKey, resourceValue, resourceInfo.Value.isDynamic, clipboard);
}
else
{
@ -99,11 +101,11 @@ namespace Avalonia.Diagnostics.ViewModels
if (isBinding)
{
setterVm = new BindingSetterViewModel(regularSetter.Property, setterValue);
setterVm = new BindingSetterViewModel(regularSetter.Property, setterValue, clipboard);
}
else
{
setterVm = new SetterViewModel(regularSetter.Property, setterValue);
setterVm = new SetterViewModel(regularSetter.Property, setterValue, clipboard);
}
}

5
src/Avalonia.Diagnostics/Diagnostics/ViewModels/ResourceSetterViewModel.cs

@ -1,4 +1,5 @@
using Avalonia.Media;
using Avalonia.Input.Platform;
using Avalonia.Media;
namespace Avalonia.Diagnostics.ViewModels
{
@ -10,7 +11,7 @@ namespace Avalonia.Diagnostics.ViewModels
public string ValueTypeTooltip { get; }
public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object? resourceValue, bool isDynamic) : base(property, resourceValue)
public ResourceSetterViewModel(AvaloniaProperty property, object resourceKey, object? resourceValue, bool isDynamic, IClipboard? clipboard) : base(property, resourceValue, clipboard)
{
Key = resourceKey;
Tint = isDynamic ? Brushes.Orange : Brushes.Brown;

12
src/Avalonia.Diagnostics/Diagnostics/ViewModels/SetterViewModel.cs

@ -25,13 +25,17 @@ namespace Avalonia.Diagnostics.ViewModels
set => RaiseAndSetIfChanged(ref _isVisible, value);
}
public SetterViewModel(AvaloniaProperty property, object? value)
private IClipboard? _clipboard;
public SetterViewModel(AvaloniaProperty property, object? value, IClipboard? clipboard)
{
Property = property;
Name = property.Name;
Value = value;
IsActive = true;
IsVisible = true;
_clipboard = clipboard;
}
public virtual void CopyValue()
@ -51,11 +55,9 @@ namespace Avalonia.Diagnostics.ViewModels
CopyToClipboard(Property.Name);
}
protected static void CopyToClipboard(string value)
protected void CopyToClipboard(string value)
{
var clipboard = AvaloniaLocator.Current.GetService<IClipboard>();
clipboard?.SetTextAsync(value);
_clipboard?.SetTextAsync(value);
}
}
}

10
src/Avalonia.FreeDesktop/Avalonia.FreeDesktop.csproj

@ -5,20 +5,20 @@
<Nullable>enable</Nullable>
</PropertyGroup>
<Import Project="..\..\build\TrimmingEnable.props" />
<Import Project="../../build/TrimmingEnable.props" />
<ItemGroup>
<Compile Include="..\Shared\IsExternalInit.cs" Link="IsExternalInit.cs" />
<Compile Include="../Shared/IsExternalInit.cs" Link="IsExternalInit.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Tmds.DBus.Protocol" Version="0.14.0" />
<PackageReference Include="Tmds.DBus.SourceGenerator" Version="0.0.4" />
<PackageReference Include="Tmds.DBus.SourceGenerator" Version="0.0.5" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Controls\Avalonia.Controls.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<ProjectReference Include="../Avalonia.Controls/Avalonia.Controls.csproj" />
<ProjectReference Include="../Avalonia.Dialogs/Avalonia.Dialogs.csproj" />
</ItemGroup>
<ItemGroup Label="InternalsVisibleTo">

21
src/Avalonia.FreeDesktop/DBusMenuExporter.cs

@ -38,7 +38,7 @@ namespace Avalonia.FreeDesktop
private bool _resetQueued;
private int _nextId = 1;
public DBusMenuExporterImpl(Connection connection, IntPtr xid)
public DBusMenuExporterImpl(Connection connection, IntPtr xid) : this()
{
Connection = connection;
_xid = (uint)xid.ToInt32();
@ -47,7 +47,7 @@ namespace Avalonia.FreeDesktop
_ = InitializeAsync();
}
public DBusMenuExporterImpl(Connection connection, string path)
public DBusMenuExporterImpl(Connection connection, string path) : this()
{
Connection = connection;
_appMenu = false;
@ -56,6 +56,13 @@ namespace Avalonia.FreeDesktop
_ = InitializeAsync();
}
private DBusMenuExporterImpl()
{
BackingProperties.Status = string.Empty;
BackingProperties.TextDirection = string.Empty;
BackingProperties.IconThemePath = Array.Empty<string>();
}
protected override Connection Connection { get; }
public override string Path { get; }
@ -202,15 +209,9 @@ namespace Avalonia.FreeDesktop
return id;
}
private void OnMenuItemsChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
QueueReset();
}
private void OnMenuItemsChanged(object? sender, NotifyCollectionChangedEventArgs e) => QueueReset();
private void OnItemPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e)
{
QueueReset();
}
private void OnItemPropertyChanged(object? sender, AvaloniaPropertyChangedEventArgs e) => QueueReset();
private static readonly string[] s_allProperties = {
"type", "label", "enabled", "visible", "shortcut", "toggle-type", "children-display", "toggle-state", "icon-data"

13
src/Avalonia.FreeDesktop/DBusTrayIconImpl.cs

@ -220,6 +220,16 @@ namespace Avalonia.FreeDesktop
{
Connection = connection;
BackingProperties.Menu = dbusMenuPath;
BackingProperties.Category = string.Empty;
BackingProperties.Status = string.Empty;
BackingProperties.Id = string.Empty;
BackingProperties.Title = string.Empty;
BackingProperties.IconPixmap = Array.Empty<(int, int, byte[])>();
BackingProperties.AttentionIconName = string.Empty;
BackingProperties.AttentionIconPixmap = Array.Empty<(int, int, byte[])>();
BackingProperties.AttentionMovieName = string.Empty;
BackingProperties.OverlayIconName = string.Empty;
BackingProperties.OverlayIconPixmap = Array.Empty<(int, int, byte[])>();
BackingProperties.ToolTip = (string.Empty, Array.Empty<(int, int, byte[])>(), string.Empty, string.Empty);
InvalidateAll();
}
@ -234,7 +244,7 @@ namespace Avalonia.FreeDesktop
protected override ValueTask OnActivateAsync(int x, int y)
{
Dispatcher.UIThread.Post(() => ActivationDelegate?.Invoke());
ActivationDelegate?.Invoke();
return new ValueTask();
}
@ -267,7 +277,6 @@ namespace Avalonia.FreeDesktop
BackingProperties.Category = "ApplicationStatus";
BackingProperties.Status = text;
BackingProperties.Title = text;
BackingProperties.ToolTip = (string.Empty, Array.Empty<(int, int, byte[])>(), text, string.Empty);
InvalidateAll();
}
}

8
src/Avalonia.Headless/HeadlessWindowImpl.cs

@ -1,12 +1,11 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Avalonia.Automation.Peers;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
@ -254,6 +253,11 @@ namespace Avalonia.Headless
return new NoopStorageProvider();
}
if(featureType == typeof(IClipboard))
{
return AvaloniaLocator.Current.GetRequiredService<IClipboard>();
}
return null;
}

6
src/Avalonia.Native/WindowImplBase.cs

@ -7,6 +7,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Native.Interop;
using Avalonia.OpenGL;
@ -528,6 +529,11 @@ namespace Avalonia.Native
return _platformBehaviorInhibition;
}
if (featureType == typeof(IClipboard))
{
return AvaloniaLocator.Current.GetRequiredService<IClipboard>();
}
return null;
}

1
src/Avalonia.Themes.Fluent/Controls/Menu.xaml

@ -28,6 +28,7 @@
<Panel>
<ContentPresenter Name="PART_HeaderPresenter"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
RecognizesAccessKey="True"

2
src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml

@ -92,7 +92,7 @@
<ContentPresenter Name="PART_HeaderPresenter"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding ItemTemplate}"
ContentTemplate="{TemplateBinding HeaderTemplate}"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
RecognizesAccessKey="True"

3
src/Avalonia.Themes.Simple/Controls/Menu.xaml

@ -17,7 +17,8 @@
<Panel>
<ContentPresenter Name="PART_HeaderPresenter"
Margin="{TemplateBinding Padding}"
Content="{TemplateBinding Header}">
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding HeaderTemplate}">
<ContentPresenter.DataTemplates>
<DataTemplate DataType="sys:String">
<AccessText Text="{Binding}" />

2
src/Avalonia.Themes.Simple/Controls/MenuItem.xaml

@ -43,7 +43,7 @@
Margin="{TemplateBinding Padding}"
VerticalAlignment="Center"
Content="{TemplateBinding Header}"
ContentTemplate="{TemplateBinding ItemTemplate}">
ContentTemplate="{TemplateBinding HeaderTemplate}">
<ContentPresenter.DataTemplates>
<DataTemplate DataType="sys:String">
<AccessText Text="{Binding}" />

6
src/Avalonia.X11/X11Window.cs

@ -23,6 +23,7 @@ using Avalonia.Threading;
using Avalonia.X11.Glx;
using Avalonia.X11.NativeDialogs;
using static Avalonia.X11.XLib;
using Avalonia.Input.Platform;
// ReSharper disable IdentifierTypo
// ReSharper disable StringLiteralTypo
@ -828,6 +829,11 @@ namespace Avalonia.X11
return _nativeControlHost;
}
if (featureType == typeof(IClipboard))
{
return AvaloniaLocator.Current.GetRequiredService<IClipboard>();
}
return null;
}

9
src/Browser/Avalonia.Browser/BrowserTopLevelImpl.cs

@ -8,6 +8,7 @@ using Avalonia.Browser.Storage;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Platform;
@ -31,6 +32,7 @@ namespace Avalonia.Browser
private readonly INativeControlHostImpl _nativeControlHost;
private readonly IStorageProvider _storageProvider;
private readonly ISystemNavigationManagerImpl _systemNavigationManager;
private readonly ClipboardImpl _clipboard;
private readonly IInsetsManager? _insetsManager;
public BrowserTopLevelImpl(AvaloniaView avaloniaView)
@ -46,7 +48,7 @@ namespace Avalonia.Browser
_nativeControlHost = _avaloniaView.GetNativeControlHostImpl();
_storageProvider = new BrowserStorageProvider();
_systemNavigationManager = new BrowserSystemNavigationManagerImpl();
_clipboard = new ClipboardImpl();
}
public ulong Timestamp => (ulong)_sw.ElapsedMilliseconds;
@ -282,6 +284,11 @@ namespace Avalonia.Browser
return _insetsManager;
}
if (featureType == typeof(IClipboard))
{
return _clipboard;
}
return null;
}
}

1
src/Browser/Avalonia.Browser/WindowingPlatform.cs

@ -36,7 +36,6 @@ namespace Avalonia.Browser
s_keyboard = new KeyboardDevice();
AvaloniaLocator.CurrentMutable
.Bind<IRuntimePlatform>().ToSingleton<BrowserRuntimePlatform>()
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<ICursorFactory>().ToSingleton<CssCursorFactory>()
.Bind<IKeyboardDevice>().ToConstant(s_keyboard)
.Bind<IPlatformSettings>().ToSingleton<BrowserPlatformSettings>()

2
src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs

@ -729,7 +729,7 @@ namespace Avalonia.Win32
{
case GCS.GCS_RESULTSTR:
{
if(ToInt32(wParam) >= 32)
if(!string.IsNullOrEmpty(previousComposition) && ToInt32(wParam) >= 32)
{
Imm32InputMethod.Current.Composition = previousComposition;

6
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -24,6 +24,7 @@ using Avalonia.Win32.OpenGl;
using Avalonia.Win32.WinRT.Composition;
using Avalonia.Win32.WinRT;
using static Avalonia.Win32.Interop.UnmanagedMethods;
using Avalonia.Input.Platform;
namespace Avalonia.Win32
{
@ -332,6 +333,11 @@ namespace Avalonia.Win32
return _storageProvider;
}
if (featureType == typeof(IClipboard))
{
return AvaloniaLocator.Current.GetRequiredService<IClipboard>();
}
return null;
}

9
src/iOS/Avalonia.iOS/AvaloniaView.cs

@ -4,6 +4,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Embedding;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.iOS.Storage;
@ -87,6 +88,8 @@ namespace Avalonia.iOS
private readonly INativeControlHostImpl _nativeControlHost;
private readonly IStorageProvider _storageProvider;
internal readonly InsetsManager _insetsManager;
private readonly ClipboardImpl _clipboard;
public AvaloniaView View => _view;
public TopLevelImpl(AvaloniaView view)
@ -99,6 +102,7 @@ namespace Avalonia.iOS
{
view._topLevel.Padding = b ? default : _insetsManager.SafeAreaPadding;
};
_clipboard = new ClipboardImpl();
}
public void Dispose()
@ -196,6 +200,11 @@ namespace Avalonia.iOS
return _insetsManager;
}
if (featureType == typeof(IClipboard))
{
return _clipboard;
}
return null;
}
}

1
src/iOS/Avalonia.iOS/Platform.cs

@ -39,7 +39,6 @@ namespace Avalonia.iOS
.Bind<IPlatformGraphics>().ToConstant(GlFeature)
.Bind<ICursorFactory>().ToConstant(new CursorFactoryStub())
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
.Bind<IClipboard>().ToConstant(new ClipboardImpl())
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IPlatformIconLoader>().ToConstant(new PlatformIconLoaderStub())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()

29
src/tools/DevGenerators/CompositionGenerator/Generator.cs

@ -170,21 +170,26 @@ namespace Avalonia.SourceGenerator.CompositionGenerator
if (manual.ServerName != null)
serverPropertyType = manual.ServerName + (isNullable ? "?" : "");
}
if (animatedServer)
server = server.AddMembers(
DeclareField(serverPropertyType, fieldName),
PropertyDeclaration(ParseTypeName(serverPropertyType), prop.Name)
.AddModifiers(SyntaxKind.PublicKeyword)
.WithExpressionBody(ArrowExpressionClause(
InvocationExpression(IdentifierName("GetAnimatedValue"),
ArgumentList(SeparatedList(new[]{
Argument(IdentifierName(CompositionPropertyField(prop))),
Argument(null, Token(SyntaxKind.RefKeyword), IdentifierName(fieldName))
}
)))))
.WithSemicolonToken(Semicolon())
);
.AddAccessorListAccessors(
AccessorDeclaration(SyntaxKind.GetAccessorDeclaration).WithExpressionBody(
ArrowExpressionClause(
InvocationExpression(IdentifierName("GetAnimatedValue"),
ArgumentList(SeparatedList(new[]
{
Argument(IdentifierName(CompositionPropertyField(prop))),
Argument(null, Token(SyntaxKind.RefKeyword),
IdentifierName(fieldName))
}
))))).WithSemicolonToken(Semicolon()),
AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
.WithExpressionBody(ArrowExpressionClause(
ParseExpression($"SetAnimatedValue({CompositionPropertyField(prop)}, out {PropertyBackingFieldName(prop)}, value)")))
.WithSemicolonToken(Semicolon())));
else
{
server = server
@ -508,9 +513,7 @@ var changed = reader.Read<{ChangedFieldsTypeName(cl)}>();
code += $@"
if((changed & {changedFieldsType}.{prop.Name}) == {changedFieldsType}.{prop.Name})
";
if (prop.Animated)
code += $"SetAnimatedValue({CompositionPropertyField(prop)}, out {PropertyBackingFieldName(prop)}, {readValueCode});";
else code += $"{prop.Name} = {readValueCode};";
code += $"{prop.Name} = {readValueCode};";
return body.AddStatements(ParseStatement(code));
}

69
tests/Avalonia.Controls.UnitTests/MaskedTextBoxTests.cs

@ -9,8 +9,10 @@ using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Moq;
using Xunit;
@ -820,18 +822,25 @@ namespace Avalonia.Controls.UnitTests
SelectionStart = selectionStart,
SelectionEnd = selectionEnd
};
var impl = CreateMockTopLevelImpl();
var topLevel = new TestTopLevel(impl.Object)
{
Template = CreateTopLevelTemplate()
};
topLevel.Content = target;
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();
target.ApplyTemplate();
if (fromClipboard)
{
AvaloniaLocator.CurrentMutable.Bind<IClipboard>().ToSingleton<ClipboardStub>();
var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService<IClipboard>();
clipboard.SetTextAsync(textInput).GetAwaiter().GetResult();
topLevel.Clipboard?.SetTextAsync(textInput).GetAwaiter().GetResult();
RaiseKeyEvent(target, Key.V, KeyModifiers.Control);
clipboard.ClearAsync().GetAwaiter().GetResult();
topLevel.Clipboard?.ClearAsync().GetAwaiter().GetResult();
}
else
{
@ -859,10 +868,18 @@ namespace Avalonia.Controls.UnitTests
AcceptsReturn = true,
AcceptsTab = true
};
var impl = CreateMockTopLevelImpl();
var topLevel = new TestTopLevel(impl.Object)
{
Template = CreateTopLevelTemplate()
};
topLevel.Content = target;
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();
target.SelectionStart = 1;
target.SelectionEnd = 3;
AvaloniaLocator.CurrentMutable
.Bind<Input.Platform.IClipboard>().ToSingleton<ClipboardStub>();
RaiseKeyEvent(target, key, modifiers);
RaiseKeyEvent(target, Key.Z, KeyModifiers.Control); // undo
@ -951,7 +968,7 @@ namespace Avalonia.Controls.UnitTests
}
}
private class ClipboardStub : IClipboard // in order to get tests working that use the clipboard
internal class ClipboardStub : IClipboard // in order to get tests working that use the clipboard
{
private string _text;
@ -976,6 +993,40 @@ namespace Avalonia.Controls.UnitTests
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
}
private class TestTopLevel : TopLevel
{
private readonly ILayoutManager _layoutManager;
public TestTopLevel(ITopLevelImpl impl, ILayoutManager layoutManager = null)
: base(impl)
{
_layoutManager = layoutManager ?? new LayoutManager(this);
}
protected override ILayoutManager CreateLayoutManager() => _layoutManager;
}
private static Mock<ITopLevelImpl> CreateMockTopLevelImpl()
{
var clipboard = new Mock<ITopLevelImpl>();
clipboard.Setup(r => r.CreateRenderer(It.IsAny<IRenderRoot>()))
.Returns(RendererMocks.CreateRenderer().Object);
clipboard.Setup(r => r.TryGetFeature(typeof(IClipboard)))
.Returns(new ClipboardStub());
clipboard.SetupGet(x => x.RenderScaling).Returns(1);
return clipboard;
}
private static FuncControlTemplate<TestTopLevel> CreateTopLevelTemplate()
{
return new FuncControlTemplate<TestTopLevel>((x, scope) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty],
}.RegisterInNameScope(scope));
}
private class TestContextMenu : ContextMenu
{
public TestContextMenu()

45
tests/Avalonia.Controls.UnitTests/MenuItemTests.cs

@ -3,7 +3,9 @@ using System.Collections.Generic;
using System.Text;
using System.Windows.Input;
using Avalonia.Collections;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Platform;
@ -348,6 +350,47 @@ namespace Avalonia.Controls.UnitTests
}
}
[Fact]
public void Menu_ItemTemplate_Should_Be_Applied_To_TopLevel_MenuItem_Header()
{
using var app = Application();
var items = new[]
{
new MenuViewModel("Foo"),
new MenuViewModel("Bar"),
};
var itemTemplate = new FuncDataTemplate<MenuViewModel>((x, _) =>
new TextBlock { Text = x.Header });
var menu = new Menu
{
ItemTemplate = itemTemplate,
ItemsSource = items,
};
var window = new Window { Content = menu };
window.LayoutManager.ExecuteInitialLayoutPass();
var panel = Assert.IsType<StackPanel>(menu.Presenter.Panel);
Assert.Equal(2, panel.Children.Count);
for (var i = 0; i < panel.Children.Count; i++)
{
var menuItem = Assert.IsType<MenuItem>(panel.Children[i]);
Assert.Equal(items[i], menuItem.Header);
Assert.Same(itemTemplate, menuItem.HeaderTemplate);
var headerPresenter = Assert.IsType<ContentPresenter>(menuItem.HeaderPresenter);
Assert.Same(itemTemplate, headerPresenter.ContentTemplate);
var headerControl = Assert.IsType<TextBlock>(headerPresenter.Child);
Assert.Equal(items[i].Header, headerControl.Text);
}
}
private IDisposable Application()
{
var screen = new PixelRect(new PixelPoint(), new PixelSize(100, 100));
@ -401,5 +444,7 @@ namespace Avalonia.Controls.UnitTests
public void RaiseCanExecuteChanged() => _canExecuteChanged?.Invoke(this, EventArgs.Empty);
}
private record MenuViewModel(string Header);
}
}

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

@ -11,6 +11,7 @@ using Avalonia.Input.Platform;
using Avalonia.Layout;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Moq;
using Xunit;
@ -760,18 +761,24 @@ namespace Avalonia.Controls.UnitTests
SelectionStart = selectionStart,
SelectionEnd = selectionEnd
};
var impl = CreateMockTopLevelImpl();
var topLevel = new TestTopLevel(impl.Object)
{
Template = CreateTopLevelTemplate()
};
topLevel.Content = target;
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();
target.Measure(Size.Infinity);
if (fromClipboard)
{
AvaloniaLocator.CurrentMutable.Bind<IClipboard>().ToSingleton<ClipboardStub>();
var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService<IClipboard>();
clipboard.SetTextAsync(textInput).GetAwaiter().GetResult();
topLevel.Clipboard?.SetTextAsync(textInput).GetAwaiter().GetResult();
RaiseKeyEvent(target, Key.V, KeyModifiers.Control);
clipboard.ClearAsync().GetAwaiter().GetResult();
topLevel.Clipboard?.ClearAsync().GetAwaiter().GetResult();
}
else
{
@ -799,11 +806,19 @@ namespace Avalonia.Controls.UnitTests
AcceptsReturn = true,
AcceptsTab = true
};
var impl = CreateMockTopLevelImpl();
var topLevel = new TestTopLevel(impl.Object)
{
Template = CreateTopLevelTemplate()
};
topLevel.Content = target;
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();
target.ApplyTemplate();
target.SelectionStart = 1;
target.SelectionEnd = 3;
AvaloniaLocator.CurrentMutable
.Bind<Input.Platform.IClipboard>().ToSingleton<ClipboardStub>();
RaiseKeyEvent(target, key, modifiers);
RaiseKeyEvent(target, Key.Z, KeyModifiers.Control); // undo
@ -872,15 +887,21 @@ namespace Avalonia.Controls.UnitTests
AcceptsReturn= true
};
target.Measure(Size.Infinity);
var impl = CreateMockTopLevelImpl();
var topLevel = new TestTopLevel(impl.Object)
{
Template = CreateTopLevelTemplate()
};
topLevel.Content = target;
topLevel.ApplyTemplate();
topLevel.LayoutManager.ExecuteInitialLayoutPass();
AvaloniaLocator.CurrentMutable.Bind<IClipboard>().ToSingleton<ClipboardStub>();
target.Measure(Size.Infinity);
var clipboard = AvaloniaLocator.CurrentMutable.GetRequiredService<IClipboard>();
clipboard.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult();
topLevel.Clipboard?.SetTextAsync(Environment.NewLine).GetAwaiter().GetResult();
RaiseKeyEvent(target, Key.V, KeyModifiers.Control);
clipboard.ClearAsync().GetAwaiter().GetResult();
topLevel.Clipboard?.ClearAsync().GetAwaiter().GetResult();
RaiseTextEvent(target, Environment.NewLine);
@ -1176,7 +1197,41 @@ namespace Avalonia.Controls.UnitTests
public Task<object> GetDataAsync(string format) => Task.FromResult((object)null);
}
private class TestTopLevel : TopLevel
{
private readonly ILayoutManager _layoutManager;
public TestTopLevel(ITopLevelImpl impl, ILayoutManager layoutManager = null)
: base(impl)
{
_layoutManager = layoutManager ?? new LayoutManager(this);
}
protected override ILayoutManager CreateLayoutManager() => _layoutManager;
}
private static Mock<ITopLevelImpl> CreateMockTopLevelImpl()
{
var clipboard = new Mock<ITopLevelImpl>();
clipboard.Setup(r => r.CreateRenderer(It.IsAny<IRenderRoot>()))
.Returns(RendererMocks.CreateRenderer().Object);
clipboard.Setup(r => r.TryGetFeature(typeof(IClipboard)))
.Returns(new ClipboardStub());
clipboard.SetupGet(x => x.RenderScaling).Returns(1);
return clipboard;
}
private static FuncControlTemplate<TestTopLevel> CreateTopLevelTemplate()
{
return new FuncControlTemplate<TestTopLevel>((x, scope) =>
new ContentPresenter
{
Name = "PART_ContentPresenter",
[!ContentPresenter.ContentProperty] = x[!ContentControl.ContentProperty],
}.RegisterInNameScope(scope));
}
private class TestContextMenu : ContextMenu
{
public TestContextMenu()

2
tests/Avalonia.Controls.UnitTests/TopLevelTests.cs

@ -2,6 +2,7 @@ using System;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.LogicalTree;
@ -11,6 +12,7 @@ using Avalonia.Styling;
using Avalonia.UnitTests;
using Moq;
using Xunit;
using static Avalonia.Controls.UnitTests.MaskedTextBoxTests;
namespace Avalonia.Controls.UnitTests
{

29
tests/Avalonia.Controls.UnitTests/TreeViewTests.cs

@ -722,6 +722,33 @@ namespace Avalonia.Controls.UnitTests
Assert.True(called);
}
[Fact]
public void SelectedItem_Should_Be_Valid_When_SelectedItemChanged_Event_Raised()
{
using var app = Start();
var data = CreateTestTreeData();
var target = CreateTarget(data: data);
var item = data[0].Children[1].Children[0];
var container = Assert.IsType<TreeViewItem>(target.TreeContainerFromItem(item));
Assert.NotNull(container);
var called = false;
target.SelectionChanged += (s, e) =>
{
Assert.Same(item, e.AddedItems[0]);
Assert.Same(item, target.SelectedItem);
called = true;
};
_mouse.Click(container);
Assert.Equal(item, target.SelectedItem);
Assert.True(container.IsSelected);
Assert.True(called);
}
[Fact]
public void Bound_SelectedItem_Should_Not_Be_Cleared_when_Changing_Selection()
{
@ -756,7 +783,7 @@ namespace Avalonia.Controls.UnitTests
using var app = Start();
var data = CreateTestTreeData();
var target = CreateTarget(data: data, expandAll: false);
target.SelectedItem = data[0].Children[1];
var rootItem = Assert.IsType<TreeViewItem>(target.ContainerFromIndex(0));

Loading…
Cancel
Save