Browse Source

Merge branch 'master' into clipboard-image

pull/12246/head
viordash 3 years ago
committed by GitHub
parent
commit
713ec4e755
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 5
      nukebuild/Build.cs
  2. 109
      nukebuild/RefAssemblyGenerator.cs
  3. 5
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  4. 26
      src/Avalonia.Base/Animation/CrossFade.cs
  5. 4
      src/Avalonia.Controls/HotkeyManager.cs
  6. 15
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  7. 12
      src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
  8. 1
      src/Avalonia.Themes.Fluent/Controls/NumericUpDown.xaml
  9. 1
      src/Avalonia.Themes.Simple/Controls/NumericUpDown.xaml
  10. 36
      src/Avalonia.X11/X11Window.cs
  11. 122
      tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs

5
nukebuild/Build.cs

@ -279,8 +279,9 @@ partial class Build : NukeBuild
if(!Numerge.NugetPackageMerger.Merge(Parameters.NugetIntermediateRoot, Parameters.NugetRoot, config,
new NumergeNukeLogger()))
throw new Exception("Package merge failed");
RefAssemblyGenerator.GenerateRefAsmsInPackage(Parameters.NugetRoot / "Avalonia." +
Parameters.Version + ".nupkg");
RefAssemblyGenerator.GenerateRefAsmsInPackage(
Parameters.NugetRoot / $"Avalonia.{Parameters.Version}.nupkg",
Parameters.NugetRoot / $"Avalonia.{Parameters.Version}.snupkg");
});
Target ValidateApiDiff => _ => _

109
nukebuild/RefAssemblyGenerator.cs

@ -1,8 +1,10 @@
#nullable enable
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using ILRepacking;
using Mono.Cecil;
using Mono.Cecil.Cil;
@ -10,8 +12,8 @@ public class RefAssemblyGenerator
{
class Resolver : DefaultAssemblyResolver, IAssemblyResolver
{
private readonly string _dir;
Dictionary<string, AssemblyDefinition> _cache = new();
readonly string _dir;
readonly Dictionary<string, AssemblyDefinition> _cache = new();
public Resolver(string dir)
{
@ -31,17 +33,17 @@ public class RefAssemblyGenerator
public static void PatchRefAssembly(string file)
{
var reader = typeof(RefAssemblyGenerator).Assembly.GetManifestResourceStream("avalonia.snk");
var reader = typeof(RefAssemblyGenerator).Assembly.GetManifestResourceStream("avalonia.snk")!;
var snk = new byte[reader.Length];
reader.Read(snk, 0, snk.Length);
reader.ReadExactly(snk, 0, snk.Length);
var def = AssemblyDefinition.ReadAssembly(file, new ReaderParameters
{
ReadWrite = true,
InMemory = true,
ReadSymbols = true,
SymbolReaderProvider = new DefaultSymbolReaderProvider(false),
AssemblyResolver = new Resolver(Path.GetDirectoryName(file))
SymbolReaderProvider = new DefaultSymbolReaderProvider(throwIfNoSymbol: true),
AssemblyResolver = new Resolver(Path.GetDirectoryName(file)!)
});
var obsoleteAttribute = def.MainModule.ImportReference(new TypeReference("System", "ObsoleteAttribute", def.MainModule,
@ -58,7 +60,7 @@ public class RefAssemblyGenerator
{
StrongNameKeyBlob = snk,
WriteSymbols = def.MainModule.HasSymbols,
SymbolWriterProvider = new EmbeddedPortablePdbWriterProvider(),
SymbolWriterProvider = new PortablePdbWriterProvider(),
DeterministicMvid = def.MainModule.HasSymbols
});
}
@ -146,7 +148,7 @@ public class RefAssemblyGenerator
m.Attributes = ((m.Attributes | dflags) ^ dflags) | MethodAttributes.Assembly;
}
static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, ICustomAttribute unstableAttribute)
static void MarkAsUnstable(IMemberDefinition def, MethodReference obsoleteCtor, ICustomAttribute? unstableAttribute)
{
if (def.CustomAttributes.Any(a => a.AttributeType.FullName == "System.ObsoleteAttribute"))
return;
@ -172,43 +174,66 @@ public class RefAssemblyGenerator
});
}
public static void GenerateRefAsmsInPackage(string packagePath)
public static void GenerateRefAsmsInPackage(string mainPackagePath, string symbolsPackagePath)
{
using (var archive = new ZipArchive(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite),
ZipArchiveMode.Update))
using var mainArchive = OpenPackage(mainPackagePath);
using var symbolsArchive = OpenPackage(symbolsPackagePath);
foreach (var entry in mainArchive.Entries
.Where(e => e.FullName.StartsWith("ref/", StringComparison.Ordinal))
.ToArray())
{
foreach (var entry in archive.Entries.ToList())
{
if (entry.FullName.StartsWith("ref/"))
entry.Delete();
}
foreach (var entry in archive.Entries.ToList())
entry.Delete();
}
foreach (var libEntry in GetLibEntries(mainArchive, ".xml"))
{
var refEntry = mainArchive.CreateEntry("ref/" + libEntry.FullName.Substring(4), CompressionLevel.Optimal);
using var src = libEntry.Open();
using var dst = refEntry.Open();
src.CopyTo(dst);
}
var pdbEntries = GetLibEntries(symbolsArchive, ".pdb").ToDictionary(e => e.FullName);
var libs = GetLibEntries(mainArchive, ".dll")
.Select(e => (NameParts: e.FullName.Split('/'), Entry: e))
.Select(e => (
Tfm: e.NameParts[1],
DllName: e.NameParts[2],
DllEntry: e.Entry,
PdbName: Path.ChangeExtension(e.NameParts[2], ".pdb"),
PdbEntry: pdbEntries.TryGetValue(Path.ChangeExtension(e.Entry.FullName, ".pdb"), out var pdbEntry) ?
pdbEntry :
throw new InvalidOperationException($"Missing symbols for {e.Entry.FullName}")))
.GroupBy(e => e.Tfm);
foreach (var tfm in libs)
{
using var _ = Helpers.UseTempDir(out var temp);
foreach (var lib in tfm)
{
if (entry.FullName.StartsWith("lib/") && entry.Name.EndsWith(".xml"))
{
var newEntry = archive.CreateEntry("ref/" + entry.FullName.Substring(4),
CompressionLevel.Optimal);
using (var src = entry.Open())
using (var dst = newEntry.Open())
src.CopyTo(dst);
}
}
var extractedDllPath = Path.Combine(temp, lib.DllName);
var extractedPdbPath = Path.Combine(temp, lib.PdbName);
lib.DllEntry.ExtractToFile(extractedDllPath);
lib.PdbEntry.ExtractToFile(extractedPdbPath);
var libs = archive.Entries.Where(e => e.FullName.StartsWith("lib/") && e.FullName.EndsWith(".dll"))
.Select((e => new { s = e.FullName.Split('/'), e = e }))
.Select(e => new { Tfm = e.s[1], Name = e.s[2], Entry = e.e })
.GroupBy(x => x.Tfm);
foreach(var tfm in libs)
using (Helpers.UseTempDir(out var temp))
{
foreach (var l in tfm)
l.Entry.ExtractToFile(Path.Combine(temp, l.Name));
foreach (var l in tfm)
PatchRefAssembly(Path.Combine(temp, l.Name));
foreach (var l in tfm)
archive.CreateEntryFromFile(Path.Combine(temp, l.Name), $"ref/{l.Tfm}/{l.Name}");
}
PatchRefAssembly(extractedDllPath);
mainArchive.CreateEntryFromFile(extractedDllPath, $"ref/{lib.Tfm}/{lib.DllName}");
symbolsArchive.CreateEntryFromFile(extractedPdbPath, $"ref/{lib.Tfm}/{lib.PdbName}");
}
}
static ZipArchive OpenPackage(string packagePath)
=> new(File.Open(packagePath, FileMode.Open, FileAccess.ReadWrite), ZipArchiveMode.Update);
static ZipArchiveEntry[] GetLibEntries(ZipArchive archive, string extension)
=> archive.Entries
.Where(e => e.FullName.StartsWith("lib/", StringComparison.Ordinal)
&& e.FullName.EndsWith(extension, StringComparison.Ordinal))
.ToArray();
}
}

5
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -113,6 +113,11 @@ namespace Avalonia.Android
private void OnSelectionChanged()
{
if (Client is null)
{
return;
}
var selection = Client.Selection;
_imm.UpdateSelection(_host, selection.Start, selection.End, selection.Start, selection.End);

26
src/Avalonia.Base/Animation/CrossFade.cs

@ -35,6 +35,18 @@ namespace Avalonia.Animation
{
Children =
{
new KeyFrame()
{
Setters =
{
new Setter
{
Property = Visual.OpacityProperty,
Value = 1d
}
},
Cue = new Cue(0d)
},
new KeyFrame()
{
Setters =
@ -54,6 +66,18 @@ namespace Avalonia.Animation
{
Children =
{
new KeyFrame()
{
Setters =
{
new Setter
{
Property = Visual.OpacityProperty,
Value = 0d
}
},
Cue = new Cue(0d)
},
new KeyFrame()
{
Setters =
@ -117,11 +141,13 @@ namespace Avalonia.Animation
if (from != null)
{
from.Opacity = 0f;
tasks.Add(_fadeOutAnimation.RunAsync(from, null, cancellationToken));
}
if (to != null)
{
to.Opacity = 1f;
to.IsVisible = true;
tasks.Add(_fadeInAnimation.RunAsync(to, null, cancellationToken));
}

4
src/Avalonia.Controls/HotkeyManager.cs

@ -149,10 +149,10 @@ namespace Avalonia.Controls
return;
var control = args.Sender as Control;
if (control is not IClickableControl)
if (control is not IClickableControl and not ICommandSource)
{
Logging.Logger.TryGet(Logging.LogEventLevel.Warning, Logging.LogArea.Control)?.Log(control,
$"The element {args.Sender.GetType().Name} does not implement IClickableControl and does not support binding a HotKey ({args.NewValue}).");
$"The element {args.Sender.GetType().Name} does not implement IClickableControl nor ICommandSource and does not support binding a HotKey ({args.NewValue}).");
return;
}

15
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -126,6 +126,12 @@ namespace Avalonia.Controls
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<NumericUpDown>();
/// <summary>
/// Defines the <see cref="TextAlignment"/> property
/// </summary>
public static readonly StyledProperty<Media.TextAlignment> TextAlignmentProperty =
TextBox.TextAlignmentProperty.AddOwner<NumericUpDown>();
private IDisposable? _textBoxTextChangedSubscription;
private bool _internalValueSet;
@ -299,6 +305,15 @@ namespace Avalonia.Controls
set => SetValue(VerticalContentAlignmentProperty, value);
}
/// <summary>
/// Gets or sets the <see cref="Media.TextAlignment"/> of the <see cref="NumericUpDown"/>
/// </summary>
public Media.TextAlignment TextAlignment
{
get => GetValue(TextAlignmentProperty);
set => SetValue(TextAlignmentProperty, value);
}
/// <summary>
/// Initializes new instance of <see cref="NumericUpDown"/> class.
/// </summary>

12
src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs

@ -62,9 +62,15 @@ namespace Avalonia.FreeDesktop.DBusIme
foreach (var name in _knownNames)
{
var dbus = new OrgFreedesktopDBus(Connection, "org.freedesktop.DBus", "/org/freedesktop/DBus");
_disposables.Add(await dbus.WatchNameOwnerChangedAsync(OnNameChange));
var nameOwner = await dbus.GetNameOwnerAsync(name);
OnNameChange(null, (name, null, nameOwner));
try
{
_disposables.Add(await dbus.WatchNameOwnerChangedAsync(OnNameChange));
var nameOwner = await dbus.GetNameOwnerAsync(name);
OnNameChange(null, (name, null, nameOwner));
}
catch (DBusException)
{
}
}
}

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

@ -57,6 +57,7 @@
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
Text="{TemplateBinding Text}"
TextAlignment="{TemplateBinding TextAlignment}"
AcceptsReturn="False"
TextWrapping="NoWrap" />
</ButtonSpinner>

1
src/Avalonia.Themes.Simple/Controls/NumericUpDown.xaml

@ -31,6 +31,7 @@
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}"
IsReadOnly="{TemplateBinding IsReadOnly}"
Text="{TemplateBinding Text}"
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="NoWrap"
Watermark="{TemplateBinding Watermark}" />
</ButtonSpinner>

36
src/Avalonia.X11/X11Window.cs

@ -17,7 +17,6 @@ using Avalonia.OpenGL;
using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Platform.Storage;
using Avalonia.Rendering;
using Avalonia.Rendering.Composition;
using Avalonia.Threading;
using Avalonia.X11.Glx;
@ -36,6 +35,7 @@ namespace Avalonia.X11
{
private readonly AvaloniaX11Platform _platform;
private readonly bool _popup;
private readonly bool _overrideRedirect;
private readonly X11Info _x11;
private XConfigureEvent? _configure;
private PixelPoint? _configurePoint;
@ -72,10 +72,11 @@ namespace Avalonia.X11
WaitPaint
}
public X11Window(AvaloniaX11Platform platform, IWindowImpl? popupParent)
public X11Window(AvaloniaX11Platform platform, IWindowImpl? popupParent, bool overrideRedirect = false)
{
_platform = platform;
_popup = popupParent != null;
_overrideRedirect = _popup || overrideRedirect;
_x11 = platform.Info;
_mouse = new MouseDevice();
_touch = new TouchDevice();
@ -92,7 +93,7 @@ namespace Avalonia.X11
| SetWindowValuemask.BackPixmap | SetWindowValuemask.BackingStore
| SetWindowValuemask.BitGravity | SetWindowValuemask.WinGravity;
if (_popup)
if (_overrideRedirect)
{
attr.override_redirect = 1;
valueMask |= SetWindowValuemask.OverrideRedirect;
@ -155,7 +156,7 @@ namespace Avalonia.X11
else
_renderHandle = _handle;
Handle = new SurfacePlatformHandle(this);
Handle = new PlatformHandle(_handle, "XID");
_realSize = new PixelSize(defaultWidth, defaultHeight);
platform.Windows[_handle] = OnEvent;
XEventMask ignoredMask = XEventMask.SubstructureRedirectMask
@ -165,15 +166,18 @@ namespace Avalonia.X11
ignoredMask |= platform.XI2.AddWindow(_handle, this);
var mask = new IntPtr(0xffffff ^ (int)ignoredMask);
XSelectInput(_x11.Display, _handle, mask);
var protocols = new[]
if (!_overrideRedirect)
{
_x11.Atoms.WM_DELETE_WINDOW
};
XSetWMProtocols(_x11.Display, _handle, protocols, protocols.Length);
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM,
32, PropertyMode.Replace, new[] {_x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL}, 1);
var protocols = new[]
{
_x11.Atoms.WM_DELETE_WINDOW
};
XSetWMProtocols(_x11.Display, _handle, protocols, protocols.Length);
XChangeProperty(_x11.Display, _handle, _x11.Atoms._NET_WM_WINDOW_TYPE, _x11.Atoms.XA_ATOM,
32, PropertyMode.Replace, new[] { _x11.Atoms._NET_WM_WINDOW_TYPE_NORMAL }, 1);
SetWmClass(_platform.Options.WmClass);
SetWmClass(_platform.Options.WmClass);
}
var surfaces = new List<object>
{
@ -187,7 +191,7 @@ namespace Avalonia.X11
if (glx != null)
surfaces.Insert(0, new GlxGlPlatformSurface(new SurfaceInfo(this, _x11.DeferredDisplay, _handle, _renderHandle)));
surfaces.Add(Handle);
surfaces.Add(new SurfacePlatformHandle(this));
Surfaces = surfaces.ToArray();
UpdateMotifHints();
@ -257,6 +261,8 @@ namespace Avalonia.X11
private void UpdateMotifHints()
{
if(_overrideRedirect)
return;
var functions = MotifFunctions.Move | MotifFunctions.Close | MotifFunctions.Resize |
MotifFunctions.Minimize | MotifFunctions.Maximize;
var decorations = MotifDecorations.Menu | MotifDecorations.Title | MotifDecorations.Border |
@ -286,6 +292,8 @@ namespace Avalonia.X11
private void UpdateSizeHints(PixelSize? preResize)
{
if(_overrideRedirect)
return;
var min = _minMaxSize.minSize;
var max = _minMaxSize.maxSize;
@ -507,7 +515,7 @@ namespace Avalonia.X11
}
UpdateImePosition();
if (changedSize && !updatedSizeViaScaling && !_popup)
if (changedSize && !updatedSizeViaScaling && !_overrideRedirect)
Resized?.Invoke(ClientSize, WindowResizeReason.Unspecified);
}, DispatcherPriority.AsyncRenderTargetResize);
@ -984,7 +992,7 @@ namespace Avalonia.X11
XConfigureResizeWindow(_x11.Display, _renderHandle, pixelSize);
XFlush(_x11.Display);
if (force || !_wasMappedAtLeastOnce || (_popup && needImmediatePopupResize))
if (force || !_wasMappedAtLeastOnce || (_overrideRedirect && needImmediatePopupResize))
{
_realSize = pixelSize;
Resized?.Invoke(ClientSize, reason);

122
tests/Avalonia.Controls.UnitTests/HotKeyedControlsTests.cs

@ -0,0 +1,122 @@
using System;
using System.Windows.Input;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.LogicalTree;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Moq;
using Xunit;
namespace Avalonia.Controls.UnitTests
{
internal class HotKeyedTextBox : TextBox, ICommandSource
{
private class DelegateCommand : ICommand
{
private readonly Action _action;
public DelegateCommand(Action action) => _action = action;
public event EventHandler CanExecuteChanged { add { } remove { } }
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _action();
}
public static readonly StyledProperty<KeyGesture> HotKeyProperty =
HotKeyManager.HotKeyProperty.AddOwner<HotKeyedTextBox>();
private KeyGesture _hotkey;
public KeyGesture HotKey
{
get => GetValue(HotKeyProperty);
set => SetValue(HotKeyProperty, value);
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (_hotkey != null)
{
this.SetValue(HotKeyProperty, _hotkey);
}
base.OnAttachedToLogicalTree(e);
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
if (this.HotKey != null)
{
_hotkey = this.HotKey;
this.SetValue(HotKeyProperty, null);
}
base.OnDetachedFromLogicalTree(e);
}
public void CanExecuteChanged(object sender, EventArgs e)
{
}
protected override Type StyleKeyOverride => typeof(TextBox);
public ICommand Command => _command;
public object CommandParameter => null;
private readonly DelegateCommand _command;
public HotKeyedTextBox()
{
_command = new DelegateCommand(() => Focus());
}
}
public class HotKeyedControlsTests
{
private static Window PreparedWindow(object content = null)
{
var platform = AvaloniaLocator.Current.GetRequiredService<IWindowingPlatform>();
var windowImpl = Mock.Get(platform.CreateWindow());
windowImpl.Setup(x => x.Compositor).Returns(RendererMocks.CreateDummyCompositor());
var w = new Window(windowImpl.Object) { Content = content };
w.ApplyTemplate();
return w;
}
private static IDisposable CreateServicesWithFocus()
{
return UnitTestApplication.Start(
TestServices.StyledWindow.With(
windowingPlatform: new MockWindowingPlatform(
null,
window => MockWindowingPlatform.CreatePopupMock(window).Object),
focusManager: new FocusManager(),
keyboardDevice: () => new KeyboardDevice()));
}
[Fact]
public void HotKeyedTextBox_Focus_Performed_On_Hotkey()
{
using var _ = CreateServicesWithFocus();
var keyboardDevice = new KeyboardDevice();
var hotKeyedTextBox = new HotKeyedTextBox { HotKey = new KeyGesture(Key.F, KeyModifiers.Control) };
var root = PreparedWindow();
root.Content = hotKeyedTextBox;
root.Show();
Assert.False(hotKeyedTextBox.IsFocused);
keyboardDevice.ProcessRawEvent(
new RawKeyEventArgs(
keyboardDevice,
0,
root,
RawKeyEventType.KeyDown,
Key.F,
RawInputModifiers.Control));
Assert.True(hotKeyedTextBox.IsFocused);
}
}
}
Loading…
Cancel
Save