Browse Source

Merge branch 'master' into master

pull/1968/head
Nikita Tsukanov 8 years ago
committed by GitHub
parent
commit
b8aabe5cd1
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  2. 9
      src/Avalonia.Controls/Application.cs
  3. 26
      src/Avalonia.Controls/Remote/RemoteWidget.cs
  4. 161
      src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs
  5. 378
      src/Avalonia.Controls/TextBox.cs
  6. 2
      src/Avalonia.DesignerSupport/DesignWindowLoader.cs
  7. 3
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  8. 10
      src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs
  9. 4
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  10. 18
      src/Avalonia.Input/Key.cs
  11. 11
      src/Avalonia.Input/KeyGesture.cs
  12. 98
      src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs
  13. 6
      src/Avalonia.Remote.Protocol/InputMessages.cs
  14. 7
      src/Avalonia.Remote.Protocol/ViewportMessages.cs
  15. 2
      src/Avalonia.Styling/Styling/IStyle.cs
  16. 52
      src/Avalonia.Styling/Styling/Style.cs
  17. 8
      src/Avalonia.Styling/Styling/Styles.cs
  18. 9
      src/Avalonia.Styling/Styling/packages.config
  19. 1
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  20. 2
      src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
  21. 2
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  22. 10
      src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
  23. 37
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs
  24. 18
      src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs
  25. 10
      src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs
  26. 1
      src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs
  27. 1
      src/Windows/Avalonia.Win32/Win32Platform.cs
  28. 1
      src/iOS/Avalonia.iOS/iOSPlatform.cs
  29. 18
      tests/Avalonia.Controls.UnitTests/ApplicationTests.cs
  30. 41
      tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs
  31. 25
      tests/Avalonia.Styling.UnitTests/StyleTests.cs
  32. 2
      tests/Avalonia.UnitTests/UnitTestApplication.cs

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

@ -54,6 +54,7 @@ namespace Avalonia.Android
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoader>()
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IAssetLoader>().ToConstant(new AssetLoader(app.GetType().Assembly));
SkiaPlatform.Initialize();

9
src/Avalonia.Controls/Application.cs

@ -122,11 +122,11 @@ namespace Avalonia
if (_resources != null)
{
hadResources = _resources.Count > 0;
_resources.ResourcesChanged -= ResourcesChanged;
_resources.ResourcesChanged -= ThisResourcesChanged;
}
_resources = value;
_resources.ResourcesChanged += ResourcesChanged;
_resources.ResourcesChanged += ThisResourcesChanged;
if (hadResources || _resources.Count > 0)
{
@ -343,5 +343,10 @@ namespace Avalonia
.Bind<IGlobalClock>().ToConstant(clock)
.GetService<IRenderLoop>()?.Add(clock);
}
private void ThisResourcesChanged(object sender, ResourcesChangedEventArgs e)
{
ResourcesChanged?.Invoke(this, e);
}
}
}

26
src/Avalonia.Controls/Remote/RemoteWidget.cs

@ -11,11 +11,19 @@ namespace Avalonia.Controls.Remote
{
public class RemoteWidget : Control
{
public enum SizingMode
{
Local,
Remote
}
private readonly IAvaloniaRemoteTransportConnection _connection;
private FrameMessage _lastFrame;
private WriteableBitmap _bitmap;
public RemoteWidget(IAvaloniaRemoteTransportConnection connection)
{
Mode = SizingMode.Local;
_connection = connection;
_connection.OnMessage += (t, msg) => Dispatcher.UIThread.Post(() => OnMessage(msg));
_connection.Send(new ClientSupportedPixelFormatsMessage
@ -28,6 +36,8 @@ namespace Avalonia.Controls.Remote
});
}
public SizingMode Mode { get; set; }
private void OnMessage(object msg)
{
if (msg is FrameMessage frame)
@ -44,13 +54,17 @@ namespace Avalonia.Controls.Remote
protected override void ArrangeCore(Rect finalRect)
{
_connection.Send(new ClientViewportAllocatedMessage
if (Mode == SizingMode.Local)
{
Width = finalRect.Width,
Height = finalRect.Height,
DpiX = 96,
DpiY = 96 //TODO: Somehow detect the actual DPI
});
_connection.Send(new ClientViewportAllocatedMessage
{
Width = finalRect.Width,
Height = finalRect.Height,
DpiX = 10 * 96,
DpiY = 10 * 96 //TODO: Somehow detect the actual DPI
});
}
base.ArrangeCore(finalRect);
}

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

@ -4,11 +4,15 @@ using System.Runtime.InteropServices;
using Avalonia.Controls.Embedding.Offscreen;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Layout;
using Avalonia.Platform;
using Avalonia.Remote.Protocol;
using Avalonia.Remote.Protocol.Input;
using Avalonia.Remote.Protocol.Viewport;
using Avalonia.Threading;
using InputModifiers = Avalonia.Input.InputModifiers;
using Key = Avalonia.Input.Key;
using PixelFormat = Avalonia.Platform.PixelFormat;
using ProtocolPixelFormat = Avalonia.Remote.Protocol.Viewport.PixelFormat;
@ -31,6 +35,67 @@ namespace Avalonia.Controls.Remote.Server
{
_transport = transport;
_transport.OnMessage += OnMessage;
KeyboardDevice = AvaloniaLocator.Current.GetService<IKeyboardDevice>();
}
private static RawMouseEventType GetAvaloniaEventType (Avalonia.Remote.Protocol.Input.MouseButton button, bool pressed)
{
switch (button)
{
case Avalonia.Remote.Protocol.Input.MouseButton.Left:
return pressed ? RawMouseEventType.LeftButtonDown : RawMouseEventType.LeftButtonUp;
case Avalonia.Remote.Protocol.Input.MouseButton.Middle:
return pressed ? RawMouseEventType.MiddleButtonDown : RawMouseEventType.MiddleButtonUp;
case Avalonia.Remote.Protocol.Input.MouseButton.Right:
return pressed ? RawMouseEventType.RightButtonDown : RawMouseEventType.RightButtonUp;
default:
return RawMouseEventType.Move;
}
}
private static InputModifiers GetAvaloniaInputModifiers (Avalonia.Remote.Protocol.Input.InputModifiers[] modifiers)
{
var result = InputModifiers.None;
foreach(var modifier in modifiers)
{
switch (modifier)
{
case Avalonia.Remote.Protocol.Input.InputModifiers.Control:
result |= InputModifiers.Control;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.Alt:
result |= InputModifiers.Alt;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.Shift:
result |= InputModifiers.Shift;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.Windows:
result |= InputModifiers.Windows;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.LeftMouseButton:
result |= InputModifiers.LeftMouseButton;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.MiddleMouseButton:
result |= InputModifiers.MiddleMouseButton;
break;
case Avalonia.Remote.Protocol.Input.InputModifiers.RightMouseButton:
result |= InputModifiers.RightMouseButton;
break;
}
}
return result;
}
protected virtual void OnMessage(IAvaloniaRemoteTransportConnection transport, object obj)
@ -45,6 +110,16 @@ namespace Avalonia.Controls.Remote.Server
}
Dispatcher.UIThread.Post(RenderIfNeeded);
}
if(obj is ClientRenderInfoMessage renderInfo)
{
lock(_lock)
{
_dpi = new Vector(renderInfo.DpiX, renderInfo.DpiY);
_invalidated = true;
}
Dispatcher.UIThread.Post(RenderIfNeeded);
}
if (obj is ClientSupportedPixelFormatsMessage supportedFormats)
{
lock (_lock)
@ -82,6 +157,84 @@ namespace Avalonia.Controls.Remote.Server
_pendingAllocation = allocated;
}
}
if(obj is PointerMovedEventMessage pointer)
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawMouseEventArgs(
MouseDevice,
0,
InputRoot,
RawMouseEventType.Move,
new Point(pointer.X, pointer.Y),
GetAvaloniaInputModifiers(pointer.Modifiers)));
}, DispatcherPriority.Input);
}
if(obj is PointerPressedEventMessage pressed)
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawMouseEventArgs(
MouseDevice,
0,
InputRoot,
GetAvaloniaEventType(pressed.Button, true),
new Point(pressed.X, pressed.Y),
GetAvaloniaInputModifiers(pressed.Modifiers)));
}, DispatcherPriority.Input);
}
if (obj is PointerPressedEventMessage released)
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawMouseEventArgs(
MouseDevice,
0,
InputRoot,
GetAvaloniaEventType(released.Button, false),
new Point(released.X, released.Y),
GetAvaloniaInputModifiers(released.Modifiers)));
}, DispatcherPriority.Input);
}
if(obj is ScrollEventMessage scroll)
{
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawMouseWheelEventArgs(
MouseDevice,
0,
InputRoot,
new Point(scroll.X, scroll.Y),
new Vector(scroll.DeltaX, scroll.DeltaY),
GetAvaloniaInputModifiers(scroll.Modifiers)));
}, DispatcherPriority.Input);
}
if(obj is KeyEventMessage key)
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawKeyEventArgs(
KeyboardDevice,
0,
key.IsDown ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
(Key)key.Key,
GetAvaloniaInputModifiers(key.Modifiers)));
}, DispatcherPriority.Input);
}
if(obj is TextInputEventMessage text)
{
Dispatcher.UIThread.RunJobs(DispatcherPriority.Input + 1);
Dispatcher.UIThread.Post(() =>
{
Input?.Invoke(new RawTextInputEventArgs(
KeyboardDevice,
0,
text.Text));
}, DispatcherPriority.Input);
}
}
}
@ -102,6 +255,12 @@ namespace Avalonia.Controls.Remote.Server
FrameMessage RenderFrame(int width, int height, ProtocolPixelFormat? format)
{
var scalingX = _dpi.X / 96.0;
var scalingY = _dpi.Y / 96.0;
width = (int)(width * scalingX);
height = (int)(height * scalingY);
var fmt = format ?? ProtocolPixelFormat.Rgba8888;
var bpp = fmt == ProtocolPixelFormat.Rgb565 ? 2 : 4;
var data = new byte[width * height * bpp];
@ -169,5 +328,7 @@ namespace Avalonia.Controls.Remote.Server
}
public override IMouseDevice MouseDevice { get; } = new MouseDevice();
public IKeyboardDevice KeyboardDevice { get; }
}
}

378
src/Avalonia.Controls/TextBox.cs

@ -368,186 +368,242 @@ namespace Avalonia.Controls
string text = Text ?? string.Empty;
int caretIndex = CaretIndex;
bool movement = false;
bool selection = false;
bool handled = false;
var modifiers = e.Modifiers;
switch (e.Key)
{
case Key.A:
if (modifiers == InputModifiers.Control)
{
SelectAll();
handled = true;
}
break;
case Key.C:
if (modifiers == InputModifiers.Control)
{
if (!IsPasswordBox)
{
Copy();
}
handled = true;
}
break;
var keymap = AvaloniaLocator.Current.GetService<PlatformHotkeyConfiguration>();
case Key.X:
if (modifiers == InputModifiers.Control)
{
if (!IsPasswordBox)
{
Copy();
DeleteSelection();
}
handled = true;
}
break;
bool Match(List<KeyGesture> gestures) => gestures.Any(g => g.Matches(e));
bool DetectSelection() => e.Modifiers.HasFlag(keymap.SelectionModifiers);
case Key.V:
if (modifiers == InputModifiers.Control)
{
Paste();
handled = true;
}
if (Match(keymap.SelectAll))
{
SelectAll();
handled = true;
}
else if (Match(keymap.Copy))
{
if (!IsPasswordBox)
{
Copy();
}
break;
handled = true;
}
else if (Match(keymap.Cut))
{
if (!IsPasswordBox)
{
Copy();
DeleteSelection();
}
case Key.Z:
if (modifiers == InputModifiers.Control)
{
try
{
_isUndoingRedoing = true;
_undoRedoHelper.Undo();
}
finally
{
_isUndoingRedoing = false;
}
handled = true;
}
break;
case Key.Y:
if (modifiers == InputModifiers.Control)
{
try
{
_isUndoingRedoing = true;
_undoRedoHelper.Redo();
}
finally
{
_isUndoingRedoing = false;
}
handled = true;
}
break;
case Key.Left:
MoveHorizontal(-1, modifiers);
movement = true;
break;
handled = true;
}
else if (Match(keymap.Paste))
{
case Key.Right:
MoveHorizontal(1, modifiers);
movement = true;
break;
Paste();
handled = true;
}
else if (Match(keymap.Undo))
{
case Key.Up:
movement = MoveVertical(-1, modifiers);
break;
try
{
_isUndoingRedoing = true;
_undoRedoHelper.Undo();
}
finally
{
_isUndoingRedoing = false;
}
case Key.Down:
movement = MoveVertical(1, modifiers);
break;
handled = true;
}
else if (Match(keymap.Redo))
{
try
{
_isUndoingRedoing = true;
_undoRedoHelper.Redo();
}
finally
{
_isUndoingRedoing = false;
}
case Key.Home:
MoveHome(modifiers);
movement = true;
break;
handled = true;
}
else if (Match(keymap.MoveCursorToTheStartOfDocument))
{
MoveHome(true);
movement = true;
selection = false;
handled = true;
}
else if (Match(keymap.MoveCursorToTheEndOfDocument))
{
MoveEnd(true);
movement = true;
selection = false;
handled = true;
}
else if (Match(keymap.MoveCursorToTheStartOfLine))
{
MoveHome(false);
movement = true;
selection = false;
handled = true;
}
else if (Match(keymap.MoveCursorToTheEndOfLine))
{
MoveEnd(false);
movement = true;
selection = false;
handled = true;
}
else if (Match(keymap.MoveCursorToTheStartOfDocumentWithSelection))
{
MoveHome(true);
movement = true;
selection = true;
handled = true;
}
else if (Match(keymap.MoveCursorToTheEndOfDocumentWithSelection))
{
MoveEnd(true);
movement = true;
selection = true;
handled = true;
}
else if (Match(keymap.MoveCursorToTheStartOfLineWithSelection))
{
MoveHome(false);
movement = true;
selection = true;
handled = true;
}
else if (Match(keymap.MoveCursorToTheEndOfLineWithSelection))
{
MoveEnd(false);
movement = true;
selection = true;
handled = true;
}
else
{
bool hasWholeWordModifiers = modifiers.HasFlag(keymap.WholeWordTextActionModifiers);
switch (e.Key)
{
case Key.Left:
MoveHorizontal(-1, hasWholeWordModifiers);
movement = true;
selection = DetectSelection();
break;
case Key.End:
MoveEnd(modifiers);
movement = true;
break;
case Key.Right:
MoveHorizontal(1, hasWholeWordModifiers);
movement = true;
selection = DetectSelection();
break;
case Key.Back:
if (modifiers == InputModifiers.Control && SelectionStart == SelectionEnd)
{
SetSelectionForControlBackspace(modifiers);
}
case Key.Up:
movement = MoveVertical(-1);
selection = DetectSelection();
break;
if (!DeleteSelection() && CaretIndex > 0)
{
var removedCharacters = 1;
// handle deleting /r/n
// you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
// a /r should also be deleted.
if (CaretIndex > 1 &&
text[CaretIndex - 1] == '\n' &&
text[CaretIndex - 2] == '\r')
case Key.Down:
movement = MoveVertical(1);
selection = DetectSelection();
break;
case Key.Back:
if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
{
removedCharacters = 2;
SetSelectionForControlBackspace();
}
SetTextInternal(text.Substring(0, caretIndex - removedCharacters) + text.Substring(caretIndex));
CaretIndex -= removedCharacters;
SelectionStart = SelectionEnd = CaretIndex;
}
handled = true;
break;
if (!DeleteSelection() && CaretIndex > 0)
{
var removedCharacters = 1;
// handle deleting /r/n
// you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
// a /r should also be deleted.
if (CaretIndex > 1 &&
text[CaretIndex - 1] == '\n' &&
text[CaretIndex - 2] == '\r')
{
removedCharacters = 2;
}
case Key.Delete:
if (modifiers == InputModifiers.Control && SelectionStart == SelectionEnd)
{
SetSelectionForControlDelete(modifiers);
}
SetTextInternal(text.Substring(0, caretIndex - removedCharacters) +
text.Substring(caretIndex));
CaretIndex -= removedCharacters;
SelectionStart = SelectionEnd = CaretIndex;
}
if (!DeleteSelection() && caretIndex < text.Length)
{
var removedCharacters = 1;
// handle deleting /r/n
// you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
// a /r should also be deleted.
if (CaretIndex < text.Length - 1 &&
text[caretIndex + 1] == '\n' &&
text[caretIndex] == '\r')
handled = true;
break;
case Key.Delete:
if (hasWholeWordModifiers && SelectionStart == SelectionEnd)
{
removedCharacters = 2;
SetSelectionForControlDelete();
}
SetTextInternal(text.Substring(0, caretIndex) + text.Substring(caretIndex + removedCharacters));
}
handled = true;
break;
if (!DeleteSelection() && caretIndex < text.Length)
{
var removedCharacters = 1;
// handle deleting /r/n
// you don't ever want to leave a dangling /r around. So, if deleting /n, check to see if
// a /r should also be deleted.
if (CaretIndex < text.Length - 1 &&
text[caretIndex + 1] == '\n' &&
text[caretIndex] == '\r')
{
removedCharacters = 2;
}
SetTextInternal(text.Substring(0, caretIndex) +
text.Substring(caretIndex + removedCharacters));
}
case Key.Enter:
if (AcceptsReturn)
{
HandleTextInput(NewLine);
handled = true;
}
break;
break;
case Key.Enter:
if (AcceptsReturn)
{
HandleTextInput(NewLine);
handled = true;
}
case Key.Tab:
if (AcceptsTab)
{
HandleTextInput("\t");
handled = true;
}
else
{
base.OnKeyDown(e);
}
break;
break;
case Key.Tab:
if (AcceptsTab)
{
HandleTextInput("\t");
handled = true;
}
else
{
base.OnKeyDown(e);
}
default:
handled = false;
break;
break;
default:
handled = false;
break;
}
}
if (movement && ((modifiers & InputModifiers.Shift) != 0))
if (movement && selection)
{
SelectionEnd = CaretIndex;
}
@ -666,12 +722,12 @@ namespace Avalonia.Controls
return result;
}
private void MoveHorizontal(int direction, InputModifiers modifiers)
private void MoveHorizontal(int direction, bool wholeWord)
{
var text = Text ?? string.Empty;
var caretIndex = CaretIndex;
if ((modifiers & InputModifiers.Control) == 0)
if (!wholeWord)
{
var index = caretIndex + direction;
@ -709,7 +765,7 @@ namespace Avalonia.Controls
}
}
private bool MoveVertical(int count, InputModifiers modifiers)
private bool MoveVertical(int count)
{
var formattedText = _presenter.FormattedText;
var lines = formattedText.GetLines().ToList();
@ -732,12 +788,12 @@ namespace Avalonia.Controls
}
}
private void MoveHome(InputModifiers modifiers)
private void MoveHome(bool document)
{
var text = Text ?? string.Empty;
var caretIndex = CaretIndex;
if ((modifiers & InputModifiers.Control) != 0)
if (document)
{
caretIndex = 0;
}
@ -762,12 +818,12 @@ namespace Avalonia.Controls
CaretIndex = caretIndex;
}
private void MoveEnd(InputModifiers modifiers)
private void MoveEnd(bool document)
{
var text = Text ?? string.Empty;
var caretIndex = CaretIndex;
if ((modifiers & InputModifiers.Control) != 0)
if (document)
{
caretIndex = text.Length;
}
@ -882,17 +938,17 @@ namespace Avalonia.Controls
}
}
private void SetSelectionForControlBackspace(InputModifiers modifiers)
private void SetSelectionForControlBackspace()
{
SelectionStart = CaretIndex;
MoveHorizontal(-1, modifiers);
MoveHorizontal(-1, true);
SelectionEnd = CaretIndex;
}
private void SetSelectionForControlDelete(InputModifiers modifiers)
private void SetSelectionForControlDelete()
{
SelectionStart = CaretIndex;
MoveHorizontal(1, modifiers);
MoveHorizontal(1, true);
SelectionEnd = CaretIndex;
}

2
src/Avalonia.DesignerSupport/DesignWindowLoader.cs

@ -18,7 +18,7 @@ namespace Avalonia.DesignerSupport
Control control;
using (PlatformManager.DesignerMode())
{
var loader = new AvaloniaXamlLoader();
var loader = new AvaloniaXamlLoader() {IsDesignMode = true};
var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml));

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

@ -57,7 +57,8 @@ namespace Avalonia.DesignerSupport.Remote
.Bind<IRenderTimer>().ToConstant(threading)
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogsStub>()
.Bind<IWindowingPlatform>().ToConstant(instance)
.Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>();
.Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>()
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>();
}

10
src/Avalonia.DesignerSupport/Remote/RemoteDesignerEntryPoint.cs

@ -15,6 +15,8 @@ namespace Avalonia.DesignerSupport.Remote
{
private static ClientSupportedPixelFormatsMessage s_supportedPixelFormats;
private static ClientViewportAllocatedMessage s_viewportAllocatedMessage;
private static ClientRenderInfoMessage s_renderInfoMessage;
private static IAvaloniaRemoteTransportConnection s_transport;
class CommandLineArgs
{
@ -161,7 +163,8 @@ namespace Avalonia.DesignerSupport.Remote
PreviewerWindowingPlatform.PreFlightMessages = new List<object>
{
s_supportedPixelFormats,
s_viewportAllocatedMessage
s_viewportAllocatedMessage,
s_renderInfoMessage
};
}
@ -173,6 +176,11 @@ namespace Avalonia.DesignerSupport.Remote
s_supportedPixelFormats = formats;
RebuildPreFlight();
}
if (obj is ClientRenderInfoMessage renderInfo)
{
s_renderInfoMessage = renderInfo;
RebuildPreFlight();
}
if (obj is ClientViewportAllocatedMessage viewport)
{
s_viewportAllocatedMessage = viewport;

4
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -1,4 +1,4 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Reactive.Disposables;
@ -20,7 +20,7 @@ namespace Avalonia.DesignerSupport.Remote
public IPlatformHandle Handle { get; }
public Size MaxClientSize { get; }
public Size ClientSize { get; }
public double Scaling { get; }
public double Scaling { get; } = 1.0;
public IEnumerable<object> Surfaces { get; }
public Action<RawInputEventArgs> Input { get; set; }
public Action<Rect> Paint { get; set; }

18
src/Avalonia.Input/Key.cs

@ -1020,5 +1020,23 @@ namespace Avalonia.Input
/// The key is used with another key to create a single combined character.
/// </summary>
DeadCharProcessed = 172,
/// <summary>
/// OSX Platform-specific Fn+Left key
/// </summary>
FnLeftArrow = 10001,
/// <summary>
/// OSX Platform-specific Fn+Right key
/// </summary>
FnRightArrow = 10002,
/// <summary>
/// OSX Platform-specific Fn+Up key
/// </summary>
FnUpArrow = 10003,
/// <summary>
/// OSX Platform-specific Fn+Down key
/// </summary>
FnDownArrow = 10004,
}
}

11
src/Avalonia.Input/KeyGesture.cs

@ -6,6 +6,17 @@ namespace Avalonia.Input
{
public sealed class KeyGesture : IEquatable<KeyGesture>
{
public KeyGesture()
{
}
public KeyGesture(Key key, InputModifiers modifiers = InputModifiers.None)
{
Key = key;
Modifiers = modifiers;
}
public bool Equals(KeyGesture other)
{
if (ReferenceEquals(null, other)) return false;

98
src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs

@ -0,0 +1,98 @@
using System.Collections.Generic;
namespace Avalonia.Input.Platform
{
public class PlatformHotkeyConfiguration
{
public PlatformHotkeyConfiguration() : this(InputModifiers.Control)
{
}
public PlatformHotkeyConfiguration(InputModifiers commandModifiers,
InputModifiers selectionModifiers = InputModifiers.Shift,
InputModifiers wholeWordTextActionModifiers = InputModifiers.Control)
{
CommandModifiers = commandModifiers;
SelectionModifiers = selectionModifiers;
WholeWordTextActionModifiers = wholeWordTextActionModifiers;
Copy = new List<KeyGesture>
{
new KeyGesture(Key.C, commandModifiers)
};
Cut = new List<KeyGesture>
{
new KeyGesture(Key.X, commandModifiers)
};
Paste = new List<KeyGesture>
{
new KeyGesture(Key.V, commandModifiers)
};
Undo = new List<KeyGesture>
{
new KeyGesture(Key.Z, commandModifiers)
};
Redo = new List<KeyGesture>
{
new KeyGesture(Key.Y, commandModifiers),
new KeyGesture(Key.Z, commandModifiers | selectionModifiers)
};
SelectAll = new List<KeyGesture>
{
new KeyGesture(Key.A, commandModifiers)
};
MoveCursorToTheStartOfLine = new List<KeyGesture>
{
new KeyGesture(Key.Home)
};
MoveCursorToTheEndOfLine = new List<KeyGesture>
{
new KeyGesture(Key.End)
};
MoveCursorToTheStartOfDocument = new List<KeyGesture>
{
new KeyGesture(Key.Home, commandModifiers)
};
MoveCursorToTheEndOfDocument = new List<KeyGesture>
{
new KeyGesture(Key.End, commandModifiers)
};
MoveCursorToTheStartOfLineWithSelection = new List<KeyGesture>
{
new KeyGesture(Key.Home, selectionModifiers)
};
MoveCursorToTheEndOfLineWithSelection = new List<KeyGesture>
{
new KeyGesture(Key.End, selectionModifiers)
};
MoveCursorToTheStartOfDocumentWithSelection = new List<KeyGesture>
{
new KeyGesture(Key.Home, commandModifiers | selectionModifiers)
};
MoveCursorToTheEndOfDocumentWithSelection = new List<KeyGesture>
{
new KeyGesture(Key.End, commandModifiers | selectionModifiers)
};
}
public InputModifiers CommandModifiers { get; set; }
public InputModifiers WholeWordTextActionModifiers { get; set; }
public InputModifiers SelectionModifiers { get; set; }
public List<KeyGesture> Copy { get; set; }
public List<KeyGesture> Cut { get; set; }
public List<KeyGesture> Paste { get; set; }
public List<KeyGesture> Undo { get; set; }
public List<KeyGesture> Redo { get; set; }
public List<KeyGesture> SelectAll { get; set; }
public List<KeyGesture> MoveCursorToTheStartOfLine { get; set; }
public List<KeyGesture> MoveCursorToTheEndOfLine { get; set; }
public List<KeyGesture> MoveCursorToTheStartOfDocument { get; set; }
public List<KeyGesture> MoveCursorToTheEndOfDocument { get; set; }
public List<KeyGesture> MoveCursorToTheStartOfLineWithSelection { get; set; }
public List<KeyGesture> MoveCursorToTheEndOfLineWithSelection { get; set; }
public List<KeyGesture> MoveCursorToTheStartOfDocumentWithSelection { get; set; }
public List<KeyGesture> MoveCursorToTheEndOfDocumentWithSelection { get; set; }
}
}

6
src/Avalonia.Remote.Protocol/InputMessages.cs

@ -75,4 +75,10 @@ namespace Avalonia.Remote.Protocol.Input
public Key Key { get; set; }
}
[AvaloniaRemoteMessageGuid("C174102E-7405-4594-916F-B10B8248A17D")]
public class TextInputEventMessage : InputEventMessageBase
{
public string Text { get; set; }
}
}

7
src/Avalonia.Remote.Protocol/ViewportMessages.cs

@ -37,6 +37,13 @@
public PixelFormat[] Formats { get; set; }
}
[AvaloniaRemoteMessageGuid("7A3c25d3-3652-438D-8EF1-86E942CC96C0")]
public class ClientRenderInfoMessage
{
public double DpiX { get; set; }
public double DpiY { get; set; }
}
[AvaloniaRemoteMessageGuid("68014F8A-289D-4851-8D34-5367EDA7F827")]
public class FrameReceivedMessage
{

2
src/Avalonia.Styling/Styling/IStyle.cs

@ -18,5 +18,7 @@ namespace Avalonia.Styling
/// The control that contains this style. May be null.
/// </param>
void Attach(IStyleable control, IStyleHost container);
void Detach();
}
}

52
src/Avalonia.Styling/Styling/Style.cs

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using Avalonia.Animation;
using Avalonia.Controls;
@ -15,9 +16,12 @@ namespace Avalonia.Styling
/// </summary>
public class Style : AvaloniaObject, IStyle, ISetStyleParent
{
private static Dictionary<IStyleable, List<IDisposable>> _applied =
new Dictionary<IStyleable, List<IDisposable>>();
private static Dictionary<IStyleable, CompositeDisposable> _applied =
new Dictionary<IStyleable, CompositeDisposable>();
private IResourceNode _parent;
private CompositeDisposable _subscriptions;
private IResourceDictionary _resources;
private IList<IAnimation> _animations;
@ -88,6 +92,14 @@ namespace Avalonia.Styling
}
}
private CompositeDisposable Subscriptions
{
get
{
return _subscriptions ?? (_subscriptions = new CompositeDisposable(2));
}
}
/// <inheritdoc/>
IResourceNode IResourceNode.ResourceParent => _parent;
@ -109,7 +121,9 @@ namespace Avalonia.Styling
if (match.ImmediateResult != false)
{
var subs = GetSubscriptions(control);
var controlSubscriptions = GetSubscriptions(control);
var subs = new CompositeDisposable(Setters.Count + Animations.Count);
if (control is Animatable animatable)
{
@ -132,17 +146,25 @@ namespace Avalonia.Styling
var sub = setter.Apply(this, control, match.ObservableResult);
subs.Add(sub);
}
controlSubscriptions.Add(subs);
Subscriptions.Add(subs);
}
}
else if (control == container)
{
var subs = GetSubscriptions(control);
var controlSubscriptions = GetSubscriptions(control);
var subs = new CompositeDisposable(Setters.Count);
foreach (var setter in Setters)
{
var sub = setter.Apply(this, control, null);
subs.Add(sub);
}
controlSubscriptions.Add(subs);
Subscriptions.Add(subs);
}
}
@ -183,16 +205,25 @@ namespace Avalonia.Styling
throw new InvalidOperationException("The Style already has a parent.");
}
if (parent == null)
{
Detach();
}
_parent = parent;
}
private static List<IDisposable> GetSubscriptions(IStyleable control)
public void Detach()
{
List<IDisposable> subscriptions;
_subscriptions?.Dispose();
_subscriptions = null;
}
if (!_applied.TryGetValue(control, out subscriptions))
private static CompositeDisposable GetSubscriptions(IStyleable control)
{
if (!_applied.TryGetValue(control, out var subscriptions))
{
subscriptions = new List<IDisposable>(2);
subscriptions = new CompositeDisposable(2);
subscriptions.Add(control.StyleDetach.Subscribe(ControlDetach));
_applied.Add(control, subscriptions);
}
@ -209,10 +240,7 @@ namespace Avalonia.Styling
{
var subscriptions = _applied[control];
foreach (var subscription in subscriptions)
{
subscription.Dispose();
}
subscriptions.Dispose();
_applied.Remove(control);
}

8
src/Avalonia.Styling/Styling/Styles.cs

@ -105,6 +105,14 @@ namespace Avalonia.Styling
}
}
public void Detach()
{
foreach (IStyle style in this)
{
style.Detach();
}
}
/// <inheritdoc/>
public bool TryGetResource(string key, out object value)
{

9
src/Avalonia.Styling/Styling/packages.config

@ -1,9 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Rx-Core" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" />
<package id="Rx-Interfaces" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" />
<package id="Rx-Linq" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" />
<package id="Rx-Main" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" />
<package id="Rx-PlatformServices" version="2.2.5" targetFramework="portable-net45+win8" userInstalled="true" />
<package id="Splat" version="1.6.2" targetFramework="portable45-net45+win8" userInstalled="true" />
</packages>

1
src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs

@ -81,6 +81,7 @@ namespace Avalonia.Gtk3
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialog>()
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<IRenderTimer>().ToConstant(new DefaultRenderTimer(60))
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IPlatformIconLoader>().ToConstant(new PlatformIconLoader());
if (useGpu)
EglGlPlatformFeature.TryInitialize();

2
src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs

@ -126,7 +126,7 @@ namespace Avalonia.Gtk3
if (state.HasFlag(GdkModifierType.ShiftMask))
rv |= InputModifiers.Shift;
if (state.HasFlag(GdkModifierType.Mod1Mask))
rv |= InputModifiers.Control;
rv |= InputModifiers.Alt;
if (state.HasFlag(GdkModifierType.Button1Mask))
rv |= InputModifiers.LeftMouseButton;
if (state.HasFlag(GdkModifierType.Button2Mask))

2
src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs

@ -5,6 +5,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Embedding;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.LinuxFramebuffer;
using Avalonia.Platform;
using Avalonia.Rendering;
@ -36,6 +37,7 @@ namespace Avalonia.LinuxFramebuffer
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
.Bind<IPlatformThreadingInterface>().ToConstant(Threading)
.Bind<IRenderLoop>().ToConstant(new RenderLoop())
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IRenderTimer>().ToConstant(Threading);
}

10
src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs

@ -21,6 +21,12 @@ namespace Avalonia.Markup.Xaml
{
private readonly AvaloniaXamlSchemaContext _context = GetContext();
public bool IsDesignMode
{
get => _context.IsDesignMode;
set => _context.IsDesignMode = value;
}
private static AvaloniaXamlSchemaContext GetContext()
{
var result = AvaloniaLocator.Current.GetService<AvaloniaXamlSchemaContext>();
@ -200,7 +206,7 @@ namespace Avalonia.Markup.Xaml
internal static object LoadFromReader(XamlReader reader, AvaloniaXamlContext context = null, IAmbientProvider parentAmbientProvider = null)
{
var writer = AvaloniaXamlObjectWriter.Create(
reader.SchemaContext,
(AvaloniaXamlSchemaContext)reader.SchemaContext,
context,
parentAmbientProvider);
@ -234,4 +240,4 @@ namespace Avalonia.Markup.Xaml
public static T Parse<T>(string xaml, Assembly localAssembly = null)
=> (T)Parse(xaml, localAssembly);
}
}
}

37
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlObjectWriter.cs

@ -4,13 +4,27 @@ using Portable.Xaml.ComponentModel;
using System.ComponentModel;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Avalonia.Controls;
using Portable.Xaml.Schema;
namespace Avalonia.Markup.Xaml.PortableXaml
{
public class AvaloniaXamlObjectWriter : XamlObjectWriter
class AvaloniaXamlObjectWriter : XamlObjectWriter
{
private static Dictionary<XamlDirective, string> DesignDirectives = new Dictionary<string, string>
{
["DataContext"] = "DataContext",
["DesignWidth"] = "Width", ["DesignHeight"] = "Height", ["PreviewWith"] = "PreviewWith"
}
.ToDictionary(p => new XamlDirective(
new[] {"http://schemas.microsoft.com/expression/blend/2008"}, p.Key,
XamlLanguage.Object, null, AllowedMemberLocations.Attribute), p => p.Value);
private readonly AvaloniaXamlSchemaContext _schemaContext;
public static AvaloniaXamlObjectWriter Create(
XamlSchemaContext schemaContext,
AvaloniaXamlSchemaContext schemaContext,
AvaloniaXamlContext context,
IAmbientProvider parentAmbientProvider = null)
{
@ -34,13 +48,14 @@ namespace Avalonia.Markup.Xaml.PortableXaml
private AvaloniaNameScope _nameScope;
private AvaloniaXamlObjectWriter(
XamlSchemaContext schemaContext,
AvaloniaXamlSchemaContext schemaContext,
XamlObjectWriterSettings settings,
AvaloniaNameScope nameScope,
IAmbientProvider parentAmbientProvider)
: base(schemaContext, settings, parentAmbientProvider)
{
_nameScope = nameScope;
_schemaContext = schemaContext;
}
protected override void Dispose(bool disposing)
@ -122,6 +137,20 @@ namespace Avalonia.Markup.Xaml.PortableXaml
(value as Avalonia.ISupportInitialize)?.EndInit();
}
public override void WriteStartMember(XamlMember property)
{
foreach(var d in DesignDirectives)
if (property == d.Key && _schemaContext.IsDesignMode)
{
base.WriteStartMember(new XamlMember(d.Value,
typeof(Design).GetMethod("Get" + d.Value, BindingFlags.Static | BindingFlags.Public),
typeof(Design).GetMethod("Set" + d.Value, BindingFlags.Static | BindingFlags.Public),
SchemaContext));
return;
}
base.WriteStartMember(property);
}
private class DelayedValuesHelper
{
private int _cnt;
@ -225,4 +254,4 @@ namespace Avalonia.Markup.Xaml.PortableXaml
}
}
}
}
}

18
src/Markup/Avalonia.Markup.Xaml/PortableXaml/AvaloniaXamlSchemaContext.cs

@ -15,6 +15,7 @@ namespace Avalonia.Markup.Xaml.PortableXaml
{
internal class AvaloniaXamlSchemaContext : XamlSchemaContext
{
public bool IsDesignMode { get; set; }
public static AvaloniaXamlSchemaContext Create(IRuntimeTypeProvider typeProvider = null)
{
return new AvaloniaXamlSchemaContext(typeProvider ?? new AvaloniaRuntimeTypeProvider());
@ -280,5 +281,20 @@ namespace Avalonia.Markup.Xaml.PortableXaml
return $"{MemberType}:{Type.Namespace}:{Type.Name}.{Member}";
}
}
public override bool TryGetCompatibleXamlNamespace(string xamlNamespace, out string compatibleNamespace)
{
//Forces XamlXmlReader to not ignore our namespace in design mode if mc:Ignorable is set
if (IsDesignMode &&
xamlNamespace == "http://schemas.microsoft.com/expression/blend/2008")
{
compatibleNamespace = xamlNamespace;
return true;
}
return base.TryGetCompatibleXamlNamespace(xamlNamespace, out compatibleNamespace);
}
}
}
}

10
src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs

@ -70,6 +70,14 @@ namespace Avalonia.Markup.Xaml.Styling
}
}
public void Detach()
{
if (Source != null)
{
Loaded.Detach();
}
}
/// <inheritdoc/>
public bool TryGetResource(string key, out object value) => Loaded.TryGetResource(key, out value);
@ -90,4 +98,4 @@ namespace Avalonia.Markup.Xaml.Styling
_parent = parent;
}
}
}
}

1
src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs

@ -37,6 +37,7 @@ namespace Avalonia.MonoMac
.Bind<IClipboard>().ToSingleton<ClipboardImpl>()
.Bind<IRenderLoop>().ToConstant(s_renderLoop)
.Bind<IRenderTimer>().ToConstant(s_renderTimer)
.Bind<PlatformHotkeyConfiguration>().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows))
.Bind<IPlatformThreadingInterface>().ToConstant(PlatformThreadingInterface.Instance)
/*.Bind<IPlatformDragSource>().ToTransient<DragSource>()*/;
}

1
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -89,6 +89,7 @@ namespace Avalonia.Win32
.Bind<IRenderTimer>().ToConstant(new RenderTimer(60))
.Bind<ISystemDialogImpl>().ToSingleton<SystemDialogImpl>()
.Bind<IWindowingPlatform>().ToConstant(s_instance)
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IPlatformIconLoader>().ToConstant(s_instance);
Win32GlManager.Initialize();
UseDeferredRendering = deferredRendering;

1
src/iOS/Avalonia.iOS/iOSPlatform.cs

@ -42,6 +42,7 @@ namespace Avalonia.iOS
.Bind<IPlatformIconLoader>().ToSingleton<PlatformIconLoader>()
.Bind<IWindowingPlatform>().ToSingleton<WindowingPlatformImpl>()
.Bind<IRenderTimer>().ToSingleton<DisplayLinkRenderTimer>()
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IRenderLoop>().ToSingleton<RenderLoop>();
}
}

18
tests/Avalonia.Controls.UnitTests/ApplicationTests.cs

@ -113,5 +113,21 @@ namespace Avalonia.Controls.UnitTests
Assert.Throws<ArgumentNullException>(() => { Application.Current.Run(null); });
}
}
[Fact]
public void Raises_ResourcesChanged_When_Event_Handler_Added_After_Resources_Has_Been_Accessed()
{
// Test for #1765.
using (UnitTestApplication.Start())
{
var resources = Application.Current.Resources;
var raised = false;
Application.Current.ResourcesChanged += (s, e) => raised = true;
resources["foo"] = "bar";
Assert.True(raised);
}
}
}
}
}

41
tests/Avalonia.Markup.Xaml.UnitTests/Xaml/BasicTests.cs

@ -924,6 +924,45 @@ do we need it?")]
}
}
[Fact]
public void Design_Mode_Properties_Should_Be_Ignored_At_Runtime_And_Set_In_Design_Mode()
{
using (UnitTestApplication.Start(TestServices.MockWindowingPlatform))
{
var xaml = @"
<Window xmlns='https://github.com/avaloniaui'
xmlns:d='http://schemas.microsoft.com/expression/blend/2008'
xmlns:mc='http://schemas.openxmlformats.org/markup-compatibility/2006'
mc:Ignorable='d'
d:DataContext='data-context'
d:DesignWidth='123'
d:DesignHeight='321'
>
</Window>";
foreach (var designMode in new[] {true, false})
{
var loader = new AvaloniaXamlLoader {IsDesignMode = designMode};
var obj = (Window)loader.Load(xaml);
var context = Design.GetDataContext(obj);
var width = Design.GetWidth(obj);
var height = Design.GetHeight(obj);
if (designMode)
{
Assert.Equal("data-context", context);
Assert.Equal(123, width);
Assert.Equal(321, height);
}
else
{
Assert.False(obj.IsSet(Design.DataContextProperty));
Assert.False(obj.IsSet(Design.WidthProperty));
Assert.False(obj.IsSet(Design.HeightProperty));
}
}
}
}
private class SelectedItemsViewModel : INotifyPropertyChanged
{
public string[] Items { get; set; }
@ -952,4 +991,4 @@ do we need it?")]
public static string GetFoo(AvaloniaObject target) => (string)target.GetValue(FooProperty);
}
}
}

25
tests/Avalonia.Styling.UnitTests/StyleTests.cs

@ -167,6 +167,31 @@ namespace Avalonia.Styling.UnitTests
Assert.Equal(new Thickness(0), border.BorderThickness);
}
[Fact]
public void Style_Should_Detach_Setters_When_Detach_Is_Called()
{
Border border;
var style = new Style(x => x.OfType<Border>())
{
Setters = new[]
{
new Setter(Border.BorderThicknessProperty, new Thickness(4)),
}
};
var root = new TestRoot
{
Child = border = new Border(),
};
style.Attach(border, null);
Assert.Equal(new Thickness(4), border.BorderThickness);
style.Detach();
Assert.Equal(new Thickness(0), border.BorderThickness);
}
private class Class1 : Control
{
public static readonly StyledProperty<string> FooProperty =

2
tests/Avalonia.UnitTests/UnitTestApplication.cs

@ -11,6 +11,7 @@ using Avalonia.Rendering;
using Avalonia.Threading;
using System.Reactive.Disposables;
using System.Reactive.Concurrency;
using Avalonia.Input.Platform;
namespace Avalonia.UnitTests
{
@ -58,6 +59,7 @@ namespace Avalonia.UnitTests
.Bind<IStandardCursorFactory>().ToConstant(Services.StandardCursorFactory)
.Bind<IStyler>().ToConstant(Services.Styler)
.Bind<IWindowingPlatform>().ToConstant(Services.WindowingPlatform)
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
.Bind<IApplicationLifecycle>().ToConstant(this);
var styles = Services.Theme?.Invoke();

Loading…
Cancel
Save