Browse Source

Platform-specific key gestures

pull/1965/head
Nikita Tsukanov 7 years ago
parent
commit
e00f0f0385
  1. 1
      src/Android/Avalonia.Android/AndroidPlatform.cs
  2. 363
      src/Avalonia.Controls/TextBox.cs
  3. 18
      src/Avalonia.Input/Key.cs
  4. 11
      src/Avalonia.Input/KeyGesture.cs
  5. 94
      src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs
  6. 1
      src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs
  7. 2
      src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
  8. 1
      src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs
  9. 1
      src/Windows/Avalonia.Win32/Win32Platform.cs
  10. 1
      src/iOS/Avalonia.iOS/iOSPlatform.cs
  11. 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();

363
src/Avalonia.Controls/TextBox.cs

@ -368,186 +368,239 @@ 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
switch (e.Key)
{
case Key.Left:
MoveHorizontal(-1, modifiers);
movement = true;
selection = DetectSelection();
break;
case Key.End:
MoveEnd(modifiers);
movement = true;
break;
case Key.Right:
MoveHorizontal(1, modifiers);
movement = true;
selection = DetectSelection();
break;
case Key.Back:
if (modifiers == InputModifiers.Control && SelectionStart == SelectionEnd)
{
SetSelectionForControlBackspace(modifiers);
}
case Key.Up:
movement = MoveVertical(-1, modifiers);
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, modifiers);
selection = DetectSelection();
break;
case Key.Back:
if (modifiers == keymap.CommandModifiers && SelectionStart == SelectionEnd)
{
removedCharacters = 2;
SetSelectionForControlBackspace(modifiers);
}
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 (modifiers == keymap.CommandModifiers && SelectionStart == SelectionEnd)
{
removedCharacters = 2;
SetSelectionForControlDelete(modifiers);
}
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;
}
@ -732,12 +785,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 +815,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;
}

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;

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

@ -0,0 +1,94 @@
using System.Collections.Generic;
namespace Avalonia.Input.Platform
{
public class PlatformHotkeyConfiguration
{
public PlatformHotkeyConfiguration() : this(InputModifiers.Control)
{
}
public PlatformHotkeyConfiguration(InputModifiers commandModifiers, InputModifiers selectionModifiers = InputModifiers.Shift)
{
CommandModifiers = commandModifiers;
SelectionModifiers = selectionModifiers;
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 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; }
}
}

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/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);
}

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>();
}
}

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