diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 5f0edadf63..4e48811c35 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -54,6 +54,7 @@ namespace Avalonia.Android .Bind().ToSingleton() .Bind().ToConstant(new DefaultRenderTimer(60)) .Bind().ToConstant(new RenderLoop()) + .Bind().ToSingleton() .Bind().ToConstant(new AssetLoader(app.GetType().Assembly)); SkiaPlatform.Initialize(); diff --git a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs index b302f2f5ec..c1ef9476db 100644 --- a/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs +++ b/src/Avalonia.Controls/Remote/Server/RemoteServerTopLevelImpl.cs @@ -102,6 +102,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]; diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 388f984b78..d6c3a1041e 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -212,7 +212,10 @@ namespace Avalonia.Controls { if (!_ignoreTextChanges) { - CaretIndex = CoerceCaretIndex(CaretIndex, value?.Length ?? 0); + var caretIndex = CaretIndex; + SelectionStart = CoerceCaretIndex(SelectionStart, value?.Length ?? 0); + SelectionEnd = CoerceCaretIndex(SelectionEnd, value?.Length ?? 0); + CaretIndex = CoerceCaretIndex(caretIndex, value?.Length ?? 0); if (SetAndRaise(TextProperty, ref _text, value) && !_isUndoingRedoing) { @@ -365,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(); - case Key.X: - if (modifiers == InputModifiers.Control) - { - if (!IsPasswordBox) - { - Copy(); - DeleteSelection(); - } - handled = true; - } - break; + bool Match(List 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; } @@ -663,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; @@ -706,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(); @@ -729,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; } @@ -759,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; } @@ -879,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; } diff --git a/src/Avalonia.Input/Key.cs b/src/Avalonia.Input/Key.cs index d4f95dbd5c..d51b990fd4 100644 --- a/src/Avalonia.Input/Key.cs +++ b/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. /// DeadCharProcessed = 172, + + + /// + /// OSX Platform-specific Fn+Left key + /// + FnLeftArrow = 10001, + /// + /// OSX Platform-specific Fn+Right key + /// + FnRightArrow = 10002, + /// + /// OSX Platform-specific Fn+Up key + /// + FnUpArrow = 10003, + /// + /// OSX Platform-specific Fn+Down key + /// + FnDownArrow = 10004, } } diff --git a/src/Avalonia.Input/KeyGesture.cs b/src/Avalonia.Input/KeyGesture.cs index bb3b15c64f..c945902def 100644 --- a/src/Avalonia.Input/KeyGesture.cs +++ b/src/Avalonia.Input/KeyGesture.cs @@ -6,6 +6,17 @@ namespace Avalonia.Input { public sealed class KeyGesture : IEquatable { + 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; diff --git a/src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs b/src/Avalonia.Input/Platform/PlatformHotkeyConfiguration.cs new file mode 100644 index 0000000000..a758a328be --- /dev/null +++ b/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 + { + new KeyGesture(Key.C, commandModifiers) + }; + Cut = new List + { + new KeyGesture(Key.X, commandModifiers) + }; + Paste = new List + { + new KeyGesture(Key.V, commandModifiers) + }; + Undo = new List + { + new KeyGesture(Key.Z, commandModifiers) + }; + Redo = new List + { + new KeyGesture(Key.Y, commandModifiers), + new KeyGesture(Key.Z, commandModifiers | selectionModifiers) + }; + SelectAll = new List + { + new KeyGesture(Key.A, commandModifiers) + }; + MoveCursorToTheStartOfLine = new List + { + new KeyGesture(Key.Home) + }; + MoveCursorToTheEndOfLine = new List + { + new KeyGesture(Key.End) + }; + MoveCursorToTheStartOfDocument = new List + { + new KeyGesture(Key.Home, commandModifiers) + }; + MoveCursorToTheEndOfDocument = new List + { + new KeyGesture(Key.End, commandModifiers) + }; + MoveCursorToTheStartOfLineWithSelection = new List + { + new KeyGesture(Key.Home, selectionModifiers) + }; + MoveCursorToTheEndOfLineWithSelection = new List + { + new KeyGesture(Key.End, selectionModifiers) + }; + MoveCursorToTheStartOfDocumentWithSelection = new List + { + new KeyGesture(Key.Home, commandModifiers | selectionModifiers) + }; + MoveCursorToTheEndOfDocumentWithSelection = new List + { + new KeyGesture(Key.End, commandModifiers | selectionModifiers) + }; + } + + public InputModifiers CommandModifiers { get; set; } + public InputModifiers WholeWordTextActionModifiers { get; set; } + public InputModifiers SelectionModifiers { get; set; } + public List Copy { get; set; } + public List Cut { get; set; } + public List Paste { get; set; } + public List Undo { get; set; } + public List Redo { get; set; } + public List SelectAll { get; set; } + public List MoveCursorToTheStartOfLine { get; set; } + public List MoveCursorToTheEndOfLine { get; set; } + public List MoveCursorToTheStartOfDocument { get; set; } + public List MoveCursorToTheEndOfDocument { get; set; } + public List MoveCursorToTheStartOfLineWithSelection { get; set; } + public List MoveCursorToTheEndOfLineWithSelection { get; set; } + public List MoveCursorToTheStartOfDocumentWithSelection { get; set; } + public List MoveCursorToTheEndOfDocumentWithSelection { get; set; } + + + } +} diff --git a/src/Avalonia.Styling/Styling/IStyle.cs b/src/Avalonia.Styling/Styling/IStyle.cs index 5f12763825..3cf8491ae3 100644 --- a/src/Avalonia.Styling/Styling/IStyle.cs +++ b/src/Avalonia.Styling/Styling/IStyle.cs @@ -18,5 +18,7 @@ namespace Avalonia.Styling /// The control that contains this style. May be null. /// void Attach(IStyleable control, IStyleHost container); + + void Detach(); } } diff --git a/src/Avalonia.Styling/Styling/Style.cs b/src/Avalonia.Styling/Styling/Style.cs index 067bb59fe9..23c318a809 100644 --- a/src/Avalonia.Styling/Styling/Style.cs +++ b/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 /// public class Style : AvaloniaObject, IStyle, ISetStyleParent { - private static Dictionary> _applied = - new Dictionary>(); + private static Dictionary _applied = + new Dictionary(); private IResourceNode _parent; + + private CompositeDisposable _subscriptions; + private IResourceDictionary _resources; private IList _animations; @@ -88,6 +92,14 @@ namespace Avalonia.Styling } } + private CompositeDisposable Subscriptions + { + get + { + return _subscriptions ?? (_subscriptions = new CompositeDisposable(2)); + } + } + /// 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 GetSubscriptions(IStyleable control) + public void Detach() { - List 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(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); } diff --git a/src/Avalonia.Styling/Styling/Styles.cs b/src/Avalonia.Styling/Styling/Styles.cs index 714e7f6def..22ed8bb241 100644 --- a/src/Avalonia.Styling/Styling/Styles.cs +++ b/src/Avalonia.Styling/Styling/Styles.cs @@ -105,6 +105,14 @@ namespace Avalonia.Styling } } + public void Detach() + { + foreach (IStyle style in this) + { + style.Detach(); + } + } + /// public bool TryGetResource(string key, out object value) { diff --git a/src/Avalonia.Styling/Styling/packages.config b/src/Avalonia.Styling/Styling/packages.config deleted file mode 100644 index aa2dbd982f..0000000000 --- a/src/Avalonia.Styling/Styling/packages.config +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - \ No newline at end of file diff --git a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs index e2e8a34676..9604e9975b 100644 --- a/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs +++ b/src/Gtk/Avalonia.Gtk3/Gtk3Platform.cs @@ -81,6 +81,7 @@ namespace Avalonia.Gtk3 .Bind().ToSingleton() .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(new DefaultRenderTimer(60)) + .Bind().ToSingleton() .Bind().ToConstant(new PlatformIconLoader()); if (useGpu) EglGlPlatformFeature.TryInitialize(); diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs index 0ec4047086..6e8cf5cb18 100644 --- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs +++ b/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)) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs index 9046c26cee..9ed06c978d 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs +++ b/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().ToSingleton() .Bind().ToConstant(Threading) .Bind().ToConstant(new RenderLoop()) + .Bind().ToSingleton() .Bind().ToConstant(Threading); } diff --git a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs b/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs index 3580b4fcb5..fc2656f236 100644 --- a/src/Markup/Avalonia.Markup.Xaml/Styling/StyleInclude.cs +++ b/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(); + } + } + /// public bool TryGetResource(string key, out object value) => Loaded.TryGetResource(key, out value); @@ -90,4 +98,4 @@ namespace Avalonia.Markup.Xaml.Styling _parent = parent; } } -} \ No newline at end of file +} diff --git a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs index 5757413b7a..0d2d4d3f9c 100644 --- a/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs +++ b/src/OSX/Avalonia.MonoMac/MonoMacPlatform.cs @@ -37,6 +37,7 @@ namespace Avalonia.MonoMac .Bind().ToSingleton() .Bind().ToConstant(s_renderLoop) .Bind().ToConstant(s_renderTimer) + .Bind().ToConstant(new PlatformHotkeyConfiguration(InputModifiers.Windows)) .Bind().ToConstant(PlatformThreadingInterface.Instance) /*.Bind().ToTransient()*/; } diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs index 5041942c63..33bee4a82a 100644 --- a/src/Windows/Avalonia.Win32/Win32Platform.cs +++ b/src/Windows/Avalonia.Win32/Win32Platform.cs @@ -89,6 +89,7 @@ namespace Avalonia.Win32 .Bind().ToConstant(new RenderTimer(60)) .Bind().ToSingleton() .Bind().ToConstant(s_instance) + .Bind().ToSingleton() .Bind().ToConstant(s_instance); Win32GlManager.Initialize(); UseDeferredRendering = deferredRendering; diff --git a/src/iOS/Avalonia.iOS/iOSPlatform.cs b/src/iOS/Avalonia.iOS/iOSPlatform.cs index 7b50040bf2..b0092bc98a 100644 --- a/src/iOS/Avalonia.iOS/iOSPlatform.cs +++ b/src/iOS/Avalonia.iOS/iOSPlatform.cs @@ -42,6 +42,7 @@ namespace Avalonia.iOS .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToSingleton() + .Bind().ToSingleton() .Bind().ToSingleton(); } } diff --git a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs index 4a8c171ecf..0d87f6d0fe 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBoxTests.cs @@ -8,7 +8,6 @@ using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Input; -using Avalonia.Markup.Data; using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; @@ -322,6 +321,71 @@ namespace Avalonia.Controls.UnitTests } } + [Fact] + public void SelectionEnd_Doesnt_Cause_Exception() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + Text = "0123456789" + }; + + target.SelectionStart = 0; + target.SelectionEnd = 9; + + target.Text = "123"; + + RaiseTextEvent(target, "456"); + + Assert.True(true); + } + } + + [Fact] + public void SelectionStart_Doesnt_Cause_Exception() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + Text = "0123456789" + }; + + target.SelectionStart = 8; + target.SelectionEnd = 9; + + target.Text = "123"; + + RaiseTextEvent(target, "456"); + + Assert.True(true); + } + } + + [Fact] + public void SelectionStartEnd_Are_Valid_AterTextChange() + { + using (UnitTestApplication.Start(Services)) + { + var target = new TextBox + { + Template = CreateTemplate(), + Text = "0123456789" + }; + + target.SelectionStart = 8; + target.SelectionEnd = 9; + + target.Text = "123"; + + Assert.True(target.SelectionStart <= "123".Length); + Assert.True(target.SelectionEnd <= "123".Length); + } + } + private static TestServices Services => TestServices.MockThreadingInterface.With( standardCursorFactory: Mock.Of()); @@ -351,6 +415,15 @@ namespace Avalonia.Controls.UnitTests }); } + private void RaiseTextEvent(TextBox textBox, string text) + { + textBox.RaiseEvent(new TextInputEventArgs + { + RoutedEvent = InputElement.TextInputEvent, + Text = text + }); + } + private class Class1 : NotifyingBase { private int _foo; diff --git a/tests/Avalonia.Styling.UnitTests/StyleTests.cs b/tests/Avalonia.Styling.UnitTests/StyleTests.cs index 5ef559b887..53e0ae5db7 100644 --- a/tests/Avalonia.Styling.UnitTests/StyleTests.cs +++ b/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()) + { + 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 FooProperty = diff --git a/tests/Avalonia.UnitTests/UnitTestApplication.cs b/tests/Avalonia.UnitTests/UnitTestApplication.cs index 359198048b..5c53334f44 100644 --- a/tests/Avalonia.UnitTests/UnitTestApplication.cs +++ b/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().ToConstant(Services.StandardCursorFactory) .Bind().ToConstant(Services.Styler) .Bind().ToConstant(Services.WindowingPlatform) + .Bind().ToSingleton() .Bind().ToConstant(this); var styles = Services.Theme?.Invoke();