From 889001dcaf5606b23f3991979738eb8492354d2c Mon Sep 17 00:00:00 2001 From: Takoooooo Date: Tue, 30 Aug 2022 16:49:01 +0300 Subject: [PATCH 01/65] Remove public constructors for KeyEventArgs, PointerEventArgs and friends. --- src/Avalonia.Base/Controls/NameScopeEventArgs.cs | 16 ---------------- src/Avalonia.Base/Input/DragEventArgs.cs | 2 +- src/Avalonia.Base/Input/GotFocusEventArgs.cs | 5 +++++ src/Avalonia.Base/Input/KeyEventArgs.cs | 5 +++++ src/Avalonia.Base/Input/PointerDeltaEventArgs.cs | 2 +- src/Avalonia.Base/Input/PointerEventArgs.cs | 12 ++++++------ src/Avalonia.Base/Input/PointerWheelEventArgs.cs | 2 +- .../Input/ScrollGestureEventArgs.cs | 6 +++--- src/Avalonia.Base/Input/TappedEventArgs.cs | 2 +- src/Avalonia.Base/Input/TextInputEventArgs.cs | 4 ++++ src/Avalonia.Base/Input/VectorEventArgs.cs | 5 +++++ .../Layout/EffectiveViewportChangedEventArgs.cs | 2 +- .../Rendering/SceneInvalidatedEventArgs.cs | 2 +- 13 files changed, 34 insertions(+), 31 deletions(-) delete mode 100644 src/Avalonia.Base/Controls/NameScopeEventArgs.cs diff --git a/src/Avalonia.Base/Controls/NameScopeEventArgs.cs b/src/Avalonia.Base/Controls/NameScopeEventArgs.cs deleted file mode 100644 index 3e9eaa6057..0000000000 --- a/src/Avalonia.Base/Controls/NameScopeEventArgs.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; - -namespace Avalonia.Controls -{ - public class NameScopeEventArgs : EventArgs - { - public NameScopeEventArgs(string name, object element) - { - Name = name; - Element = element; - } - - public string Name { get; } - public object Element { get; } - } -} diff --git a/src/Avalonia.Base/Input/DragEventArgs.cs b/src/Avalonia.Base/Input/DragEventArgs.cs index 0e613c0f21..41276e2f06 100644 --- a/src/Avalonia.Base/Input/DragEventArgs.cs +++ b/src/Avalonia.Base/Input/DragEventArgs.cs @@ -32,7 +32,7 @@ namespace Avalonia.Input return point; } - public DragEventArgs(RoutedEvent routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers) + internal DragEventArgs(RoutedEvent routedEvent, IDataObject data, Interactive target, Point targetLocation, KeyModifiers keyModifiers) : base(routedEvent) { Data = data; diff --git a/src/Avalonia.Base/Input/GotFocusEventArgs.cs b/src/Avalonia.Base/Input/GotFocusEventArgs.cs index 5cce138ee0..f3de55ebae 100644 --- a/src/Avalonia.Base/Input/GotFocusEventArgs.cs +++ b/src/Avalonia.Base/Input/GotFocusEventArgs.cs @@ -7,6 +7,11 @@ namespace Avalonia.Input /// public class GotFocusEventArgs : RoutedEventArgs { + internal GotFocusEventArgs() + { + + } + /// /// Gets or sets a value indicating how the change in focus occurred. /// diff --git a/src/Avalonia.Base/Input/KeyEventArgs.cs b/src/Avalonia.Base/Input/KeyEventArgs.cs index b8291e9096..39c9766105 100644 --- a/src/Avalonia.Base/Input/KeyEventArgs.cs +++ b/src/Avalonia.Base/Input/KeyEventArgs.cs @@ -5,6 +5,11 @@ namespace Avalonia.Input { public class KeyEventArgs : RoutedEventArgs { + internal KeyEventArgs() + { + + } + public IKeyboardDevice? Device { get; set; } public Key Key { get; set; } diff --git a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs b/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs index b3085a038d..d5577d77af 100644 --- a/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerDeltaEventArgs.cs @@ -7,7 +7,7 @@ namespace Avalonia.Input { public Vector Delta { get; set; } - public PointerDeltaEventArgs(RoutedEvent routedEvent, IInteractive? source, + internal PointerDeltaEventArgs(RoutedEvent routedEvent, IInteractive? source, IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, Vector delta) : base(routedEvent, source, pointer, rootVisual, rootVisualPosition, diff --git a/src/Avalonia.Base/Input/PointerEventArgs.cs b/src/Avalonia.Base/Input/PointerEventArgs.cs index 1f3c726e7b..25c10413ad 100644 --- a/src/Avalonia.Base/Input/PointerEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerEventArgs.cs @@ -13,7 +13,7 @@ namespace Avalonia.Input private readonly PointerPointProperties _properties; private readonly Lazy?>? _previousPoints; - public PointerEventArgs(RoutedEvent routedEvent, + internal PointerEventArgs(RoutedEvent routedEvent, IInteractive? source, IPointer pointer, IVisual? rootVisual, Point rootVisualPosition, @@ -30,8 +30,8 @@ namespace Avalonia.Input Timestamp = timestamp; KeyModifiers = modifiers; } - - public PointerEventArgs(RoutedEvent routedEvent, + + internal PointerEventArgs(RoutedEvent routedEvent, IInteractive? source, IPointer pointer, IVisual? rootVisual, Point rootVisualPosition, @@ -123,7 +123,7 @@ namespace Avalonia.Input public class PointerPressedEventArgs : PointerEventArgs { - public PointerPressedEventArgs( + internal PointerPressedEventArgs( IInteractive source, IPointer pointer, IVisual rootVisual, Point rootVisualPosition, @@ -142,7 +142,7 @@ namespace Avalonia.Input public class PointerReleasedEventArgs : PointerEventArgs { - public PointerReleasedEventArgs( + internal PointerReleasedEventArgs( IInteractive source, IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, @@ -163,7 +163,7 @@ namespace Avalonia.Input { public IPointer Pointer { get; } - public PointerCaptureLostEventArgs(IInteractive source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent) + internal PointerCaptureLostEventArgs(IInteractive source, IPointer pointer) : base(InputElement.PointerCaptureLostEvent) { Pointer = pointer; Source = source; diff --git a/src/Avalonia.Base/Input/PointerWheelEventArgs.cs b/src/Avalonia.Base/Input/PointerWheelEventArgs.cs index e5701dcf23..dbc06ec934 100644 --- a/src/Avalonia.Base/Input/PointerWheelEventArgs.cs +++ b/src/Avalonia.Base/Input/PointerWheelEventArgs.cs @@ -7,7 +7,7 @@ namespace Avalonia.Input { public Vector Delta { get; set; } - public PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, + internal PointerWheelEventArgs(IInteractive source, IPointer pointer, IVisual rootVisual, Point rootVisualPosition, ulong timestamp, PointerPointProperties properties, KeyModifiers modifiers, Vector delta) : base(InputElement.PointerWheelChangedEvent, source, pointer, rootVisual, rootVisualPosition, diff --git a/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs b/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs index a682e8f0a4..fd1d0f42c3 100644 --- a/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs +++ b/src/Avalonia.Base/Input/ScrollGestureEventArgs.cs @@ -9,8 +9,8 @@ namespace Avalonia.Input private static int _nextId = 1; public static int GetNextFreeId() => _nextId++; - - public ScrollGestureEventArgs(int id, Vector delta) : base(Gestures.ScrollGestureEvent) + + internal ScrollGestureEventArgs(int id, Vector delta) : base(Gestures.ScrollGestureEvent) { Id = id; Delta = delta; @@ -21,7 +21,7 @@ namespace Avalonia.Input { public int Id { get; } - public ScrollGestureEndedEventArgs(int id) : base(Gestures.ScrollGestureEndedEvent) + internal ScrollGestureEndedEventArgs(int id) : base(Gestures.ScrollGestureEndedEvent) { Id = id; } diff --git a/src/Avalonia.Base/Input/TappedEventArgs.cs b/src/Avalonia.Base/Input/TappedEventArgs.cs index daaab70632..8af6164fc1 100644 --- a/src/Avalonia.Base/Input/TappedEventArgs.cs +++ b/src/Avalonia.Base/Input/TappedEventArgs.cs @@ -7,7 +7,7 @@ namespace Avalonia.Input { private readonly PointerEventArgs lastPointerEventArgs; - public TappedEventArgs(RoutedEvent routedEvent, PointerEventArgs lastPointerEventArgs) + internal TappedEventArgs(RoutedEvent routedEvent, PointerEventArgs lastPointerEventArgs) : base(routedEvent) { this.lastPointerEventArgs = lastPointerEventArgs; diff --git a/src/Avalonia.Base/Input/TextInputEventArgs.cs b/src/Avalonia.Base/Input/TextInputEventArgs.cs index cda0103749..787bf1abd3 100644 --- a/src/Avalonia.Base/Input/TextInputEventArgs.cs +++ b/src/Avalonia.Base/Input/TextInputEventArgs.cs @@ -4,6 +4,10 @@ namespace Avalonia.Input { public class TextInputEventArgs : RoutedEventArgs { + internal TextInputEventArgs() + { + + } public IKeyboardDevice? Device { get; set; } public string? Text { get; set; } diff --git a/src/Avalonia.Base/Input/VectorEventArgs.cs b/src/Avalonia.Base/Input/VectorEventArgs.cs index 000fd52f69..3e8098f904 100644 --- a/src/Avalonia.Base/Input/VectorEventArgs.cs +++ b/src/Avalonia.Base/Input/VectorEventArgs.cs @@ -5,6 +5,11 @@ namespace Avalonia.Input { public class VectorEventArgs : RoutedEventArgs { + internal VectorEventArgs() + { + + } + public Vector Vector { get; set; } } } diff --git a/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs b/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs index 1cdc775b13..749d2ecc2b 100644 --- a/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs +++ b/src/Avalonia.Base/Layout/EffectiveViewportChangedEventArgs.cs @@ -7,7 +7,7 @@ namespace Avalonia.Layout /// public class EffectiveViewportChangedEventArgs : EventArgs { - public EffectiveViewportChangedEventArgs(Rect effectiveViewport) + internal EffectiveViewportChangedEventArgs(Rect effectiveViewport) { EffectiveViewport = effectiveViewport; } diff --git a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs index cac4d1693a..73840376fe 100644 --- a/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs +++ b/src/Avalonia.Base/Rendering/SceneInvalidatedEventArgs.cs @@ -12,7 +12,7 @@ namespace Avalonia.Rendering /// /// The render root that has been updated. /// The updated area. - public SceneInvalidatedEventArgs( + internal SceneInvalidatedEventArgs( IRenderRoot root, Rect dirtyRect) { From ab36ea3cebff25047a21cca83bf48ccc2c9fbe96 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Sat, 1 Oct 2022 11:48:55 +0200 Subject: [PATCH 02/65] feat: Add support at Pointer Wheel --- .../Input/LibInput/LibInputBackend.Pointer.cs | 110 ++++++++++++++++++ .../Input/LibInput/LibInputBackend.cs | 59 ++-------- .../LibInput/LibInputNativeUnsafeMethods.cs | 53 ++++++++- 3 files changed, 166 insertions(+), 56 deletions(-) create mode 100644 src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.Pointer.cs diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.Pointer.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.Pointer.cs new file mode 100644 index 0000000000..ef039a38bc --- /dev/null +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.Pointer.cs @@ -0,0 +1,110 @@ +using System; +using Avalonia.Input; +using Avalonia.Input.Raw; +using Avalonia.Logging; +using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods; + +namespace Avalonia.LinuxFramebuffer.Input.LibInput; + +public partial class LibInputBackend +{ + private MouseDevice _mouse = new MouseDevice(); + private Point _mousePosition; + private const string Pointer = LibInput + "/" + nameof(Pointer); + + private void HandlePointer(IntPtr ev, LibInputEventType type) + { + var modifiers = RawInputModifiers.None; //TODO: support input modifiers + var pev = libinput_event_get_pointer_event(ev); + var info = _screen.ScaledSize; + var ts = libinput_event_pointer_get_time_usec(pev) / 1000; + switch (type) + { + case LibInputEventType.LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE: + _mousePosition = new Point(libinput_event_pointer_get_absolute_x_transformed(pev, (int)info.Width), + libinput_event_pointer_get_absolute_y_transformed(pev, (int)info.Height)); + ScheduleInput(new RawPointerEventArgs(_mouse, ts, _inputRoot, RawPointerEventType.Move, _mousePosition, + modifiers)); + break; + case LibInputEventType.LIBINPUT_EVENT_POINTER_BUTTON: + { + var button = (EvKey)libinput_event_pointer_get_button(pev); + var buttonState = libinput_event_pointer_get_button_state(pev); + + RawPointerEventArgs evnt = button switch + { + EvKey.BTN_LEFT when buttonState == 1 + => new(_mouse, ts, _inputRoot, RawPointerEventType.LeftButtonDown, _mousePosition, modifiers), + EvKey.BTN_LEFT when buttonState == 0 + => new(_mouse, ts, _inputRoot, RawPointerEventType.LeftButtonUp, _mousePosition, modifiers), + EvKey.BTN_RIGHT when buttonState == 1 + => new(_mouse, ts, _inputRoot, RawPointerEventType.RightButtonUp, _mousePosition, modifiers), + EvKey.BTN_RIGHT when buttonState == 2 + => new(_mouse, ts, _inputRoot, RawPointerEventType.RightButtonDown, _mousePosition, modifiers), + EvKey.BTN_MIDDLE when buttonState == 1 + => new(_mouse, ts, _inputRoot, RawPointerEventType.MiddleButtonDown, _mousePosition, modifiers), + EvKey.BTN_MIDDLE when buttonState == 2 + => new(_mouse, ts, _inputRoot, RawPointerEventType.MiddleButtonUp, _mousePosition, modifiers), + _ => default, + }; + if (evnt is not null) + { + ScheduleInput(evnt); + } + else + { + Logger.TryGet(LogEventLevel.Warning, Pointer) + ?.Log(this, $"The button {button} is not associated"); + } + } + break; + // Backward compatibility with low-res wheel + case LibInputEventType.LIBINPUT_EVENT_POINTER_AXIS: + { + var sourceAxis = libinput_event_pointer_get_axis_source(pev); + switch (sourceAxis) + { + case LibInputPointerAxisSource.LIBINPUT_POINTER_AXIS_SOURCE_WHEEL: + { + var value = libinput_event_pointer_get_axis_value_discrete(pev, + LibInputPointerAxis.LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL); + ScheduleInput(new RawMouseWheelEventArgs(_mouse + , ts + , _inputRoot + , _mousePosition + , new Vector(0, -value) + , modifiers)); + } + break; + case LibInputPointerAxisSource.LIBINPUT_POINTER_AXIS_SOURCE_FINGER: + case LibInputPointerAxisSource.LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS: + case LibInputPointerAxisSource.LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT: + default: + Logger.TryGet(LogEventLevel.Debug, Pointer) + ?.Log(this, $"The pointer axis {sourceAxis} is not managed."); + break; + } + } + break; + // Hi-Res wheel + case LibInputEventType.LIBINPUT_EVENT_POINTER_SCROLL_WHEEL: + { + var value = new Vector(0, + -libinput_event_pointer_get_scroll_value_v120(pev, + LibInputPointerAxis.LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) / 120); + ScheduleInput(new RawMouseWheelEventArgs(_mouse + , ts + , _inputRoot + , _mousePosition + , value + , modifiers)); + } + break; + default: + Logger.TryGet(LogEventLevel.Warning, Pointer) + ?.Log(this, $"The pointer event {type} is not mapped."); + break; + } + + } +} diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs index 702ae3f8e5..77e8202fac 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs @@ -4,18 +4,16 @@ using System.IO; using System.Threading; using Avalonia.Input; using Avalonia.Input.Raw; -using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods; +using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods; namespace Avalonia.LinuxFramebuffer.Input.LibInput { - public class LibInputBackend : IInputBackend + public partial class LibInputBackend : IInputBackend { private IScreenInfoProvider _screen; private IInputRoot _inputRoot; private readonly Queue _inputThreadActions = new Queue(); private TouchDevice _touch = new TouchDevice(); - private MouseDevice _mouse = new MouseDevice(); - private Point _mousePosition; - + private const string LibInput = nameof(Avalonia.LinuxFramebuffer) + "/" + nameof(Avalonia.LinuxFramebuffer.Input) + "/" + nameof(LibInput); private readonly RawEventGroupingThreadingHelper _inputQueue; private Action _onInput; private Dictionary _pointers = new Dictionary(); @@ -24,13 +22,13 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput { var ctx = libinput_path_create_context(); _inputQueue = new(e => _onInput?.Invoke(e)); - new Thread(()=>InputThread(ctx)).Start(); + new Thread(() => InputThread(ctx)).Start(); } private unsafe void InputThread(IntPtr ctx) { var fd = libinput_get_fd(ctx); - + var timeval = stackalloc IntPtr[2]; @@ -38,12 +36,11 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput libinput_path_add_device(ctx, f); while (true) { - IntPtr ev; libinput_dispatch(ctx); while ((ev = libinput_get_event(ctx)) != IntPtr.Zero) { - + var type = libinput_event_get_type(ev); if (type >= LibInputEventType.LIBINPUT_EVENT_TOUCH_DOWN && type <= LibInputEventType.LIBINPUT_EVENT_TOUCH_CANCEL) @@ -52,12 +49,12 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput if (type >= LibInputEventType.LIBINPUT_EVENT_POINTER_MOTION && type <= LibInputEventType.LIBINPUT_EVENT_POINTER_AXIS) HandlePointer(ev, type); - + libinput_event_destroy(ev); libinput_dispatch(ctx); } - pollfd pfd = new pollfd {fd = fd, events = 1}; + pollfd pfd = new pollfd { fd = fd, events = 1 }; NativeUnsafeMethods.poll(&pfd, new IntPtr(1), 10); } } @@ -67,7 +64,7 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput private void HandleTouch(IntPtr ev, LibInputEventType type) { var tev = libinput_event_get_touch_event(ev); - if(tev == IntPtr.Zero) + if (tev == IntPtr.Zero) return; if (type < LibInputEventType.LIBINPUT_EVENT_TOUCH_FRAME) { @@ -102,44 +99,6 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput } } - private void HandlePointer(IntPtr ev, LibInputEventType type) - { - //TODO: support input modifiers - var pev = libinput_event_get_pointer_event(ev); - var info = _screen.ScaledSize; - var ts = libinput_event_pointer_get_time_usec(pev) / 1000; - if (type == LibInputEventType.LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE) - { - _mousePosition = new Point(libinput_event_pointer_get_absolute_x_transformed(pev, (int)info.Width), - libinput_event_pointer_get_absolute_y_transformed(pev, (int)info.Height)); - ScheduleInput(new RawPointerEventArgs(_mouse, ts, _inputRoot, RawPointerEventType.Move, _mousePosition, - RawInputModifiers.None)); - } - else if (type == LibInputEventType.LIBINPUT_EVENT_POINTER_BUTTON) - { - var button = (EvKey)libinput_event_pointer_get_button(pev); - var buttonState = libinput_event_pointer_get_button_state(pev); - - - var evnt = button == EvKey.BTN_LEFT ? - (buttonState == 1 ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp) : - button == EvKey.BTN_MIDDLE ? - (buttonState == 1 ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp) : - button == EvKey.BTN_RIGHT ? - (buttonState == 1 ? - RawPointerEventType.RightButtonDown : - RawPointerEventType.RightButtonUp) : - (RawPointerEventType)(-1); - if (evnt == (RawPointerEventType)(-1)) - return; - - - ScheduleInput( - new RawPointerEventArgs(_mouse, ts, _inputRoot, evnt, _mousePosition, RawInputModifiers.None)); - } - - } - public void Initialize(IScreenInfoProvider screen, Action onInput) { _screen = screen; diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs index 0492090461..df6defb653 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs @@ -77,6 +77,9 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE, LIBINPUT_EVENT_POINTER_BUTTON, LIBINPUT_EVENT_POINTER_AXIS, + LIBINPUT_EVENT_POINTER_SCROLL_WHEEL, + LIBINPUT_EVENT_POINTER_SCROLL_FINGER, + LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS, LIBINPUT_EVENT_TOUCH_DOWN = 500, LIBINPUT_EVENT_TOUCH_UP, LIBINPUT_EVENT_TOUCH_MOTION, @@ -97,8 +100,38 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput LIBINPUT_EVENT_GESTURE_PINCH_END, LIBINPUT_EVENT_SWITCH_TOGGLE = 900, } - - + + public enum LibInputPointerAxisSource + { + /** + * The event is caused by the rotation of a wheel. + **/ + LIBINPUT_POINTER_AXIS_SOURCE_WHEEL = 1, + /** + * The event is caused by the movement of one or more fingers on a device. + **/ + LIBINPUT_POINTER_AXIS_SOURCE_FINGER, + /** + * The event is caused by the motion of some device. + **/ + LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS, + /** + * The event is caused by the tilting of a mouse wheel rather than + * its rotation. This method is commonly used on mice without + * separate horizontal scroll wheels. + * @deprecated This axis source is deprecated as of libinput 1.16. + * It was never used by any device before libinput 1.16. All wheel + * tilt devices use @ref LIBINPUT_POINTER_AXIS_SOURCE_WHEEL instead. + **/ + LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT, + }; + + public enum LibInputPointerAxis + { + LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL = 0, + LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL = 1, + }; + [DllImport(LibInput)] public extern static void libinput_event_destroy(IntPtr ev); @@ -119,21 +152,29 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput [DllImport(LibInput)] public extern static IntPtr libinput_event_get_pointer_event(IntPtr ev); - - + [DllImport(LibInput)] public extern static ulong libinput_event_pointer_get_time_usec(IntPtr ev); [DllImport(LibInput)] public extern static double libinput_event_pointer_get_absolute_x_transformed(IntPtr ev, int width); - + [DllImport(LibInput)] public extern static double libinput_event_pointer_get_absolute_y_transformed(IntPtr ev, int height); - + [DllImport(LibInput)] public extern static int libinput_event_pointer_get_button(IntPtr ev); [DllImport(LibInput)] public extern static int libinput_event_pointer_get_button_state(IntPtr ev); + + [DllImport(LibInput)] + public extern static LibInputPointerAxisSource libinput_event_pointer_get_axis_source(IntPtr ev); + + [DllImport((LibInput))] + public extern static double libinput_event_pointer_get_axis_value_discrete(IntPtr ev, LibInputPointerAxis axis); + + [DllImport(LibInput)] + public extern static double libinput_event_pointer_get_scroll_value_v120(IntPtr ev, LibInputPointerAxis axis); } } From 50cea008d789f1130a9c694cf1bcbc30a8f8e8b9 Mon Sep 17 00:00:00 2001 From: daniilpavliuchyk Date: Mon, 3 Oct 2022 18:18:00 +0300 Subject: [PATCH 03/65] WIP --- native/Avalonia.Native/src/OSX/main.mm | 31 +++++++++++++++---- samples/ControlCatalog.NetCore/Program.cs | 8 +++++ src/Avalonia.Native/AvaloniaNativePlatform.cs | 1 + .../AvaloniaNativePlatformExtensions.cs | 2 ++ src/Avalonia.Native/avn.idl | 1 + 5 files changed, 37 insertions(+), 6 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index 6ee86b21ae..cf6503b61c 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -3,7 +3,7 @@ #include "common.h" static NSString* s_appTitle = @"Avalonia"; - +static int disableSetProcessName; // Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -101,7 +101,9 @@ void SetProcessName(NSString* appTitle) { class MacOptions : public ComSingleObject { + public: + FORWARD_IUNKNOWN() virtual HRESULT SetApplicationTitle(char* utf8String) override @@ -111,11 +113,17 @@ public: @autoreleasepool { auto appTitle = [NSString stringWithUTF8String: utf8String]; - - [[NSProcessInfo processInfo] setProcessName:appTitle]; - - - SetProcessName(appTitle); + if (disableSetProcessName == 0) + { + [[NSProcessInfo processInfo] setProcessName:appTitle]; + + SetProcessName(appTitle); + } + if (disableSetProcessName == 1) + { + auto rootMenu = [NSApp mainMenu]; + [rootMenu setTitle:appTitle]; + } return S_OK; } @@ -133,6 +141,17 @@ public: } } + virtual HRESULT SetDisableSetProcessName(int disable) override + { + START_COM_CALL; + + @autoreleasepool + { + disableSetProcessName = disable; + return S_OK; + } + } + }; /// See "Using POSIX Threads in a Cocoa Application" section here: diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index b1bacc6483..d0ea7ac85e 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -115,6 +115,14 @@ namespace ControlCatalog.NetCore UseDBusMenu = true, EnableIme = true }) + .With(new MacOSPlatformOptions() + { + DisableSetProcessName = true + }) + .With(new AvaloniaNativePlatformOptions() + { + AvaloniaNativeLibraryPath = "/Users/daniilpavliuchyk/Library/Developer/Xcode/DerivedData/Avalonia.Native.OSX-hchxgrlrewlcvufcdcugzgajjpdt/Build/Products/Debug/libAvalonia.Native.OSX.dylib" + }) .UseSkia() .AfterSetup(builder => { diff --git a/src/Avalonia.Native/AvaloniaNativePlatform.cs b/src/Avalonia.Native/AvaloniaNativePlatform.cs index b45fe5559b..acb538268f 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatform.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatform.cs @@ -102,6 +102,7 @@ namespace Avalonia.Native var macOpts = AvaloniaLocator.Current.GetService() ?? new MacOSPlatformOptions(); _factory.MacOptions.SetShowInDock(macOpts.ShowInDock ? 1 : 0); + _factory.MacOptions.SetDisableSetProcessName(macOpts.DisableSetProcessName ? 1 : 0); } AvaloniaLocator.CurrentMutable diff --git a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs index 61889aa9e4..baa2018183 100644 --- a/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs +++ b/src/Avalonia.Native/AvaloniaNativePlatformExtensions.cs @@ -83,5 +83,7 @@ namespace Avalonia /// Gets or sets a value indicating whether the native macOS menu bar will be enabled for the application. /// public bool DisableNativeMenus { get; set; } + + public bool DisableSetProcessName { get; set; } } } diff --git a/src/Avalonia.Native/avn.idl b/src/Avalonia.Native/avn.idl index a98d213887..b94b8ec0f4 100644 --- a/src/Avalonia.Native/avn.idl +++ b/src/Avalonia.Native/avn.idl @@ -599,6 +599,7 @@ interface IAvnMacOptions : IUnknown { HRESULT SetShowInDock(int show); HRESULT SetApplicationTitle(char* utf8string); + HRESULT SetDisableSetProcessName(int disable); } [uuid(04c1b049-1f43-418a-9159-cae627ec1367)] From f6cae3af060b0064a98bfdbf6df78732f2b37fcb Mon Sep 17 00:00:00 2001 From: daniilpavliuchyk Date: Mon, 3 Oct 2022 18:21:34 +0300 Subject: [PATCH 04/65] WIP --- native/Avalonia.Native/src/OSX/main.mm | 3 +-- samples/ControlCatalog.NetCore/Program.cs | 8 -------- 2 files changed, 1 insertion(+), 10 deletions(-) diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index cf6503b61c..ba8a4d439c 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -4,6 +4,7 @@ static NSString* s_appTitle = @"Avalonia"; static int disableSetProcessName; + // Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -101,9 +102,7 @@ void SetProcessName(NSString* appTitle) { class MacOptions : public ComSingleObject { - public: - FORWARD_IUNKNOWN() virtual HRESULT SetApplicationTitle(char* utf8String) override diff --git a/samples/ControlCatalog.NetCore/Program.cs b/samples/ControlCatalog.NetCore/Program.cs index d0ea7ac85e..b1bacc6483 100644 --- a/samples/ControlCatalog.NetCore/Program.cs +++ b/samples/ControlCatalog.NetCore/Program.cs @@ -115,14 +115,6 @@ namespace ControlCatalog.NetCore UseDBusMenu = true, EnableIme = true }) - .With(new MacOSPlatformOptions() - { - DisableSetProcessName = true - }) - .With(new AvaloniaNativePlatformOptions() - { - AvaloniaNativeLibraryPath = "/Users/daniilpavliuchyk/Library/Developer/Xcode/DerivedData/Avalonia.Native.OSX-hchxgrlrewlcvufcdcugzgajjpdt/Build/Products/Debug/libAvalonia.Native.OSX.dylib" - }) .UseSkia() .AfterSetup(builder => { From 677e89edd0b516b6f3a3f6bf625557622be216d5 Mon Sep 17 00:00:00 2001 From: daniilpavliuchyk Date: Mon, 3 Oct 2022 18:30:01 +0300 Subject: [PATCH 05/65] WIP --- native/Avalonia.Native/src/OSX/main.mm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm index ba8a4d439c..2fc72afb46 100644 --- a/native/Avalonia.Native/src/OSX/main.mm +++ b/native/Avalonia.Native/src/OSX/main.mm @@ -3,7 +3,7 @@ #include "common.h" static NSString* s_appTitle = @"Avalonia"; -static int disableSetProcessName; +static int disableSetProcessName = 0; // Copyright (c) 2011 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be From 73e4985746b7794fea07599a949478b71ce62a58 Mon Sep 17 00:00:00 2001 From: Fabian Huegle Date: Wed, 5 Oct 2022 19:43:20 +0200 Subject: [PATCH 06/65] Added new optional parameter "quality" for IBitmap and IBitmapImpl.Save method --- src/Avalonia.Base/Media/Imaging/Bitmap.cs | 18 +++++++--- src/Avalonia.Base/Media/Imaging/IBitmap.cs | 14 ++++++-- src/Avalonia.Base/Platform/IBitmapImpl.cs | 14 ++++++-- .../HeadlessPlatformRenderInterface.cs | 4 +-- .../Gpu/OpenGl/OpenGlBitmapImpl.cs | 4 +-- .../Helpers/ImageSavingHelper.cs | 33 +++++++++++++++---- src/Skia/Avalonia.Skia/ImmutableBitmap.cs | 4 +-- src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs | 8 ++--- src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs | 8 ++--- .../Media/Imaging/BitmapImpl.cs | 6 ++-- .../Media/Imaging/D2DBitmapImpl.cs | 2 +- .../Imaging/D2DRenderTargetBitmapImpl.cs | 2 +- .../Media/Imaging/WicBitmapImpl.cs | 2 +- 13 files changed, 84 insertions(+), 35 deletions(-) diff --git a/src/Avalonia.Base/Media/Imaging/Bitmap.cs b/src/Avalonia.Base/Media/Imaging/Bitmap.cs index cf8a31c3e9..ce38fc5abc 100644 --- a/src/Avalonia.Base/Media/Imaging/Bitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/Bitmap.cs @@ -121,18 +121,28 @@ namespace Avalonia.Media.Imaging /// Saves the bitmap to a file. /// /// The filename. - public void Save(string fileName) + /// + /// The optional quality for compression. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting is applied. + /// + public void Save(string fileName, int? quality = null) { - PlatformImpl.Item.Save(fileName); + PlatformImpl.Item.Save(fileName, quality); } /// /// Saves the bitmap to a stream. /// /// The stream. - public void Save(Stream stream) + /// + /// The optional quality for compression. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting is applied. + /// + public void Save(Stream stream, int? quality = null) { - PlatformImpl.Item.Save(stream); + PlatformImpl.Item.Save(stream, quality); } /// diff --git a/src/Avalonia.Base/Media/Imaging/IBitmap.cs b/src/Avalonia.Base/Media/Imaging/IBitmap.cs index bd04d5ce86..e7d1862aa2 100644 --- a/src/Avalonia.Base/Media/Imaging/IBitmap.cs +++ b/src/Avalonia.Base/Media/Imaging/IBitmap.cs @@ -35,12 +35,22 @@ namespace Avalonia.Media.Imaging /// Saves the bitmap to a file. /// /// The filename. - void Save(string fileName); + /// + /// The optional quality for compression if supported by the specific backend. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting of the backend is applied. + /// + void Save(string fileName, int? quality = null); /// /// Saves the bitmap to a stream in png format. /// /// The stream. - void Save(Stream stream); + /// + /// The optional quality for compression if supported by the specific backend. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting of the backend is applied. + /// + void Save(Stream stream, int? quality = null); } } diff --git a/src/Avalonia.Base/Platform/IBitmapImpl.cs b/src/Avalonia.Base/Platform/IBitmapImpl.cs index 8f11f68e7c..299a758961 100644 --- a/src/Avalonia.Base/Platform/IBitmapImpl.cs +++ b/src/Avalonia.Base/Platform/IBitmapImpl.cs @@ -29,12 +29,22 @@ namespace Avalonia.Platform /// Saves the bitmap to a file. /// /// The filename. - void Save(string fileName); + /// + /// The optional quality for compression if supported by the specific backend. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting of the backend is applied. + /// + void Save(string fileName, int? quality = null); /// /// Saves the bitmap to a stream in png format. /// /// The stream. - void Save(Stream stream); + /// + /// The optional quality for compression if supported by the specific backend. + /// The quality value is interpreted from 0 - 100. If quality is null the default quality + /// setting of the backend is applied. + /// + void Save(Stream stream, int? quality = null); } } diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index cb23c6c336..999c616ba0 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -323,12 +323,12 @@ namespace Avalonia.Headless public Vector Dpi { get; } public PixelSize PixelSize { get; } public int Version { get; set; } - public void Save(string fileName) + public void Save(string fileName, int? quality = null) { } - public void Save(Stream stream) + public void Save(Stream stream, int? quality = null) { } diff --git a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs index d700d4848e..857433f95f 100644 --- a/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/Gpu/OpenGl/OpenGlBitmapImpl.cs @@ -25,9 +25,9 @@ namespace Avalonia.Skia public Vector Dpi { get; } public PixelSize PixelSize { get; } public int Version { get; private set; } - public void Save(string fileName) => throw new NotSupportedException(); + public void Save(string fileName, int? quality = null) => throw new NotSupportedException(); - public void Save(Stream stream) => throw new NotSupportedException(); + public void Save(Stream stream, int? quality = null) => throw new NotSupportedException(); public void Draw(DrawingContextImpl context, SKRect sourceRect, SKRect destRect, SKPaint paint) { diff --git a/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs b/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs index 5fa961cc99..b4dd754822 100644 --- a/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs +++ b/src/Skia/Avalonia.Skia/Helpers/ImageSavingHelper.cs @@ -14,14 +14,19 @@ namespace Avalonia.Skia.Helpers /// /// Image to save /// Target file. - public static void SaveImage(SKImage image, string fileName) + /// + /// The optional quality for PNG compression. + /// The quality value is interpreted from 0 - 100. If quality is null + /// the encoder applies the default quality value. + /// + public static void SaveImage(SKImage image, string fileName, int? quality = null) { if (image == null) throw new ArgumentNullException(nameof(image)); if (fileName == null) throw new ArgumentNullException(nameof(fileName)); using (var stream = File.Create(fileName)) { - SaveImage(image, stream); + SaveImage(image, stream, quality); } } @@ -29,16 +34,30 @@ namespace Avalonia.Skia.Helpers /// Save Skia image to a stream. /// /// Image to save - /// Target stream. - public static void SaveImage(SKImage image, Stream stream) + /// + /// The optional quality for PNG compression. + /// The quality value is interpreted from 0 - 100. If quality is null + /// the encoder applies the default quality value. + /// + public static void SaveImage(SKImage image, Stream stream, int? quality = null) { if (image == null) throw new ArgumentNullException(nameof(image)); if (stream == null) throw new ArgumentNullException(nameof(stream)); - using (var data = image.Encode()) + if (quality == null) { - data.SaveTo(stream); + using (var data = image.Encode()) + { + data.SaveTo(stream); + } + } + else + { + using (var data = image.Encode(SKEncodedImageFormat.Png, (int)quality)) + { + data.SaveTo(stream); + } } } } -} \ No newline at end of file +} diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs index 6400d67fde..e24d805050 100644 --- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs +++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs @@ -139,13 +139,13 @@ namespace Avalonia.Skia } /// - public void Save(string fileName) + public void Save(string fileName, int? quality = null) { ImageSavingHelper.SaveImage(_image, fileName); } /// - public void Save(Stream stream) + public void Save(Stream stream, int? quality = null) { ImageSavingHelper.SaveImage(_image, stream); } diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs index 01b7449b64..d3231c92a5 100644 --- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs +++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs @@ -116,20 +116,20 @@ namespace Avalonia.Skia public int Version { get; private set; } = 1; /// - public void Save(string fileName) + public void Save(string fileName, int? quality = null) { using (var image = SnapshotImage()) { - ImageSavingHelper.SaveImage(image, fileName); + ImageSavingHelper.SaveImage(image, fileName, quality); } } /// - public void Save(Stream stream) + public void Save(Stream stream, int? quality = null) { using (var image = SnapshotImage()) { - ImageSavingHelper.SaveImage(image, stream); + ImageSavingHelper.SaveImage(image, stream, quality); } } diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs index 80bfcc5973..d437f514bb 100644 --- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs +++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs @@ -137,20 +137,20 @@ namespace Avalonia.Skia } /// - public void Save(Stream stream) + public void Save(Stream stream, int? quality = null) { using (var image = GetSnapshot()) { - ImageSavingHelper.SaveImage(image, stream); + ImageSavingHelper.SaveImage(image, stream, quality); } } /// - public void Save(string fileName) + public void Save(string fileName, int? quality = null) { using (var image = GetSnapshot()) { - ImageSavingHelper.SaveImage(image, fileName); + ImageSavingHelper.SaveImage(image, fileName, quality); } } diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs index 843efe2cc4..059105c112 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs @@ -15,7 +15,7 @@ namespace Avalonia.Direct2D1.Media public abstract OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target); - public void Save(string fileName) + public void Save(string fileName, int? quality = null) { if (Path.GetExtension(fileName) != ".png") { @@ -25,11 +25,11 @@ namespace Avalonia.Direct2D1.Media using (FileStream s = new FileStream(fileName, FileMode.Create)) { - Save(s); + Save(s, quality); } } - public abstract void Save(Stream stream); + public abstract void Save(Stream stream, int? quality = null); public virtual void Dispose() { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs index 2656ab4c58..a321b225a0 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs @@ -43,7 +43,7 @@ namespace Avalonia.Direct2D1.Media return new OptionalDispose(_direct2DBitmap, false); } - public override void Save(Stream stream) + public override void Save(Stream stream, int? quality = null) { using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) using (var frame = new BitmapFrameEncode(encoder)) diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs index 357e472d34..c5f8e837ce 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs @@ -56,7 +56,7 @@ namespace Avalonia.Direct2D1.Media.Imaging return new OptionalDispose(_renderTarget.Bitmap, false); } - public override void Save(Stream stream) + public override void Save(Stream stream, int? quality = null) { using (var wic = new WicRenderTargetBitmapImpl(PixelSize, Dpi)) { diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs index 1156246b29..051790ef03 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs @@ -186,7 +186,7 @@ namespace Avalonia.Direct2D1.Media return new OptionalDispose(D2DBitmap.FromWicBitmap(renderTarget, converter), true); } - public override void Save(Stream stream) + public override void Save(Stream stream, int? quality = null) { using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream)) using (var frame = new BitmapFrameEncode(encoder)) From fe3b48054db194b97728f89d3ab48263bd54b818 Mon Sep 17 00:00:00 2001 From: Markus Wenzl Date: Fri, 7 Oct 2022 20:01:48 +0200 Subject: [PATCH 07/65] Added DrmOutputOptions.VideMode property --- .../Avalonia.LinuxFramebuffer/DrmOutputOptions.cs | 2 ++ .../Avalonia.LinuxFramebuffer/Output/DrmOutput.cs | 11 ++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs index e92ad02c7a..965d382238 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs @@ -23,5 +23,7 @@ namespace Avalonia.LinuxFramebuffer /// Default: R0 G0 B0 A0 /// public Color InitialBufferSwappingColor { get; set; } = new Color(0, 0, 0, 0); + + public PixelSize? VideoMode { get; set; } } } diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs index ce210019c0..9bc9d8c7e6 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs @@ -51,7 +51,16 @@ namespace Avalonia.LinuxFramebuffer.Output if(connector == null) throw new InvalidOperationException("Unable to find connected DRM connector"); - var mode = connector.Modes.OrderByDescending(x => x.IsPreferred) + DrmModeInfo? mode = null; + + if (options?.VideoMode != null) + { + mode = connector.Modes + .FirstOrDefault(x => x.Resolution.Width == options.VideoMode.Value.Width && + x.Resolution.Height == options.VideoMode.Value.Height); + } + + mode ??= connector.Modes.OrderByDescending(x => x.IsPreferred) .ThenByDescending(x => x.Resolution.Width * x.Resolution.Height) //.OrderByDescending(x => x.Resolution.Width * x.Resolution.Height) .FirstOrDefault(); From fa754f21c8ef257d756b9254bbb4fc43a5d01389 Mon Sep 17 00:00:00 2001 From: Markus Wenzl Date: Fri, 7 Oct 2022 20:07:22 +0200 Subject: [PATCH 08/65] Added doc --- src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs index 965d382238..ce843952e7 100644 --- a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs +++ b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs @@ -24,6 +24,10 @@ namespace Avalonia.LinuxFramebuffer /// public Color InitialBufferSwappingColor { get; set; } = new Color(0, 0, 0, 0); + /// + /// specific the video mode with which the DrmOutput should be created, if it is not found it will fallback to the preferred mode. + /// If NULL preferred mode will be used. + /// public PixelSize? VideoMode { get; set; } } } From 93b77b1b6a2181822f3bd1b074a504366aa4e0b4 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Fri, 21 Oct 2022 07:43:36 +0200 Subject: [PATCH 09/65] Move inlines support to TextBlock and rename RichTextBlock to SelectableTextBlock --- .../ControlCatalog/Pages/TextBlockPage.xaml | 4 +- .../Media/TextFormatting/TextFormatterImpl.cs | 4 +- .../Documents/InlineCollection.cs | 9 +- ...ichTextBlock.cs => SelectableTextBlock.cs} | 374 +++--------------- src/Avalonia.Controls/TextBlock.cs | 272 ++++++++++--- .../Controls/FluentControls.xaml | 2 +- .../Controls/RichTextBlock.xaml | 14 - .../Controls/SelectableTextBlock.xaml | 18 + .../Controls/RichTextBlock.xaml | 14 - .../Controls/SelectableTextBlock.xaml | 18 + .../Controls/SimpleControls.xaml | 2 +- src/Skia/Avalonia.Skia/TextShaperImpl.cs | 2 +- .../Media/TextShaperImpl.cs | 2 +- .../RichTextBlockTests.cs | 132 ------- .../TextBlockTests.cs | 121 ++++++ 15 files changed, 451 insertions(+), 537 deletions(-) rename src/Avalonia.Controls/{RichTextBlock.cs => SelectableTextBlock.cs} (55%) delete mode 100644 src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml create mode 100644 src/Avalonia.Themes.Fluent/Controls/SelectableTextBlock.xaml delete mode 100644 src/Avalonia.Themes.Simple/Controls/RichTextBlock.xaml create mode 100644 src/Avalonia.Themes.Simple/Controls/SelectableTextBlock.xaml delete mode 100644 tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml index 32914428ed..6bb428e2c7 100644 --- a/samples/ControlCatalog/Pages/TextBlockPage.xaml +++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml @@ -118,7 +118,7 @@ - + This is a TextBlock with several @@ -126,7 +126,7 @@ using a variety of styles . - + diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 73dd3366aa..5df458cc3b 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -502,7 +502,7 @@ namespace Avalonia.Media.TextFormatting case { } drawableTextRun: { - if (currentWidth + drawableTextRun.Size.Width > paragraphWidth) + if (currentWidth + drawableTextRun.Size.Width >= paragraphWidth) { goto found; } @@ -665,7 +665,7 @@ namespace Avalonia.Media.TextFormatting if (!breakFound) { - currentLength += currentRun.Text.Length; + currentLength += currentRun.TextSourceLength; continue; } diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index 1ba65b3e8f..9ff5627434 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -70,6 +70,11 @@ namespace Avalonia.Controls.Documents { get { + if (Count == 0) + { + return null; + } + var builder = StringBuilderCache.Acquire(); foreach (var inline in this) @@ -111,7 +116,7 @@ namespace Avalonia.Controls.Documents private void AddText(string text) { - if (Parent is RichTextBlock textBlock && !textBlock.HasComplexContent) + if (Parent is TextBlock textBlock && !textBlock.HasComplexContent) { textBlock._text += text; } @@ -123,7 +128,7 @@ namespace Avalonia.Controls.Documents private void OnAdd() { - if (Parent is RichTextBlock textBlock) + if (Parent is TextBlock textBlock) { if (!textBlock.HasComplexContent && !string.IsNullOrEmpty(textBlock._text)) { diff --git a/src/Avalonia.Controls/RichTextBlock.cs b/src/Avalonia.Controls/SelectableTextBlock.cs similarity index 55% rename from src/Avalonia.Controls/RichTextBlock.cs rename to src/Avalonia.Controls/SelectableTextBlock.cs index d0b713ba56..b343439f98 100644 --- a/src/Avalonia.Controls/RichTextBlock.cs +++ b/src/Avalonia.Controls/SelectableTextBlock.cs @@ -8,7 +8,6 @@ using Avalonia.Input.Platform; using Avalonia.Interactivity; using Avalonia.Media; using Avalonia.Media.TextFormatting; -using Avalonia.Metadata; using Avalonia.Utilities; namespace Avalonia.Controls @@ -16,67 +15,53 @@ namespace Avalonia.Controls /// /// A control that displays a block of formatted text. /// - public class RichTextBlock : TextBlock, IInlineHost + public class SelectableTextBlock : TextBlock, IInlineHost { - public static readonly StyledProperty IsTextSelectionEnabledProperty = - AvaloniaProperty.Register(nameof(IsTextSelectionEnabled), false); - - public static readonly DirectProperty SelectionStartProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty SelectionStartProperty = + AvaloniaProperty.RegisterDirect( nameof(SelectionStart), o => o.SelectionStart, (o, v) => o.SelectionStart = v); - public static readonly DirectProperty SelectionEndProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty SelectionEndProperty = + AvaloniaProperty.RegisterDirect( nameof(SelectionEnd), o => o.SelectionEnd, (o, v) => o.SelectionEnd = v); - public static readonly DirectProperty SelectedTextProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty SelectedTextProperty = + AvaloniaProperty.RegisterDirect( nameof(SelectedText), o => o.SelectedText); public static readonly StyledProperty SelectionBrushProperty = - AvaloniaProperty.Register(nameof(SelectionBrush), Brushes.Blue); + AvaloniaProperty.Register(nameof(SelectionBrush), Brushes.Blue); - /// - /// Defines the property. - /// - public static readonly StyledProperty InlinesProperty = - AvaloniaProperty.Register( - nameof(Inlines)); - public static readonly DirectProperty CanCopyProperty = - AvaloniaProperty.RegisterDirect( + public static readonly DirectProperty CanCopyProperty = + AvaloniaProperty.RegisterDirect( nameof(CanCopy), o => o.CanCopy); public static readonly RoutedEvent CopyingToClipboardEvent = - RoutedEvent.Register( + RoutedEvent.Register( nameof(CopyingToClipboard), RoutingStrategies.Bubble); private bool _canCopy; private int _selectionStart; private int _selectionEnd; private int _wordSelectionStart = -1; - private IReadOnlyList? _textRuns; - static RichTextBlock() + static SelectableTextBlock() { - FocusableProperty.OverrideDefaultValue(typeof(RichTextBlock), true); - - AffectsRender(SelectionStartProperty, SelectionEndProperty, SelectionBrushProperty, IsTextSelectionEnabledProperty); + FocusableProperty.OverrideDefaultValue(typeof(SelectableTextBlock), true); + AffectsRender(SelectionStartProperty, SelectionEndProperty, SelectionBrushProperty); } - public RichTextBlock() + public event EventHandler? CopyingToClipboard { - Inlines = new InlineCollection - { - Parent = this, - InlineHost = this - }; + add => AddHandler(CopyingToClipboardEvent, value); + remove => RemoveHandler(CopyingToClipboardEvent, value); } /// @@ -99,6 +84,8 @@ namespace Avalonia.Controls if (SetAndRaise(SelectionStartProperty, ref _selectionStart, value)) { RaisePropertyChanged(SelectedTextProperty, "", ""); + + UpdateCommandStates(); } } } @@ -114,6 +101,8 @@ namespace Avalonia.Controls if (SetAndRaise(SelectionEndProperty, ref _selectionEnd, value)) { RaisePropertyChanged(SelectedTextProperty, "", ""); + + UpdateCommandStates(); } } } @@ -126,25 +115,6 @@ namespace Avalonia.Controls get => GetSelection(); } - /// - /// Gets or sets a value that indicates whether text selection is enabled, either through user action or calling selection-related API. - /// - public bool IsTextSelectionEnabled - { - get => GetValue(IsTextSelectionEnabledProperty); - set => SetValue(IsTextSelectionEnabledProperty, value); - } - - /// - /// Gets or sets the inlines. - /// - [Content] - public InlineCollection? Inlines - { - get => GetValue(InlinesProperty); - set => SetValue(InlinesProperty, value); - } - /// /// Property for determining if the Copy command can be executed. /// @@ -154,20 +124,12 @@ namespace Avalonia.Controls private set => SetAndRaise(CanCopyProperty, ref _canCopy, value); } - public event EventHandler? CopyingToClipboard - { - add => AddHandler(CopyingToClipboardEvent, value); - remove => RemoveHandler(CopyingToClipboardEvent, value); - } - - internal bool HasComplexContent => Inlines != null && Inlines.Count > 0; - /// /// Copies the current selection to the Clipboard. /// public async void Copy() { - if (_canCopy || !IsTextSelectionEnabled) + if (!_canCopy) { return; } @@ -188,45 +150,13 @@ namespace Avalonia.Controls await ((IClipboard)AvaloniaLocator.Current.GetRequiredService(typeof(IClipboard))) .SetTextAsync(text); } - } - - protected override void RenderTextLayout(DrawingContext context, Point origin) - { - var selectionStart = SelectionStart; - var selectionEnd = SelectionEnd; - var selectionBrush = SelectionBrush; - - var selectionEnabled = IsTextSelectionEnabled; - - if (selectionEnabled && selectionStart != selectionEnd && selectionBrush != null) - { - var start = Math.Min(selectionStart, selectionEnd); - var length = Math.Max(selectionStart, selectionEnd) - start; - - var rects = TextLayout.HitTestTextRange(start, length); - - using (context.PushPostTransform(Matrix.CreateTranslation(origin))) - { - foreach (var rect in rects) - { - context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1)); - } - } - } - - base.RenderTextLayout(context, origin); - } + } /// /// Select all text in the TextBox /// public void SelectAll() { - if (!IsTextSelectionEnabled) - { - return; - } - var text = Text; SelectionStart = 0; @@ -238,94 +168,52 @@ namespace Avalonia.Controls /// public void ClearSelection() { - if (!IsTextSelectionEnabled) - { - return; - } - SelectionEnd = SelectionStart; } - protected void AddText(string? text) + protected override void OnGotFocus(GotFocusEventArgs e) { - if (string.IsNullOrEmpty(text)) - { - return; - } - - if (!HasComplexContent && string.IsNullOrEmpty(_text)) - { - _text = text; - } - else - { - if (!string.IsNullOrEmpty(_text)) - { - Inlines?.Add(_text); - - _text = null; - } - - Inlines?.Add(text); - } - } + base.OnGotFocus(e); - protected override string? GetText() - { - return _text ?? Inlines?.Text; + UpdateCommandStates(); } - protected override void SetText(string? text) + protected override void OnLostFocus(RoutedEventArgs e) { - var oldValue = GetText(); + base.OnLostFocus(e); - AddText(text); + if ((ContextFlyout == null || !ContextFlyout.IsOpen) && + (ContextMenu == null || !ContextMenu.IsOpen)) + { + ClearSelection(); + } - RaisePropertyChanged(TextProperty, oldValue, text); + UpdateCommandStates(); } - /// - /// Creates the used to render the text. - /// - /// A object. - protected override TextLayout CreateTextLayout(string? text) + protected override void RenderTextLayout(DrawingContext context, Point origin) { - var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch); - var defaultProperties = new GenericTextRunProperties( - typeface, - FontSize, - TextDecorations, - Foreground); - - var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false, - defaultProperties, TextWrapping, LineHeight, 0); - - ITextSource textSource; + var selectionStart = SelectionStart; + var selectionEnd = SelectionEnd; + var selectionBrush = SelectionBrush; - if (_textRuns != null) + if (selectionStart != selectionEnd && selectionBrush != null) { - textSource = new InlinesTextSource(_textRuns); - } - else - { - textSource = new SimpleTextSource((text ?? "").AsMemory(), defaultProperties); - } + var start = Math.Min(selectionStart, selectionEnd); + var length = Math.Max(selectionStart, selectionEnd) - start; - return new TextLayout( - textSource, - paragraphProperties, - TextTrimming, - _constraint.Width, - _constraint.Height, - maxLines: MaxLines, - lineHeight: LineHeight); - } + var rects = TextLayout.HitTestTextRange(start, length); - protected override void OnLostFocus(RoutedEventArgs e) - { - base.OnLostFocus(e); + using (context.PushPostTransform(Matrix.CreateTranslation(origin))) + { + foreach (var rect in rects) + { + context.FillRectangle(selectionBrush, PixelRect.FromRect(rect, 1).ToRect(1)); + } + } + } - ClearSelection(); + base.RenderTextLayout(context, origin); } protected override void OnKeyDown(KeyEventArgs e) @@ -352,11 +240,6 @@ namespace Avalonia.Controls { base.OnPointerPressed(e); - if (!IsTextSelectionEnabled) - { - return; - } - var text = Text; var clickInfo = e.GetCurrentPoint(this); @@ -435,11 +318,6 @@ namespace Avalonia.Controls { base.OnPointerMoved(e); - if (!IsTextSelectionEnabled) - { - return; - } - // selection should not change during pointer move if the user right clicks if (e.Pointer.Captured == this && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed) { @@ -486,11 +364,6 @@ namespace Avalonia.Controls { base.OnPointerReleased(e); - if (!IsTextSelectionEnabled) - { - return; - } - if (e.Pointer.Captured != this) { return; @@ -521,100 +394,15 @@ namespace Avalonia.Controls e.Pointer.Capture(null); } - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) - { - base.OnPropertyChanged(change); - - switch (change.Property.Name) - { - case nameof(Inlines): - { - OnInlinesChanged(change.OldValue as InlineCollection, change.NewValue as InlineCollection); - InvalidateTextLayout(); - break; - } - } - } - - protected override Size MeasureOverride(Size availableSize) + private void UpdateCommandStates() { - if(_textRuns != null) - { - LogicalChildren.Clear(); - - VisualChildren.Clear(); - - _textRuns = null; - } - - if (Inlines != null && Inlines.Count > 0) - { - var inlines = Inlines; - - var textRuns = new List(); - - foreach (var inline in inlines) - { - inline.BuildTextRun(textRuns); - } - - foreach (var textRun in textRuns) - { - if (textRun is EmbeddedControlRun controlRun && - controlRun.Control is Control control) - { - LogicalChildren.Add(control); - - VisualChildren.Add(control); - - control.Measure(Size.Infinity); - } - } - - _textRuns = textRuns; - } - - return base.MeasureOverride(availableSize); - } - - protected override Size ArrangeOverride(Size finalSize) - { - if (HasComplexContent) - { - var currentY = 0.0; - - foreach (var textLine in TextLayout.TextLines) - { - var currentX = textLine.Start; - - foreach (var run in textLine.TextRuns) - { - if (run is DrawableTextRun drawable) - { - if (drawable is EmbeddedControlRun controlRun - && controlRun.Control is Control control) - { - control.Arrange(new Rect(new Point(currentX, currentY), control.DesiredSize)); - } - - currentX += drawable.Size.Width; - } - } + var text = GetSelection(); - currentY += textLine.Height; - } - } - - return base.ArrangeOverride(finalSize); + CanCopy = !string.IsNullOrEmpty(text); } private string GetSelection() { - if (!IsTextSelectionEnabled) - { - return ""; - } - var text = GetText(); if (string.IsNullOrEmpty(text)) @@ -638,59 +426,5 @@ namespace Avalonia.Controls return selectedText; } - - private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue) - { - if (oldValue is not null) - { - oldValue.Parent = null; - oldValue.InlineHost = null; - oldValue.Invalidated -= (s, e) => InvalidateTextLayout(); - } - - if (newValue is not null) - { - newValue.Parent = this; - newValue.InlineHost = this; - newValue.Invalidated += (s, e) => InvalidateTextLayout(); - } - } - - void IInlineHost.Invalidate() - { - InvalidateTextLayout(); - } - - private readonly struct InlinesTextSource : ITextSource - { - private readonly IReadOnlyList _textRuns; - - public InlinesTextSource(IReadOnlyList textRuns) - { - _textRuns = textRuns; - } - - public TextRun? GetTextRun(int textSourceIndex) - { - var currentPosition = 0; - - foreach (var textRun in _textRuns) - { - if (textRun.TextSourceLength == 0) - { - continue; - } - - if (currentPosition >= textSourceIndex) - { - return textRun; - } - - currentPosition += textRun.TextSourceLength; - } - - return null; - } - } } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 99c8068b3d..7d4d326a0c 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using Avalonia.Automation.Peers; using Avalonia.Controls.Documents; using Avalonia.Layout; @@ -12,7 +13,7 @@ namespace Avalonia.Controls /// /// A control that displays a block of text. /// - public class TextBlock : Control, IAddChild + public class TextBlock : Control, IInlineHost { /// /// Defines the property. @@ -96,15 +97,15 @@ namespace Avalonia.Controls public static readonly DirectProperty TextProperty = AvaloniaProperty.RegisterDirect( nameof(Text), - o => o.Text, - (o, v) => o.Text = v); + o => o.GetText(), + (o, v) => o.SetText(v)); /// /// Defines the property. /// public static readonly AttachedProperty TextAlignmentProperty = AvaloniaProperty.RegisterAttached( - nameof(TextAlignment), + nameof(TextAlignment), defaultValue: TextAlignment.Start, inherits: true); @@ -112,14 +113,14 @@ namespace Avalonia.Controls /// Defines the property. /// public static readonly AttachedProperty TextWrappingProperty = - AvaloniaProperty.RegisterAttached(nameof(TextWrapping), + AvaloniaProperty.RegisterAttached(nameof(TextWrapping), inherits: true); /// /// Defines the property. /// public static readonly AttachedProperty TextTrimmingProperty = - AvaloniaProperty.RegisterAttached(nameof(TextTrimming), + AvaloniaProperty.RegisterAttached(nameof(TextTrimming), defaultValue: TextTrimming.None, inherits: true); @@ -129,9 +130,17 @@ namespace Avalonia.Controls public static readonly StyledProperty TextDecorationsProperty = AvaloniaProperty.Register(nameof(TextDecorations)); + /// + /// Defines the property. + /// + public static readonly StyledProperty InlinesProperty = + AvaloniaProperty.Register( + nameof(Inlines)); + internal string? _text; protected TextLayout? _textLayout; protected Size _constraint; + private IReadOnlyList? _textRuns; /// /// Initializes static members of the class. @@ -139,10 +148,19 @@ namespace Avalonia.Controls static TextBlock() { ClipToBoundsProperty.OverrideDefaultValue(true); - + AffectsRender(BackgroundProperty, ForegroundProperty); } + public TextBlock() + { + Inlines = new InlineCollection + { + Parent = this, + InlineHost = this + }; + } + /// /// Gets the used to render the text. /// @@ -288,9 +306,21 @@ namespace Avalonia.Controls get => GetValue(TextDecorationsProperty); set => SetValue(TextDecorationsProperty, value); } - + + /// + /// Gets or sets the inlines. + /// + [Content] + public InlineCollection? Inlines + { + get => GetValue(InlinesProperty); + set => SetValue(InlinesProperty, value); + } + protected override bool BypassFlowDirectionPolicies => true; + internal bool HasComplexContent => Inlines != null && Inlines.Count > 0; + /// /// The BaselineOffset property provides an adjustment to baseline offset /// @@ -513,19 +543,30 @@ namespace Avalonia.Controls TextLayout.Draw(context, origin); } - void IAddChild.AddChild(string text) - { - _text = text; - } - protected virtual string? GetText() { - return _text; + return _text ?? Inlines?.Text; } protected virtual void SetText(string? text) { - SetAndRaise(TextProperty, ref _text, text); + if (Inlines != null && Inlines.Count > 0) + { + var oldValue = Inlines.Text; + + if (!string.IsNullOrEmpty(text)) + { + Inlines.Add(text); + } + + text = Inlines.Text; + + RaisePropertyChanged(TextProperty, oldValue, text); + } + else + { + SetAndRaise(TextProperty, ref _text, text); + } } /// @@ -534,8 +575,10 @@ namespace Avalonia.Controls /// A object. protected virtual TextLayout CreateTextLayout(string? text) { + var typeface = new Typeface(FontFamily, FontStyle, FontWeight, FontStretch); + var defaultProperties = new GenericTextRunProperties( - new Typeface(FontFamily, FontStyle, FontWeight, FontStretch), + typeface, FontSize, TextDecorations, Foreground); @@ -543,8 +586,19 @@ namespace Avalonia.Controls var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false, defaultProperties, TextWrapping, LineHeight, 0); + ITextSource textSource; + + if (_textRuns != null) + { + textSource = new InlinesTextSource(_textRuns); + } + else + { + textSource = new SimpleTextSource((text ?? "").AsMemory(), defaultProperties); + } + return new TextLayout( - new SimpleTextSource((text ?? "").AsMemory(), defaultProperties), + textSource, paragraphProperties, TextTrimming, _constraint.Width, @@ -560,6 +614,8 @@ namespace Avalonia.Controls { _textLayout = null; + InvalidateVisual(); + InvalidateMeasure(); } @@ -573,7 +629,39 @@ namespace Avalonia.Controls _textLayout = null; - InvalidateArrange(); + var inlines = Inlines; + + if (HasComplexContent) + { + if (_textRuns != null) + { + LogicalChildren.Clear(); + + VisualChildren.Clear(); + } + + var textRuns = new List(); + + foreach (var inline in inlines!) + { + inline.BuildTextRun(textRuns); + } + + foreach (var textRun in textRuns) + { + if (textRun is EmbeddedControlRun controlRun && + controlRun.Control is Control control) + { + VisualChildren.Add(control); + + LogicalChildren.Add(control); + + control.Measure(Size.Infinity); + } + } + + _textRuns = textRuns; + } var measuredSize = TextLayout.Bounds.Size.Inflate(padding); @@ -584,16 +672,11 @@ namespace Avalonia.Controls { var textWidth = Math.Ceiling(TextLayout.Bounds.Width); - if(finalSize.Width < textWidth) + if (finalSize.Width < textWidth) { finalSize = finalSize.WithWidth(textWidth); } - if (MathUtilities.AreClose(_constraint.Width, finalSize.Width)) - { - return finalSize; - } - var scale = LayoutHelper.GetLayoutScale(this); var padding = LayoutHelper.RoundLayoutThickness(Padding, scale, scale); @@ -602,6 +685,32 @@ namespace Avalonia.Controls _textLayout = null; + if (HasComplexContent) + { + var currentY = padding.Top; + + foreach (var textLine in TextLayout.TextLines) + { + var currentX = padding.Left + textLine.Start; + + foreach (var run in textLine.TextRuns) + { + if (run is DrawableTextRun drawable) + { + if (drawable is EmbeddedControlRun controlRun + && controlRun.Control is Control control) + { + control.Arrange(new Rect(new Point(currentX, currentY), control.DesiredSize)); + } + + currentX += drawable.Size.Width; + } + } + + currentY += textLine.Height; + } + } + return finalSize; } @@ -610,42 +719,70 @@ namespace Avalonia.Controls return new TextBlockAutomationPeer(this); } - private static bool IsValidMaxLines(int maxLines) => maxLines >= 0; - - private static bool IsValidLineHeight(double lineHeight) => double.IsNaN(lineHeight) || lineHeight > 0; - protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs change) { base.OnPropertyChanged(change); switch (change.Property.Name) { - case nameof (FontSize): - case nameof (FontWeight): - case nameof (FontStyle): - case nameof (FontFamily): - case nameof (FontStretch): + case nameof(FontSize): + case nameof(FontWeight): + case nameof(FontStyle): + case nameof(FontFamily): + case nameof(FontStretch): + + case nameof(TextWrapping): + case nameof(TextTrimming): + case nameof(TextAlignment): + + case nameof(FlowDirection): + + case nameof(Padding): + case nameof(LineHeight): + case nameof(MaxLines): + + case nameof(Text): + case nameof(TextDecorations): + case nameof(Foreground): + { + InvalidateTextLayout(); + break; + } + case nameof(Inlines): + { + OnInlinesChanged(change.OldValue as InlineCollection, change.NewValue as InlineCollection); + InvalidateTextLayout(); + break; + } + } + } - case nameof (TextWrapping): - case nameof (TextTrimming): - case nameof (TextAlignment): + private static bool IsValidMaxLines(int maxLines) => maxLines >= 0; - case nameof (FlowDirection): + private static bool IsValidLineHeight(double lineHeight) => double.IsNaN(lineHeight) || lineHeight > 0; - case nameof (Padding): - case nameof (LineHeight): - case nameof (MaxLines): + private void OnInlinesChanged(InlineCollection? oldValue, InlineCollection? newValue) + { + if (oldValue is not null) + { + oldValue.Parent = null; + oldValue.InlineHost = null; + oldValue.Invalidated -= (s, e) => InvalidateTextLayout(); + } - case nameof (Text): - case nameof (TextDecorations): - case nameof (Foreground): - { - InvalidateTextLayout(); - break; - } + if (newValue is not null) + { + newValue.Parent = this; + newValue.InlineHost = this; + newValue.Invalidated += (s, e) => InvalidateTextLayout(); } } + void IInlineHost.Invalidate() + { + InvalidateTextLayout(); + } + protected readonly struct SimpleTextSource : ITextSource { private readonly ReadOnlySlice _text; @@ -674,5 +811,46 @@ namespace Avalonia.Controls return new TextCharacters(runText, _defaultProperties); } } + + private readonly struct InlinesTextSource : ITextSource + { + private readonly IReadOnlyList _textRuns; + + public InlinesTextSource(IReadOnlyList textRuns) + { + _textRuns = textRuns; + } + + public IReadOnlyList TextRuns => _textRuns; + + public TextRun? GetTextRun(int textSourceIndex) + { + var currentPosition = 0; + + foreach (var textRun in _textRuns) + { + if (textRun.TextSourceLength == 0) + { + continue; + } + + if (textSourceIndex >= currentPosition + textRun.TextSourceLength) + { + currentPosition += textRun.TextSourceLength; + + continue; + } + + if (textRun is TextCharacters textCharacters) + { + return new TextCharacters(textRun.Text.Skip(Math.Max(0, textSourceIndex - currentPosition)), textRun.Properties!); + } + + return textRun; + } + + return null; + } + } } } diff --git a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml index 577539b26b..5383aa3180 100644 --- a/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/FluentControls.xaml @@ -68,7 +68,7 @@ - + diff --git a/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml b/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml deleted file mode 100644 index 75af2efcb1..0000000000 --- a/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - - - diff --git a/src/Avalonia.Themes.Fluent/Controls/SelectableTextBlock.xaml b/src/Avalonia.Themes.Fluent/Controls/SelectableTextBlock.xaml new file mode 100644 index 0000000000..f630969ae6 --- /dev/null +++ b/src/Avalonia.Themes.Fluent/Controls/SelectableTextBlock.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Simple/Controls/RichTextBlock.xaml b/src/Avalonia.Themes.Simple/Controls/RichTextBlock.xaml deleted file mode 100644 index c0570282cb..0000000000 --- a/src/Avalonia.Themes.Simple/Controls/RichTextBlock.xaml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - - - - - diff --git a/src/Avalonia.Themes.Simple/Controls/SelectableTextBlock.xaml b/src/Avalonia.Themes.Simple/Controls/SelectableTextBlock.xaml new file mode 100644 index 0000000000..aaa6448aea --- /dev/null +++ b/src/Avalonia.Themes.Simple/Controls/SelectableTextBlock.xaml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + diff --git a/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml b/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml index 644c6ed416..4aefa0136c 100644 --- a/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml +++ b/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml @@ -64,7 +64,7 @@ - + diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index b07deb1f4d..d6bb37a06a 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -64,7 +64,7 @@ namespace Avalonia.Skia var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale); - if(glyphIndex == 0 && text.Buffer.Span[glyphCluster] == '\t') + if(text.Buffer.Span[glyphCluster] == '\t') { glyphIndex = typeface.GetGlyph(' '); diff --git a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs index 064320f809..7f2cbc6182 100644 --- a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs +++ b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs @@ -64,7 +64,7 @@ namespace Avalonia.Direct2D1.Media var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale); - if (glyphIndex == 0 && text.Buffer.Span[glyphCluster] == '\t') + if (text.Buffer.Span[glyphCluster] == '\t') { glyphIndex = typeface.GetGlyph(' '); diff --git a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs deleted file mode 100644 index 05007e4f2e..0000000000 --- a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs +++ /dev/null @@ -1,132 +0,0 @@ -using Avalonia.Controls.Documents; -using Avalonia.Controls.Presenters; -using Avalonia.Controls.Templates; -using Avalonia.Media; -using Avalonia.UnitTests; -using Xunit; - -namespace Avalonia.Controls.UnitTests -{ - public class RichTextBlockTests - { - [Fact] - public void Changing_InlinesCollection_Should_Invalidate_Measure() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var target = new RichTextBlock(); - - target.Measure(Size.Infinity); - - Assert.True(target.IsMeasureValid); - - target.Inlines.Add(new Run("Hello")); - - Assert.False(target.IsMeasureValid); - - target.Measure(Size.Infinity); - - Assert.True(target.IsMeasureValid); - } - } - - [Fact] - public void Changing_Inlines_Properties_Should_Invalidate_Measure() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var target = new RichTextBlock(); - - var inline = new Run("Hello"); - - target.Inlines.Add(inline); - - target.Measure(Size.Infinity); - - Assert.True(target.IsMeasureValid); - - inline.Foreground = Brushes.Green; - - Assert.False(target.IsMeasureValid); - } - } - - [Fact] - public void Changing_Inlines_Should_Invalidate_Measure() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var target = new RichTextBlock(); - - var inlines = new InlineCollection { new Run("Hello") }; - - target.Measure(Size.Infinity); - - Assert.True(target.IsMeasureValid); - - target.Inlines = inlines; - - Assert.False(target.IsMeasureValid); - } - } - - [Fact] - public void Changing_Inlines_Should_Reset_Inlines_Parent() - { - using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface)) - { - var target = new RichTextBlock(); - - var run = new Run("Hello"); - - target.Inlines.Add(run); - - target.Measure(Size.Infinity); - - Assert.True(target.IsMeasureValid); - - target.Inlines = null; - - Assert.Null(run.Parent); - - target.Inlines = new InlineCollection { run }; - - Assert.Equal(target, run.Parent); - } - } - - [Fact] - public void InlineUIContainer_Child_Schould_Be_Arranged() - { - using (UnitTestApplication.Start(TestServices.StyledWindow)) - { - var target = new RichTextBlock(); - - var button = new Button { Content = "12345678" }; - - button.Template = new FuncControlTemplate public static readonly RoutedEvent SizeChangedEvent = RoutedEvent.Register( - nameof(SizeChanged), RoutingStrategies.Bubble); + nameof(SizeChanged), RoutingStrategies.Direct); /// /// Defines the property. From 62485f53bc26d5f80795799b36df80b445c60d9f Mon Sep 17 00:00:00 2001 From: robloo Date: Sun, 30 Oct 2022 22:10:58 -0400 Subject: [PATCH 15/65] Only raise SizeChanged when the Size component changes (ignore position) --- src/Avalonia.Controls/Control.cs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Control.cs b/src/Avalonia.Controls/Control.cs index e21f0fd33d..beaee34b07 100644 --- a/src/Avalonia.Controls/Control.cs +++ b/src/Avalonia.Controls/Control.cs @@ -556,13 +556,20 @@ namespace Avalonia.Controls var oldValue = change.GetOldValue(); var newValue = change.GetNewValue(); - var sizeChangedEventArgs = new SizeChangedEventArgs( - SizeChangedEvent, - source: this, - previousSize: new Size(oldValue.Width, oldValue.Height), - newSize: new Size(newValue.Width, newValue.Height)); + // Bounds is a Rect with an X/Y Position as well as Height/Width. + // This means it is possible for the Rect to change position but not size. + // Therefore, we want to explicity check only the size and raise an event + // only when that size has changed. + if (newValue.Size != oldValue.Size) + { + var sizeChangedEventArgs = new SizeChangedEventArgs( + SizeChangedEvent, + source: this, + previousSize: new Size(oldValue.Width, oldValue.Height), + newSize: new Size(newValue.Width, newValue.Height)); - RaiseEvent(sizeChangedEventArgs); + RaiseEvent(sizeChangedEventArgs); + } } else if (change.Property == FlowDirectionProperty) { From bdd637298e7822aa3166c9097c99fd9fd49a91ef Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 31 Oct 2022 20:08:50 -0400 Subject: [PATCH 16/65] Take into account LayoutEpsilon when calculating Height/WidthChanged --- .../Interactivity/RoutedEventArgs.cs | 2 +- src/Avalonia.Controls/SizeChangedEventArgs.cs | 29 +++++++++++++++---- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs b/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs index 60a6b64677..2b660e7080 100644 --- a/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs +++ b/src/Avalonia.Base/Interactivity/RoutedEventArgs.cs @@ -3,7 +3,7 @@ using System; namespace Avalonia.Interactivity { /// - /// Provices state information and data specific to a routed event. + /// Provides state information and data specific to a routed event. /// public class RoutedEventArgs : EventArgs { diff --git a/src/Avalonia.Controls/SizeChangedEventArgs.cs b/src/Avalonia.Controls/SizeChangedEventArgs.cs index 201a00a3fc..b3e399ff55 100644 --- a/src/Avalonia.Controls/SizeChangedEventArgs.cs +++ b/src/Avalonia.Controls/SizeChangedEventArgs.cs @@ -1,4 +1,6 @@ using Avalonia.Interactivity; +using Avalonia.Layout; +using Avalonia.Utilities; namespace Avalonia.Controls { @@ -42,14 +44,23 @@ namespace Avalonia.Controls { PreviousSize = previousSize; NewSize = newSize; - HeightChanged = newSize.Height != previousSize.Height; - WidthChanged = newSize.Width != previousSize.Width; + + // Only consider changed when the size difference is greater than LayoutEpsilon + // This compensates for any rounding or precision difference between layout cycles + HeightChanged = !MathUtilities.AreClose(newSize.Height, previousSize.Height, LayoutHelper.LayoutEpsilon); + WidthChanged = !MathUtilities.AreClose(newSize.Width, previousSize.Width, LayoutHelper.LayoutEpsilon); } /// - /// Gets a value indicating whether the height of the new size is different - /// than the previous size height. + /// Gets a value indicating whether the height of the new size is considered + /// different than the previous size height. /// + /// + /// This will take into account layout epsilon and will not be true if both + /// heights are considered equivalent for layout purposes. Remember there can + /// be small variations in the calculations between layout cycles due to + /// rounding and precision even when the size has not otherwise changed. + /// public bool HeightChanged { get; init; } /// @@ -63,9 +74,15 @@ namespace Avalonia.Controls public Size PreviousSize { get; init; } /// - /// Gets a value indicating whether the width of the new size is different - /// than the previous size width. + /// Gets a value indicating whether the width of the new size is considered + /// different than the previous size width. /// + /// + /// This will take into account layout epsilon and will not be true if both + /// heights are considered equivalent for layout purposes. Remember there can + /// be small variations in the calculations between layout cycles due to + /// rounding and precision even when the size has not otherwise changed. + /// public bool WidthChanged { get; init; } } } From 3e53621daee8b07be3e295e5e12931bdec658dc4 Mon Sep 17 00:00:00 2001 From: robloo Date: Mon, 31 Oct 2022 20:12:21 -0400 Subject: [PATCH 17/65] Fix typo --- src/Avalonia.Controls/SizeChangedEventArgs.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Controls/SizeChangedEventArgs.cs b/src/Avalonia.Controls/SizeChangedEventArgs.cs index b3e399ff55..fd40c50505 100644 --- a/src/Avalonia.Controls/SizeChangedEventArgs.cs +++ b/src/Avalonia.Controls/SizeChangedEventArgs.cs @@ -79,7 +79,7 @@ namespace Avalonia.Controls /// /// /// This will take into account layout epsilon and will not be true if both - /// heights are considered equivalent for layout purposes. Remember there can + /// widths are considered equivalent for layout purposes. Remember there can /// be small variations in the calculations between layout cycles due to /// rounding and precision even when the size has not otherwise changed. /// From 9403a3d0c7050a1553c0b5a0958304570453ed9b Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 1 Nov 2022 09:07:42 +0100 Subject: [PATCH 18/65] Fix TextBlock inlines logical tree handling --- .../Documents/InlineCollection.cs | 25 ++++++++++--------- src/Avalonia.Controls/Documents/Span.cs | 4 +-- src/Avalonia.Controls/TextBlock.cs | 17 +++++++++---- 3 files changed, 27 insertions(+), 19 deletions(-) diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index 9ff5627434..54ee9c99d5 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls.Documents [WhitespaceSignificantCollection] public class InlineCollection : AvaloniaList { - private ILogical? _parent; + private IAvaloniaList? _parent; private IInlineHost? _inlineHost; /// @@ -24,28 +24,28 @@ namespace Avalonia.Controls.Documents this.ForEachItem( x => - { - ((ISetLogicalParent)x).SetParent(Parent); + { x.InlineHost = InlineHost; + Parent?.Add(x); Invalidate(); }, x => { - ((ISetLogicalParent)x).SetParent(null); + Parent?.Remove(x); x.InlineHost = InlineHost; Invalidate(); }, () => throw new NotSupportedException()); } - internal ILogical? Parent + internal IAvaloniaList? Parent { get => _parent; set { _parent = value; - OnParentChanged(value); + OnParentChanged(_parent, value); } } @@ -157,20 +157,21 @@ namespace Avalonia.Controls.Documents Invalidated?.Invoke(this, EventArgs.Empty); } - private void OnParentChanged(ILogical? parent) + private void OnParentChanged(IAvaloniaList? oldParent, IAvaloniaList? newParent) { foreach (var child in this) { - var oldParent = child.Parent; - - if (oldParent != parent) + if (oldParent != newParent) { if (oldParent != null) { - ((ISetLogicalParent)child).SetParent(null); + oldParent.Remove(child); } - ((ISetLogicalParent)child).SetParent(parent); + if(newParent != null) + { + newParent.Add(child); + } } } } diff --git a/src/Avalonia.Controls/Documents/Span.cs b/src/Avalonia.Controls/Documents/Span.cs index 363ce1011b..041cdc74ce 100644 --- a/src/Avalonia.Controls/Documents/Span.cs +++ b/src/Avalonia.Controls/Documents/Span.cs @@ -21,7 +21,7 @@ namespace Avalonia.Controls.Documents { Inlines = new InlineCollection { - Parent = this + Parent = LogicalChildren }; } @@ -85,7 +85,7 @@ namespace Avalonia.Controls.Documents if (newValue is not null) { - newValue.Parent = this; + newValue.Parent = LogicalChildren; newValue.InlineHost = InlineHost; newValue.Invalidated += (s, e) => InlineHost?.Invalidate(); } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 7d4d326a0c..75a275f778 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -156,7 +156,7 @@ namespace Avalonia.Controls { Inlines = new InlineCollection { - Parent = this, + Parent = LogicalChildren, InlineHost = this }; } @@ -635,9 +635,16 @@ namespace Avalonia.Controls { if (_textRuns != null) { - LogicalChildren.Clear(); + foreach (var textRun in _textRuns) + { + if (textRun is EmbeddedControlRun controlRun && + controlRun.Control is Control control) + { + VisualChildren.Remove(control); - VisualChildren.Clear(); + LogicalChildren.Remove(control); + } + } } var textRuns = new List(); @@ -772,7 +779,7 @@ namespace Avalonia.Controls if (newValue is not null) { - newValue.Parent = this; + newValue.Parent = LogicalChildren; newValue.InlineHost = this; newValue.Invalidated += (s, e) => InvalidateTextLayout(); } @@ -841,7 +848,7 @@ namespace Avalonia.Controls continue; } - if (textRun is TextCharacters textCharacters) + if (textRun is TextCharacters) { return new TextCharacters(textRun.Text.Skip(Math.Max(0, textSourceIndex - currentPosition)), textRun.Properties!); } From e639aead67cd95e96709e8153aee75e5d4f3ab83 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 1 Nov 2022 10:36:31 +0100 Subject: [PATCH 19/65] Fix logical parent cleanup --- .../Documents/InlineCollection.cs | 20 ++++++++++--------- src/Avalonia.Controls/Documents/Span.cs | 6 +++--- src/Avalonia.Controls/TextBlock.cs | 6 +++--- 3 files changed, 17 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index 54ee9c99d5..a265f88e21 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -12,7 +12,7 @@ namespace Avalonia.Controls.Documents [WhitespaceSignificantCollection] public class InlineCollection : AvaloniaList { - private IAvaloniaList? _parent; + private IAvaloniaList? _logicalChildren; private IInlineHost? _inlineHost; /// @@ -26,26 +26,28 @@ namespace Avalonia.Controls.Documents x => { x.InlineHost = InlineHost; - Parent?.Add(x); + LogicalChildren?.Add(x); Invalidate(); }, x => { - Parent?.Remove(x); + LogicalChildren?.Remove(x); x.InlineHost = InlineHost; Invalidate(); }, () => throw new NotSupportedException()); } - internal IAvaloniaList? Parent + internal IAvaloniaList? LogicalChildren { - get => _parent; + get => _logicalChildren; set { - _parent = value; + var oldValue = _logicalChildren; - OnParentChanged(_parent, value); + _logicalChildren = value; + + OnParentChanged(oldValue, value); } } @@ -116,7 +118,7 @@ namespace Avalonia.Controls.Documents private void AddText(string text) { - if (Parent is TextBlock textBlock && !textBlock.HasComplexContent) + if (LogicalChildren is TextBlock textBlock && !textBlock.HasComplexContent) { textBlock._text += text; } @@ -128,7 +130,7 @@ namespace Avalonia.Controls.Documents private void OnAdd() { - if (Parent is TextBlock textBlock) + if (LogicalChildren is TextBlock textBlock) { if (!textBlock.HasComplexContent && !string.IsNullOrEmpty(textBlock._text)) { diff --git a/src/Avalonia.Controls/Documents/Span.cs b/src/Avalonia.Controls/Documents/Span.cs index 041cdc74ce..a7a702ceae 100644 --- a/src/Avalonia.Controls/Documents/Span.cs +++ b/src/Avalonia.Controls/Documents/Span.cs @@ -21,7 +21,7 @@ namespace Avalonia.Controls.Documents { Inlines = new InlineCollection { - Parent = LogicalChildren + LogicalChildren = LogicalChildren }; } @@ -78,14 +78,14 @@ namespace Avalonia.Controls.Documents { if (oldValue is not null) { - oldValue.Parent = null; + oldValue.LogicalChildren = null; oldValue.InlineHost = null; oldValue.Invalidated -= (s, e) => InlineHost?.Invalidate(); } if (newValue is not null) { - newValue.Parent = LogicalChildren; + newValue.LogicalChildren = LogicalChildren; newValue.InlineHost = InlineHost; newValue.Invalidated += (s, e) => InlineHost?.Invalidate(); } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 75a275f778..f79d3f8296 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -156,7 +156,7 @@ namespace Avalonia.Controls { Inlines = new InlineCollection { - Parent = LogicalChildren, + LogicalChildren = LogicalChildren, InlineHost = this }; } @@ -772,14 +772,14 @@ namespace Avalonia.Controls { if (oldValue is not null) { - oldValue.Parent = null; + oldValue.LogicalChildren = null; oldValue.InlineHost = null; oldValue.Invalidated -= (s, e) => InvalidateTextLayout(); } if (newValue is not null) { - newValue.Parent = LogicalChildren; + newValue.LogicalChildren = LogicalChildren; newValue.InlineHost = this; newValue.Invalidated += (s, e) => InvalidateTextLayout(); } From 66595bad2e9b532a434b6470eb67c2f32f718527 Mon Sep 17 00:00:00 2001 From: robloo Date: Tue, 1 Nov 2022 07:45:03 -0400 Subject: [PATCH 20/65] Calculate Height/WidthChanged in the getter directly --- src/Avalonia.Controls/SizeChangedEventArgs.cs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/Avalonia.Controls/SizeChangedEventArgs.cs b/src/Avalonia.Controls/SizeChangedEventArgs.cs index fd40c50505..2dc642b163 100644 --- a/src/Avalonia.Controls/SizeChangedEventArgs.cs +++ b/src/Avalonia.Controls/SizeChangedEventArgs.cs @@ -44,11 +44,6 @@ namespace Avalonia.Controls { PreviousSize = previousSize; NewSize = newSize; - - // Only consider changed when the size difference is greater than LayoutEpsilon - // This compensates for any rounding or precision difference between layout cycles - HeightChanged = !MathUtilities.AreClose(newSize.Height, previousSize.Height, LayoutHelper.LayoutEpsilon); - WidthChanged = !MathUtilities.AreClose(newSize.Width, previousSize.Width, LayoutHelper.LayoutEpsilon); } /// @@ -61,7 +56,7 @@ namespace Avalonia.Controls /// be small variations in the calculations between layout cycles due to /// rounding and precision even when the size has not otherwise changed. /// - public bool HeightChanged { get; init; } + public bool HeightChanged => !MathUtilities.AreClose(NewSize.Height, PreviousSize.Height, LayoutHelper.LayoutEpsilon); /// /// Gets the new size (or bounds) of the object. @@ -83,6 +78,6 @@ namespace Avalonia.Controls /// be small variations in the calculations between layout cycles due to /// rounding and precision even when the size has not otherwise changed. /// - public bool WidthChanged { get; init; } + public bool WidthChanged => !MathUtilities.AreClose(NewSize.Width, PreviousSize.Width, LayoutHelper.LayoutEpsilon); } } From b13f7cbd38163742c8e2972de4c3aa400b43fac7 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 2 Nov 2022 15:47:18 +0600 Subject: [PATCH 21/65] Use strong-named version of microcom --- nukebuild/_build.csproj | 2 +- src/Avalonia.MicroCom/Avalonia.MicroCom.csproj | 2 +- src/Avalonia.Native/Avalonia.Native.csproj | 2 +- src/Windows/Avalonia.Win32/Avalonia.Win32.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 8c0d824298..865d935ad7 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -13,7 +13,7 @@ - + diff --git a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj index e784bda105..14dafb7284 100644 --- a/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj +++ b/src/Avalonia.MicroCom/Avalonia.MicroCom.csproj @@ -4,7 +4,7 @@ true - + false all diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index 4ceb1be340..c924ef3a45 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -24,7 +24,7 @@ - + diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 0d29bb91ea..308e22b4e2 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -11,7 +11,7 @@ - + From 17b2834d21f7e7acd63586b69f8ece4d7099f131 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Wed, 2 Nov 2022 10:56:06 +0100 Subject: [PATCH 22/65] Implement letter spacing --- src/Avalonia.Base/Media/FormattedText.cs | 3 +- src/Avalonia.Base/Media/GlyphRun.cs | 77 +--------- .../GenericTextParagraphProperties.cs | 19 ++- .../Media/TextFormatting/TextFormatterImpl.cs | 3 +- .../Media/TextFormatting/TextLayout.cs | 21 ++- .../TextFormatting/TextParagraphProperties.cs | 9 +- .../Media/TextFormatting/TextShaperOptions.cs | 9 +- .../Platform/IPlatformRenderInterface.cs | 37 +---- .../Presenters/TextPresenter.cs | 20 ++- src/Avalonia.Controls/TextBlock.cs | 56 +++++++- src/Avalonia.Controls/TextBox.cs | 12 ++ .../HeadlessPlatformRenderInterface.cs | 42 +----- .../Controls/TextBox.xaml | 1 + .../Controls/TextBox.xaml | 4 +- src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs | 4 - .../Avalonia.Skia/PlatformRenderInterface.cs | 126 +++++++++-------- src/Skia/Avalonia.Skia/TextShaperImpl.cs | 2 +- .../Avalonia.Direct2D1/Direct2D1Platform.cs | 131 +++++++++--------- .../VisualTree/MockRenderInterface.cs | 17 +-- .../NullRenderingPlatform.cs | 15 +- .../TextFormatting/TextFormatterTests.cs | 2 +- .../Media/TextFormatting/TextLineTests.cs | 6 +- tests/Avalonia.UnitTests/MockGlyphRun.cs | 12 ++ .../MockPlatformRenderInterface.cs | 2 +- 24 files changed, 306 insertions(+), 324 deletions(-) create mode 100644 tests/Avalonia.UnitTests/MockGlyphRun.cs diff --git a/src/Avalonia.Base/Media/FormattedText.cs b/src/Avalonia.Base/Media/FormattedText.cs index 27d99bdc10..90b9755493 100644 --- a/src/Avalonia.Base/Media/FormattedText.cs +++ b/src/Avalonia.Base/Media/FormattedText.cs @@ -93,7 +93,8 @@ namespace Avalonia.Media runProps, TextWrapping.WrapWithOverflow, 0, // line height not specified - 0 // indentation not specified + 0, // indentation not specified + 0 ); InvalidateMetrics(); diff --git a/src/Avalonia.Base/Media/GlyphRun.cs b/src/Avalonia.Base/Media/GlyphRun.cs index a1cb00e209..d93a68e78b 100644 --- a/src/Avalonia.Base/Media/GlyphRun.cs +++ b/src/Avalonia.Base/Media/GlyphRun.cs @@ -170,7 +170,7 @@ namespace Avalonia.Media } /// - /// Gets the scale of the current + /// Gets the scale of the current /// internal double Scale => FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight; @@ -860,82 +860,9 @@ namespace Avalonia.Media private IGlyphRunImpl CreateGlyphRunImpl() { - IGlyphRunImpl glyphRunImpl; - var platformRenderInterface = AvaloniaLocator.Current.GetRequiredService(); - var count = GlyphIndices.Count; - var scale = (float)(FontRenderingEmSize / GlyphTypeface.Metrics.DesignEmHeight); - - if (GlyphOffsets == null) - { - if (GlyphTypeface.Metrics.IsFixedPitch) - { - var buffer = platformRenderInterface.AllocateGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count); - - var glyphs = buffer.GlyphIndices; - - for (int i = 0; i < glyphs.Length; i++) - { - glyphs[i] = GlyphIndices[i]; - } - - glyphRunImpl = buffer.Build(); - } - else - { - var buffer = platformRenderInterface.AllocateHorizontalGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count); - var glyphs = buffer.GlyphIndices; - var positions = buffer.GlyphPositions; - var width = 0d; - - for (var i = 0; i < count; i++) - { - positions[i] = (float)width; - - if (GlyphAdvances == null) - { - width += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale; - } - else - { - width += GlyphAdvances[i]; - } - - glyphs[i] = GlyphIndices[i]; - } - - glyphRunImpl = buffer.Build(); - } - } - else - { - var buffer = platformRenderInterface.AllocatePositionedGlyphRun(GlyphTypeface, (float)FontRenderingEmSize, count); - var glyphs = buffer.GlyphIndices; - var glyphPositions = buffer.GlyphPositions; - var currentX = 0.0; - - for (var i = 0; i < count; i++) - { - var glyphOffset = GlyphOffsets[i]; - - glyphPositions[i] = new PointF((float)(currentX + glyphOffset.X), (float)glyphOffset.Y); - - if (GlyphAdvances == null) - { - currentX += GlyphTypeface.GetGlyphAdvance(GlyphIndices[i]) * scale; - } - else - { - currentX += GlyphAdvances[i]; - } - - glyphs[i] = GlyphIndices[i]; - } - - glyphRunImpl = buffer.Build(); - } - return glyphRunImpl; + return platformRenderInterface.CreateGlyphRun(GlyphTypeface, FontRenderingEmSize, GlyphIndices, GlyphAdvances, GlyphOffsets); } void IDisposable.Dispose() diff --git a/src/Avalonia.Base/Media/TextFormatting/GenericTextParagraphProperties.cs b/src/Avalonia.Base/Media/TextFormatting/GenericTextParagraphProperties.cs index dccad1e647..b9ed31523e 100644 --- a/src/Avalonia.Base/Media/TextFormatting/GenericTextParagraphProperties.cs +++ b/src/Avalonia.Base/Media/TextFormatting/GenericTextParagraphProperties.cs @@ -17,15 +17,18 @@ /// logical horizontal alignment /// text wrap option /// Paragraph line height + /// letter spacing public GenericTextParagraphProperties(TextRunProperties defaultTextRunProperties, TextAlignment textAlignment = TextAlignment.Left, TextWrapping textWrap = TextWrapping.NoWrap, - double lineHeight = 0) + double lineHeight = 0, + double letterSpacing = 0) { DefaultTextRunProperties = defaultTextRunProperties; _textAlignment = textAlignment; _textWrap = textWrap; _lineHeight = lineHeight; + LetterSpacing = letterSpacing; } /// @@ -39,6 +42,7 @@ /// text wrap option /// Paragraph line height /// line indentation + /// letter spacing public GenericTextParagraphProperties( FlowDirection flowDirection, TextAlignment textAlignment, @@ -47,8 +51,8 @@ TextRunProperties defaultTextRunProperties, TextWrapping textWrap, double lineHeight, - double indent - ) + double indent, + double letterSpacing) { _flowDirection = flowDirection; _textAlignment = textAlignment; @@ -57,6 +61,7 @@ DefaultTextRunProperties = defaultTextRunProperties; _textWrap = textWrap; _lineHeight = lineHeight; + LetterSpacing = letterSpacing; Indent = indent; } @@ -72,7 +77,8 @@ textParagraphProperties.DefaultTextRunProperties, textParagraphProperties.TextWrapping, textParagraphProperties.LineHeight, - textParagraphProperties.Indent) + textParagraphProperties.Indent, + textParagraphProperties.LetterSpacing) { } @@ -131,6 +137,11 @@ /// public override double Indent { get; } + /// + /// The letter spacing + /// + public override double LetterSpacing { get; } + /// /// Set text flow direction /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs index 145c99cadc..7bad95c4a2 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextFormatterImpl.cs @@ -249,7 +249,8 @@ namespace Avalonia.Media.TextFormatting var shaperOptions = new TextShaperOptions(currentRun.Properties!.Typeface.GlyphTypeface, currentRun.Properties.FontRenderingEmSize, - shapeableRun.BidiLevel, currentRun.Properties.CultureInfo, paragraphProperties.DefaultIncrementalTab); + shapeableRun.BidiLevel, currentRun.Properties.CultureInfo, + paragraphProperties.DefaultIncrementalTab, paragraphProperties.LetterSpacing); drawableTextRuns.AddRange(ShapeTogether(groupedRuns, text, shaperOptions)); diff --git a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs index 0828b6518a..dc79e61333 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextLayout.cs @@ -31,6 +31,7 @@ namespace Avalonia.Media.TextFormatting /// The maximum width. /// The maximum height. /// The height of each line of text. + /// The letter spacing that is applied to rendered glyphs. /// The maximum number of text lines. /// The text style overrides. public TextLayout( @@ -46,12 +47,13 @@ namespace Avalonia.Media.TextFormatting double maxWidth = double.PositiveInfinity, double maxHeight = double.PositiveInfinity, double lineHeight = double.NaN, + double letterSpacing = 0, int maxLines = 0, IReadOnlyList>? textStyleOverrides = null) { _paragraphProperties = CreateTextParagraphProperties(typeface, fontSize, foreground, textAlignment, textWrapping, - textDecorations, flowDirection, lineHeight); + textDecorations, flowDirection, lineHeight, letterSpacing); _textSource = new FormattedTextSource(text.AsMemory(), _paragraphProperties.DefaultTextRunProperties, textStyleOverrides); @@ -63,6 +65,8 @@ namespace Avalonia.Media.TextFormatting MaxHeight = maxHeight; + LetterSpacing = letterSpacing; + MaxLines = maxLines; TextLines = CreateTextLines(); @@ -77,6 +81,7 @@ namespace Avalonia.Media.TextFormatting /// The maximum width. /// The maximum height. /// The height of each line of text. + /// The letter spacing that is applied to rendered glyphs. /// The maximum number of text lines. public TextLayout( ITextSource textSource, @@ -85,6 +90,7 @@ namespace Avalonia.Media.TextFormatting double maxWidth = double.PositiveInfinity, double maxHeight = double.PositiveInfinity, double lineHeight = double.NaN, + double letterSpacing = 0, int maxLines = 0) { _textSource = textSource; @@ -99,6 +105,8 @@ namespace Avalonia.Media.TextFormatting MaxHeight = maxHeight; + LetterSpacing = letterSpacing; + MaxLines = maxLines; TextLines = CreateTextLines(); @@ -128,6 +136,11 @@ namespace Avalonia.Media.TextFormatting /// public int MaxLines { get; } + /// + /// Gets the text spacing. + /// + public double LetterSpacing { get; } + /// /// Gets the text lines. /// @@ -374,15 +387,17 @@ namespace Avalonia.Media.TextFormatting /// The text decorations. /// The text flow direction. /// The height of each line of text. + /// The letter spacing that is applied to rendered glyphs. /// private static TextParagraphProperties CreateTextParagraphProperties(Typeface typeface, double fontSize, IBrush? foreground, TextAlignment textAlignment, TextWrapping textWrapping, - TextDecorationCollection? textDecorations, FlowDirection flowDirection, double lineHeight) + TextDecorationCollection? textDecorations, FlowDirection flowDirection, double lineHeight, + double letterSpacing) { var textRunStyle = new GenericTextRunProperties(typeface, fontSize, textDecorations, foreground); return new GenericTextParagraphProperties(flowDirection, textAlignment, true, false, - textRunStyle, textWrapping, lineHeight, 0); + textRunStyle, textWrapping, lineHeight, 0, letterSpacing); } /// diff --git a/src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs b/src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs index 82a0ba14d8..5691dd8ad0 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextParagraphProperties.cs @@ -57,7 +57,7 @@ public abstract double Indent { get; } /// - /// Paragraph indentation + /// Get the paragraph indentation. /// public virtual double ParagraphIndent { @@ -65,11 +65,16 @@ } /// - /// Default Incremental Tab + /// Gets the default incremental tab width. /// public virtual double DefaultIncrementalTab { get { return 4 * DefaultTextRunProperties.FontRenderingEmSize; } } + + /// + /// Gets the letter spacing. + /// + public virtual double LetterSpacing { get; } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs index 0d00bed51e..80bbbcdbfe 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextShaperOptions.cs @@ -12,13 +12,15 @@ namespace Avalonia.Media.TextFormatting double fontRenderingEmSize = 12, sbyte bidiLevel = 0, CultureInfo? culture = null, - double incrementalTabWidth = 0) + double incrementalTabWidth = 0, + double letterSpacing = 0) { Typeface = typeface; FontRenderingEmSize = fontRenderingEmSize; BidiLevel = bidiLevel; Culture = culture; IncrementalTabWidth = incrementalTabWidth; + LetterSpacing = letterSpacing; } /// @@ -45,5 +47,10 @@ namespace Avalonia.Media.TextFormatting /// public double IncrementalTabWidth { get; } + /// + /// Get the letter spacing. + /// + public double LetterSpacing { get; } + } } diff --git a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs index 9d0d7974b4..518c5f37b8 100644 --- a/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs +++ b/src/Avalonia.Base/Platform/IPlatformRenderInterface.cs @@ -171,40 +171,15 @@ namespace Avalonia.Platform IBitmapImpl LoadBitmap(PixelFormat format, AlphaFormat alphaFormat, IntPtr data, PixelSize size, Vector dpi, int stride); /// - /// Allocates a platform glyph run buffer. + /// Creates a platform implementation of a glyph run. /// /// The glyph typeface. /// The font rendering em size. - /// The length. - /// An . - /// - /// This buffer only holds glyph indices. - /// - IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); - - /// - /// Allocates a horizontal platform glyph run buffer. - /// - /// The glyph typeface. - /// The font rendering em size. - /// The length. - /// An . - /// - /// This buffer holds glyph indices and glyph advances. - /// - IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); - - /// - /// Allocates a positioned platform glyph run buffer. - /// - /// The glyph typeface. - /// The font rendering em size. - /// The length. - /// An . - /// - /// This buffer holds glyph indices, glyph advances and glyph positions. - /// - IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length); + /// The glyph indices. + /// The glyph advances. + /// The glyph offsets. + /// + IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList? glyphAdvances, IReadOnlyList? glyphOffsets); /// /// Gets a value indicating whether the platform directly supports rectangles with rounded corners. diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index a9bb16c7df..adf0569551 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -80,6 +80,12 @@ namespace Avalonia.Controls.Presenters public static readonly StyledProperty LineHeightProperty = TextBlock.LineHeightProperty.AddOwner(); + /// + /// Defines the property. + /// + public static readonly StyledProperty LetterSpacingProperty = + TextBlock.LetterSpacingProperty.AddOwner(); + /// /// Defines the property. /// @@ -212,6 +218,15 @@ namespace Avalonia.Controls.Presenters set => SetValue(LineHeightProperty, value); } + /// + /// Gets or sets the letter spacing. + /// + public double LetterSpacing + { + get => GetValue(LetterSpacingProperty); + set => SetValue(LetterSpacingProperty, value); + } + /// /// Gets or sets the text alignment. /// @@ -333,7 +348,7 @@ namespace Avalonia.Controls.Presenters var textLayout = new TextLayout(text, typeface, FontSize, foreground, TextAlignment, TextWrapping, maxWidth: maxWidth, maxHeight: maxHeight, textStyleOverrides: textStyleOverrides, - flowDirection: FlowDirection, lineHeight: LineHeight); + flowDirection: FlowDirection, lineHeight: LineHeight, letterSpacing: LetterSpacing); return textLayout; } @@ -916,6 +931,9 @@ namespace Avalonia.Controls.Presenters case nameof(TextAlignment): case nameof(TextWrapping): + case nameof(LineHeight): + case nameof(LetterSpacing): + case nameof(SelectionStart): case nameof(SelectionEnd): case nameof(SelectionForegroundBrush): diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index f79d3f8296..0492c2c1e3 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -82,6 +82,15 @@ namespace Avalonia.Controls validate: IsValidLineHeight, inherits: true); + /// + /// Defines the property. + /// + public static readonly AttachedProperty LetterSpacingProperty = + AvaloniaProperty.RegisterAttached( + nameof(LetterSpacing), + 0, + inherits: true); + /// /// Defines the property. /// @@ -262,6 +271,15 @@ namespace Avalonia.Controls set => SetValue(LineHeightProperty, value); } + /// + /// Gets or sets the letter spacing. + /// + public double LetterSpacing + { + get => GetValue(LetterSpacingProperty); + set => SetValue(LetterSpacingProperty, value); + } + /// /// Gets or sets the maximum number of text lines. /// @@ -475,6 +493,35 @@ namespace Avalonia.Controls control.SetValue(LineHeightProperty, height); } + /// + /// Reads the attached property from the given element + /// + /// The element to which to read the attached property. + public static double GetLetterSpacing(Control control) + { + if (control == null) + { + throw new ArgumentNullException(nameof(control)); + } + + return control.GetValue(LetterSpacingProperty); + } + + /// + /// Writes the attached property LetterSpacing to the given element. + /// + /// The element to which to write the attached property. + /// The property value to set + public static void SetLetterSpacing(Control control, double letterSpacing) + { + if (control == null) + { + throw new ArgumentNullException(nameof(control)); + } + + control.SetValue(LetterSpacingProperty, letterSpacing); + } + /// /// Reads the attached property from the given element /// @@ -584,7 +631,7 @@ namespace Avalonia.Controls Foreground); var paragraphProperties = new GenericTextParagraphProperties(FlowDirection, TextAlignment, true, false, - defaultProperties, TextWrapping, LineHeight, 0); + defaultProperties, TextWrapping, LineHeight, 0, LetterSpacing); ITextSource textSource; @@ -744,9 +791,10 @@ namespace Avalonia.Controls case nameof(FlowDirection): - case nameof(Padding): - case nameof(LineHeight): - case nameof(MaxLines): + case nameof (Padding): + case nameof (LineHeight): + case nameof (LetterSpacing): + case nameof (MaxLines): case nameof(Text): case nameof(TextDecorations): diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index da4e90fb66..85c1c9a9d1 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -114,6 +114,12 @@ namespace Avalonia.Controls public static readonly StyledProperty LineHeightProperty = TextBlock.LineHeightProperty.AddOwner(); + /// + /// Defines see property. + /// + public static readonly StyledProperty LetterSpacingProperty = + TextBlock.LetterSpacingProperty.AddOwner(); + public static readonly StyledProperty WatermarkProperty = AvaloniaProperty.Register(nameof(Watermark)); @@ -378,6 +384,12 @@ namespace Avalonia.Controls set => SetValue(MaxLinesProperty, value); } + public double LetterSpacing + { + get => GetValue(LetterSpacingProperty); + set => SetValue(LetterSpacingProperty, value); + } + /// /// Gets or sets the line height. /// diff --git a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs index fcd7f1e31f..501d239cee 100644 --- a/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs +++ b/src/Avalonia.Headless/HeadlessPlatformRenderInterface.cs @@ -115,19 +115,16 @@ namespace Avalonia.Headless return new HeadlessGeometryStub(new Rect(glyphRun.Size)); } - public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) { - return new HeadlessGlyphRunBufferStub(); + return new HeadlessGlyphRunStub(); } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - return new HeadlessHorizontalGlyphRunBufferStub(); - } - - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + class HeadlessGlyphRunStub : IGlyphRunImpl { - return new HeadlessPositionedGlyphRunBufferStub(); + public void Dispose() + { + } } class HeadlessGeometryStub : IGeometryImpl @@ -213,33 +210,6 @@ namespace Avalonia.Headless public Matrix Transform { get; } } - class HeadlessGlyphRunBufferStub : IGlyphRunBuffer - { - public Span GlyphIndices => Span.Empty; - - public IGlyphRunImpl Build() - { - return new HeadlessGlyphRunStub(); - } - } - - class HeadlessHorizontalGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IHorizontalGlyphRunBuffer - { - public Span GlyphPositions => Span.Empty; - } - - class HeadlessPositionedGlyphRunBufferStub : HeadlessGlyphRunBufferStub, IPositionedGlyphRunBuffer - { - public Span GlyphPositions => Span.Empty; - } - - class HeadlessGlyphRunStub : IGlyphRunImpl - { - public void Dispose() - { - } - } - class HeadlessStreamingGeometryStub : HeadlessGeometryStub, IStreamGeometryImpl { public HeadlessStreamingGeometryStub() : base(Rect.Empty) diff --git a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml index 17c69da8fd..db487ef76b 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml @@ -161,6 +161,7 @@ TextAlignment="{TemplateBinding TextAlignment}" TextWrapping="{TemplateBinding TextWrapping}" LineHeight="{TemplateBinding LineHeight}" + LetterSpacing="{TemplateBinding LetterSpacing}" PasswordChar="{TemplateBinding PasswordChar}" RevealPassword="{TemplateBinding RevealPassword}" SelectionBrush="{TemplateBinding SelectionBrush}" diff --git a/src/Avalonia.Themes.Simple/Controls/TextBox.xaml b/src/Avalonia.Themes.Simple/Controls/TextBox.xaml index 5fa6412688..0bcb425ca9 100644 --- a/src/Avalonia.Themes.Simple/Controls/TextBox.xaml +++ b/src/Avalonia.Themes.Simple/Controls/TextBox.xaml @@ -149,14 +149,14 @@ CaretBrush="{TemplateBinding CaretBrush}" CaretIndex="{TemplateBinding CaretIndex}" LineHeight="{TemplateBinding LineHeight}" + LetterSpacing="{TemplateBinding LetterSpacing}" PasswordChar="{TemplateBinding PasswordChar}" RevealPassword="{TemplateBinding RevealPassword}" SelectionBrush="{TemplateBinding SelectionBrush}" SelectionEnd="{TemplateBinding SelectionEnd}" SelectionForegroundBrush="{TemplateBinding SelectionForegroundBrush}" SelectionStart="{TemplateBinding SelectionStart}" - Text="{TemplateBinding Text, - Mode=TwoWay}" + Text="{TemplateBinding Text,Mode=TwoWay}" TextAlignment="{TemplateBinding TextAlignment}" TextWrapping="{TemplateBinding TextWrapping}" /> diff --git a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs index d11f4aa7d3..71bdc1bd6b 100644 --- a/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs +++ b/src/Skia/Avalonia.Skia/GlyphTypefaceImpl.cs @@ -69,10 +69,6 @@ namespace Avalonia.Skia public int GlyphCount { get; } - public bool IsFakeBold { get; } - - public bool IsFakeItalic { get; } - public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics) { metrics = default; diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs index a9696efbd4..dd3badb2d8 100644 --- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs +++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs @@ -12,8 +12,6 @@ using Avalonia.OpenGL.Imaging; using Avalonia.Platform; using Avalonia.Media.Imaging; using SkiaSharp; -using System.Runtime.InteropServices; -using System.Drawing; namespace Avalonia.Skia { @@ -79,7 +77,7 @@ namespace Avalonia.Skia var skFont = new SKFont(glyphTypeface.Typeface, fontRenderingEmSize) { Size = fontRenderingEmSize, - Edging = SKFontEdging.Antialias, + Edging = SKFontEdging.Alias, Hinting = SKFontHinting.None, LinearMetrics = true }; @@ -244,85 +242,91 @@ namespace Avalonia.Skia "Current GPU acceleration backend does not support OpenGL integration"); } - public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - => new SKGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, + IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) + { + if (glyphTypeface == null) + { + throw new ArgumentNullException(nameof(glyphTypeface)); + } - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - => new SKHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); + if (glyphIndices == null) + { + throw new ArgumentNullException(nameof(glyphIndices)); + } - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - => new SKPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); + var glyphTypefaceImpl = glyphTypeface as GlyphTypefaceImpl; - private abstract class SKGlyphRunBufferBase : IGlyphRunBuffer - { - protected readonly SKTextBlobBuilder _builder; - protected readonly SKFont _font; + var font = new SKFont + { + LinearMetrics = true, + Subpixel = true, + Edging = SKFontEdging.SubpixelAntialias, + Hinting = SKFontHinting.Full, + Size = (float)fontRenderingEmSize, + Typeface = glyphTypefaceImpl.Typeface, + Embolden = (glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0, + SkewX = (glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0 + }; + + var builder = new SKTextBlobBuilder(); - public SKGlyphRunBufferBase(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + var count = glyphIndices.Count; + + if(glyphOffsets != null && glyphAdvances != null) { - _builder = new SKTextBlobBuilder(); + var runBuffer = builder.AllocatePositionedRun(font, count); - var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; + var glyphSpan = runBuffer.GetGlyphSpan(); + var positionSpan = runBuffer.GetPositionSpan(); - _font = new SKFont - { - Subpixel = true, - Edging = SKFontEdging.SubpixelAntialias, - Hinting = SKFontHinting.Full, - LinearMetrics = true, - Size = fontRenderingEmSize, - Typeface = glyphTypefaceImpl.Typeface, - Embolden = glyphTypefaceImpl.IsFakeBold, - SkewX = glyphTypefaceImpl.IsFakeItalic ? -0.2f : 0 - }; - } + var currentX = 0.0; - public abstract Span GlyphIndices { get; } + for (int i = 0; i < glyphOffsets.Count; i++) + { + var offset = glyphOffsets[i]; - public IGlyphRunImpl Build() - { - return new GlyphRunImpl(_builder.Build()); - } - } + glyphSpan[i] = glyphIndices[i]; - private sealed class SKGlyphRunBuffer : SKGlyphRunBufferBase - { - private readonly SKRunBuffer _buffer; + positionSpan[i] = new SKPoint((float)(currentX + offset.X), (float)offset.Y); - public SKGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) - { - _buffer = _builder.AllocateRun(_font, length, 0, 0); + currentX += glyphAdvances[i]; + } } + else + { + if(glyphAdvances != null) + { + var runBuffer = builder.AllocateHorizontalRun(font, count, 0); - public override Span GlyphIndices => _buffer.GetGlyphSpan(); - } + var glyphSpan = runBuffer.GetGlyphSpan(); + var positionSpan = runBuffer.GetPositionSpan(); - private sealed class SKHorizontalGlyphRunBuffer : SKGlyphRunBufferBase, IHorizontalGlyphRunBuffer - { - private readonly SKHorizontalRunBuffer _buffer; + var currentX = 0.0; - public SKHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) - { - _buffer = _builder.AllocateHorizontalRun(_font, length, 0); - } + for (int i = 0; i < glyphOffsets.Count; i++) + { + glyphSpan[i] = glyphIndices[i]; - public override Span GlyphIndices => _buffer.GetGlyphSpan(); + positionSpan[i] = (float)currentX; - public Span GlyphPositions => _buffer.GetPositionSpan(); - } + currentX += glyphAdvances[i]; + } + } + else + { + var runBuffer = builder.AllocateRun(font, count, 0, 0); - private sealed class SKPositionedGlyphRunBuffer : SKGlyphRunBufferBase, IPositionedGlyphRunBuffer - { - private readonly SKPositionedRunBuffer _buffer; + var glyphSpan = runBuffer.GetGlyphSpan(); - public SKPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length) - { - _buffer = _builder.AllocatePositionedRun(_font, length); + for (int i = 0; i < glyphOffsets.Count; i++) + { + glyphSpan[i] = glyphIndices[i]; + } + } } - public override Span GlyphIndices => _buffer.GetGlyphSpan(); - - public Span GlyphPositions => MemoryMarshal.Cast(_buffer.GetPositionSpan()); + return new GlyphRunImpl(builder.Build()); } } } diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs index d6bb37a06a..eaf588c27d 100644 --- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs +++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs @@ -60,7 +60,7 @@ namespace Avalonia.Skia var glyphCluster = (int)(sourceInfo.Cluster); - var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale); + var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale) + options.LetterSpacing; var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale); diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs index 8103f89dad..4d307c9762 100644 --- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs +++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs @@ -10,10 +10,7 @@ using Avalonia.Media.Imaging; using Avalonia.Platform; using SharpDX.DirectWrite; using GlyphRun = Avalonia.Media.GlyphRun; -using TextAlignment = Avalonia.Media.TextAlignment; using SharpDX.Mathematics.Interop; -using System.Runtime.InteropServices; -using System.Drawing; namespace Avalonia { @@ -160,6 +157,72 @@ namespace Avalonia.Direct2D1 public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) => new GeometryGroupImpl(fillRule, children); public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => new CombinedGeometryImpl(combineMode, g1, g2); + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, + IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) + { + var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; + + var glyphCount = glyphIndices.Count; + + var run = new SharpDX.DirectWrite.GlyphRun + { + FontFace = glyphTypefaceImpl.FontFace, + FontSize = (float)fontRenderingEmSize + }; + + var indices = new short[glyphCount]; + + for (var i = 0; i < glyphCount; i++) + { + indices[i] = (short)glyphIndices[i]; + } + + run.Indices = indices; + + run.Advances = new float[glyphCount]; + + var scale = (float)(fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight); + + if (glyphAdvances == null) + { + for (var i = 0; i < glyphCount; i++) + { + var advance = glyphTypeface.GetGlyphAdvance(glyphIndices[i]) * scale; + + run.Advances[i] = advance; + } + } + else + { + for (var i = 0; i < glyphCount; i++) + { + var advance = (float)glyphAdvances[i]; + + run.Advances[i] = advance; + } + } + + if (glyphOffsets == null) + { + return new GlyphRunImpl(run); + } + + run.Offsets = new GlyphOffset[glyphCount]; + + for (var i = 0; i < glyphCount; i++) + { + var (x, y) = glyphOffsets[i]; + + run.Offsets[i] = new GlyphOffset + { + AdvanceOffset = (float)x, + AscenderOffset = (float)y + }; + } + + return new GlyphRunImpl(run); + } + public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun) { if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface) @@ -260,68 +323,6 @@ namespace Avalonia.Direct2D1 return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride); } - private class DWGlyphRunBuffer : IGlyphRunBuffer - { - protected readonly SharpDX.DirectWrite.GlyphRun _dwRun; - - public DWGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface; - - _dwRun = new SharpDX.DirectWrite.GlyphRun - { - FontFace = glyphTypefaceImpl.FontFace, - FontSize = fontRenderingEmSize, - Indices = new short[length] - }; - } - - public Span GlyphIndices => MemoryMarshal.Cast(_dwRun.Indices.AsSpan()); - - public IGlyphRunImpl Build() - { - return new GlyphRunImpl(_dwRun); - } - } - - private class DWHorizontalGlyphRunBuffer : DWGlyphRunBuffer, IHorizontalGlyphRunBuffer - { - public DWHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - : base(glyphTypeface, fontRenderingEmSize, length) - { - _dwRun.Advances = new float[length]; - } - - public Span GlyphPositions => _dwRun.Advances.AsSpan(); - } - - private class DWPositionedGlyphRunBuffer : DWGlyphRunBuffer, IPositionedGlyphRunBuffer - { - public DWPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - : base(glyphTypeface, fontRenderingEmSize, length) - { - _dwRun.Advances = new float[length]; - _dwRun.Offsets = new GlyphOffset[length]; - } - - public Span GlyphPositions => MemoryMarshal.Cast(_dwRun.Offsets.AsSpan()); - } - - public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - return new DWGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - } - - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - return new DWHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - } - - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - return new DWPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length); - } - public bool SupportsIndividualRoundRects => false; public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul; diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs index 5f8eb45f71..10db08f302 100644 --- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs +++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs @@ -72,7 +72,7 @@ namespace Avalonia.Base.UnitTests.VisualTree throw new NotImplementedException(); } - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) { throw new NotImplementedException(); } @@ -126,21 +126,6 @@ namespace Avalonia.Base.UnitTests.VisualTree throw new NotImplementedException(); } - public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - throw new NotImplementedException(); - } - - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - throw new NotImplementedException(); - } - - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - throw new NotImplementedException(); - } - class MockStreamGeometry : IStreamGeometryImpl { private MockStreamGeometryContext _impl = new MockStreamGeometryContext(); diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs index 34f0dfef11..4170de71e6 100644 --- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs +++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs @@ -5,6 +5,7 @@ using Avalonia.Media; using Avalonia.Platform; using Avalonia.UnitTests; using Avalonia.Media.Imaging; +using Microsoft.Diagnostics.Runtime; namespace Avalonia.Benchmarks { @@ -117,19 +118,9 @@ namespace Avalonia.Benchmarks return new MockStreamGeometryImpl(); } - public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) { - throw new NotImplementedException(); - } - - public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - throw new NotImplementedException(); - } - - public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) - { - throw new NotImplementedException(); + return new MockGlyphRun(); } public bool SupportsIndividualRoundRects => true; diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs index a315158e1b..316926b00c 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs @@ -425,7 +425,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var defaultProperties = new GenericTextRunProperties(Typeface.Default); var paragraphProperties = new GenericTextParagraphProperties(flowDirection, textAlignment, true, true, - defaultProperties, TextWrapping.NoWrap, 0, 0); + defaultProperties, TextWrapping.NoWrap, 0, 0, 0); var textSource = new SingleBufferTextSource(text, defaultProperties); var formatter = new TextFormatterImpl(); diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs index 33c18c5064..87de9ed11f 100644 --- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs +++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs @@ -878,7 +878,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = formatter.FormatLine(textSource, 0, 200, - new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left, true, true, defaultProperties, TextWrapping.NoWrap, 0, 0)); + new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left, + true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0)); var textBounds = textLine.GetTextBounds(0, 3); @@ -924,7 +925,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting var textLine = formatter.FormatLine(textSource, 0, 200, - new GenericTextParagraphProperties(FlowDirection.RightToLeft, TextAlignment.Left, true, true, defaultProperties, TextWrapping.NoWrap, 0, 0)); + new GenericTextParagraphProperties(FlowDirection.RightToLeft, TextAlignment.Left, + true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0)); var textBounds = textLine.GetTextBounds(0, 4); diff --git a/tests/Avalonia.UnitTests/MockGlyphRun.cs b/tests/Avalonia.UnitTests/MockGlyphRun.cs new file mode 100644 index 0000000000..24948aff01 --- /dev/null +++ b/tests/Avalonia.UnitTests/MockGlyphRun.cs @@ -0,0 +1,12 @@ +using Avalonia.Platform; + +namespace Avalonia.UnitTests +{ + public class MockGlyphRun : IGlyphRunImpl + { + public void Dispose() + { + + } + } +} diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs index 586436ef7f..0f951ed867 100644 --- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs +++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs @@ -142,7 +142,7 @@ namespace Avalonia.UnitTests throw new NotImplementedException(); } - public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun) + public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets) { return Mock.Of(); } From 79e2d44005f27b3d1611ee90a8c89c3196122770 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Wed, 2 Nov 2022 16:09:01 +0600 Subject: [PATCH 23/65] Only use scoped keyword when building with .NET 7 SDK --- Directory.Build.targets | 5 +++++ src/Avalonia.Base/Media/PathMarkupParser.cs | 12 ++++++++++-- src/Avalonia.Base/Utilities/IdentifierParser.cs | 6 +++++- .../Markup/Parsers/BindingExpressionGrammar.cs | 12 ++++++++++-- 4 files changed, 30 insertions(+), 5 deletions(-) create mode 100644 Directory.Build.targets diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 0000000000..73954c7f4d --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,5 @@ + + + $(DefineConstants);NET7SDK + + diff --git a/src/Avalonia.Base/Media/PathMarkupParser.cs b/src/Avalonia.Base/Media/PathMarkupParser.cs index cf12bf5126..5e808488fc 100644 --- a/src/Avalonia.Base/Media/PathMarkupParser.cs +++ b/src/Avalonia.Base/Media/PathMarkupParser.cs @@ -188,7 +188,11 @@ namespace Avalonia.Media _isOpen = true; } - private void SetFillRule(scoped ref ReadOnlySpan span) + private void SetFillRule( +#if NET7SDK + scoped +#endif + ref ReadOnlySpan span) { ThrowIfDisposed(); @@ -452,7 +456,11 @@ namespace Avalonia.Media return !span.IsEmpty && (span[0] == ',' || span[0] == '-' || span[0] == '.' || char.IsDigit(span[0])); } - private static bool ReadArgument(scoped ref ReadOnlySpan remaining, out ReadOnlySpan argument) + private static bool ReadArgument( +#if NET7SDK + scoped +#endif + ref ReadOnlySpan remaining, out ReadOnlySpan argument) { remaining = SkipWhitespace(remaining); if (remaining.IsEmpty) diff --git a/src/Avalonia.Base/Utilities/IdentifierParser.cs b/src/Avalonia.Base/Utilities/IdentifierParser.cs index ee176a6b85..76e6459e2e 100644 --- a/src/Avalonia.Base/Utilities/IdentifierParser.cs +++ b/src/Avalonia.Base/Utilities/IdentifierParser.cs @@ -8,7 +8,11 @@ namespace Avalonia.Utilities #endif static class IdentifierParser { - public static ReadOnlySpan ParseIdentifier(this scoped ref CharacterReader r) + public static ReadOnlySpan ParseIdentifier(this +#if NET7SDK + scoped +#endif + ref CharacterReader r) { if (IsValidIdentifierStart(r.Peek)) { diff --git a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs index 0a9fbcfacb..21c0d97c74 100644 --- a/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs +++ b/src/Markup/Avalonia.Markup/Markup/Parsers/BindingExpressionGrammar.cs @@ -168,7 +168,11 @@ namespace Avalonia.Markup.Parsers } } - private static State ParseAttachedProperty(scoped ref CharacterReader r, List nodes) + private static State ParseAttachedProperty( +#if NET7SDK + scoped +#endif + ref CharacterReader r, List nodes) { var (ns, owner) = ParseTypeName(ref r); @@ -318,7 +322,11 @@ namespace Avalonia.Markup.Parsers return State.AfterMember; } - private static TypeName ParseTypeName(scoped ref CharacterReader r) + private static TypeName ParseTypeName( +#if NET7SDK + scoped +#endif + ref CharacterReader r) { ReadOnlySpan ns, typeName; ns = ReadOnlySpan.Empty; From 1b7544509d39fba46af148e1d1f4412884dc7491 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 2 Nov 2022 11:13:51 +0000 Subject: [PATCH 24/65] fix minimise button being disabled when either a parent or a dialog. --- native/Avalonia.Native/src/OSX/WindowImpl.mm | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm index ddc50c26b6..2443965957 100644 --- a/native/Avalonia.Native/src/OSX/WindowImpl.mm +++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm @@ -63,7 +63,7 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) { START_COM_CALL; @autoreleasepool { - _isDialog = isDialog; + _isDialog = isDialog || _parent != nullptr; WindowBaseImpl::Show(activate, isDialog); @@ -96,6 +96,8 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) { auto cparent = dynamic_cast(parent); _parent = cparent; + + _isDialog = _parent != nullptr; if(_parent != nullptr && Window != nullptr){ // If one tries to show a child window with a minimized parent window, then the parent window will be From 9aca3d827c24003106a60da5acbded5810e982c9 Mon Sep 17 00:00:00 2001 From: hacklex Date: Wed, 2 Nov 2022 18:00:05 +0300 Subject: [PATCH 25/65] Replaced hard-coded indexer with its actual name This was possibly just overlooked since nobody probably ever changed the name from "Item". --- src/Avalonia.Base/Collections/AvaloniaDictionary.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs index 750fb263f5..35a391f2cb 100644 --- a/src/Avalonia.Base/Collections/AvaloniaDictionary.cs +++ b/src/Avalonia.Base/Collections/AvaloniaDictionary.cs @@ -81,7 +81,7 @@ namespace Avalonia.Collections if (replace) { - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"{CommonPropertyNames.IndexerName}[{key}]")); if (CollectionChanged != null) { @@ -148,7 +148,7 @@ namespace Avalonia.Collections { _inner.Remove(key); PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"{CommonPropertyNames.IndexerName}[{key}]")); if (CollectionChanged != null) { @@ -208,7 +208,7 @@ namespace Avalonia.Collections private void NotifyAdd(TKey key, TValue value) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Count))); - PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"Item[{key}]")); + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs($"{CommonPropertyNames.IndexerName}[{key}]")); if (CollectionChanged != null) From a8150e24ba76f0600bc11a6d74e3f50ea09eb7be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrei=20=C8=9Aurcan?= <6237485+equalent@users.noreply.github.com> Date: Thu, 3 Nov 2022 01:55:21 +0400 Subject: [PATCH 26/65] Fix support URL in readme --- readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/readme.md b/readme.md index 1009e86c29..c2be487af3 100644 --- a/readme.md +++ b/readme.md @@ -104,7 +104,7 @@ Support this project by becoming a sponsor. Your logo will show up here with a l ## Commercial Support -We have a range of [support plans available](https://avaloniaui.net/support.html) for those looking to partner with the creators of Avalonia, enabling access to the best support at every step of the development process. +We have a range of [support plans available](https://avaloniaui.net/support) for those looking to partner with the creators of Avalonia, enabling access to the best support at every step of the development process. *Please note that donations are not considered payment for commercial support agreements. Please contact us to discuss your needs first. [team@avaloniaui.net](mailto://team@avaloniaui.net)* ## .NET Foundation From 80b0c3d54c8247a6b87170a2b288e86e8cb42838 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 3 Nov 2022 00:24:17 +0100 Subject: [PATCH 27/65] Allow AccessibilityView to override peer. Setting `AutomationProperties.AccessibilityView` can now override the `IsControlElementCore` and `IsContentElementCore` settings returned from the automation peer. --- .../Automation/AutomationProperties.cs | 8 ++++++-- .../Automation/Peers/AutomationPeer.cs | 14 ++++++++++++-- .../Automation/Peers/ControlAutomationPeer.cs | 16 ++++++++++++++-- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/Avalonia.Controls/Automation/AutomationProperties.cs b/src/Avalonia.Controls/Automation/AutomationProperties.cs index c20af148b8..a192ef6420 100644 --- a/src/Avalonia.Controls/Automation/AutomationProperties.cs +++ b/src/Avalonia.Controls/Automation/AutomationProperties.cs @@ -9,6 +9,11 @@ namespace Avalonia.Automation /// public enum AccessibilityView { + /// + /// The control's view is defined by its automation peer. + /// + Default, + /// /// The control is included in the Raw view of the automation tree. /// @@ -44,8 +49,7 @@ namespace Avalonia.Automation public static readonly AttachedProperty AccessibilityViewProperty = AvaloniaProperty.RegisterAttached( "AccessibilityView", - typeof(AutomationProperties), - defaultValue: AccessibilityView.Content); + typeof(AutomationProperties)); /// /// Defines the AutomationProperties.AccessKey attached property diff --git a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs index 71421ac136..3d3fe35d29 100644 --- a/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/AutomationPeer.cs @@ -128,13 +128,13 @@ namespace Avalonia.Automation.Peers /// Gets a value that indicates whether the element that is associated with this automation /// peer contains data that is presented to the user. /// - public bool IsContentElement() => IsControlElement() && IsContentElementCore(); + public bool IsContentElement() => IsContentElementOverrideCore(); /// /// Gets a value that indicates whether the element is understood by the user as /// interactive or as contributing to the logical structure of the control in the GUI. /// - public bool IsControlElement() => IsControlElementCore(); + public bool IsControlElement() => IsControlElementOverrideCore(); /// /// Gets a value indicating whether the control is enabled for user interaction. @@ -247,6 +247,16 @@ namespace Avalonia.Automation.Peers return GetAutomationControlTypeCore(); } + protected virtual bool IsContentElementOverrideCore() + { + return IsControlElement() && IsContentElementCore(); + } + + protected virtual bool IsControlElementOverrideCore() + { + return IsControlElementCore(); + } + protected virtual object? GetProviderCore(Type providerType) { return providerType.IsAssignableFrom(this.GetType()) ? this : null; diff --git a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs index a93d3fa7dd..25172b2d1c 100644 --- a/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs +++ b/src/Avalonia.Controls/Automation/Peers/ControlAutomationPeer.cs @@ -149,8 +149,8 @@ namespace Avalonia.Automation.Peers protected override Rect GetBoundingRectangleCore() => GetBounds(Owner); protected override string GetClassNameCore() => Owner.GetType().Name; protected override bool HasKeyboardFocusCore() => Owner.IsFocused; - protected override bool IsContentElementCore() => AutomationProperties.GetAccessibilityView(Owner) >= AccessibilityView.Content; - protected override bool IsControlElementCore() => AutomationProperties.GetAccessibilityView(Owner) >= AccessibilityView.Control; + protected override bool IsContentElementCore() => true; + protected override bool IsControlElementCore() => true; protected override bool IsEnabledCore() => Owner.IsEnabled; protected override bool IsKeyboardFocusableCore() => Owner.Focusable; protected override void SetFocusCore() => Owner.Focus(); @@ -160,6 +160,18 @@ namespace Avalonia.Automation.Peers return AutomationProperties.GetControlTypeOverride(Owner) ?? GetAutomationControlTypeCore(); } + protected override bool IsContentElementOverrideCore() + { + var view = AutomationProperties.GetAccessibilityView(Owner); + return view == AccessibilityView.Default ? IsContentElementCore() : view >= AccessibilityView.Content; + } + + protected override bool IsControlElementOverrideCore() + { + var view = AutomationProperties.GetAccessibilityView(Owner); + return view == AccessibilityView.Default ? IsControlElementCore() : view >= AccessibilityView.Control; + } + private static Rect GetBounds(Control control) { var root = control.GetVisualRoot(); From 4670536b82044e9cc21d2d3f03c98e1781453efb Mon Sep 17 00:00:00 2001 From: Max Katz Date: Thu, 3 Nov 2022 01:52:47 -0400 Subject: [PATCH 28/65] [ARM only] Fix incorrect parameters passed to the CreateDrawingSurface method (#9342) * Use CreateDrawingSurface2 method with a correct SIZE struct * Fix old method definition just for case * Fix ICompositionDrawingSurface.GetSize method too --- .../Avalonia.Win32/Interop/UnmanagedMethods.cs | 6 ++++++ .../WinRT/Composition/WinUICompositorConnection.cs | 3 ++- src/Windows/Avalonia.Win32/WinRT/winrt.idl | 11 +++++++++-- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs index f1ed53ea99..4afb1068df 100644 --- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs +++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs @@ -2104,6 +2104,12 @@ namespace Avalonia.Win32.Interop public int Y; } + public struct SIZE_F + { + public float X; + public float Y; + } + public struct RECT { public int left; diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs index 8a41c00add..6da17b8ea5 100644 --- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs +++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs @@ -174,8 +174,9 @@ namespace Avalonia.Win32.WinRT.Composition using var sc = _syncContext.EnsureLocked(); using var desktopTarget = _compositorDesktopInterop.CreateDesktopWindowTarget(hWnd, 0); using var target = desktopTarget.QueryInterface(); + using var device2 = _device.QueryInterface(); - using var drawingSurface = _device.CreateDrawingSurface(new UnmanagedMethods.SIZE(), DirectXPixelFormat.B8G8R8A8UIntNormalized, + using var drawingSurface = device2.CreateDrawingSurface2(new UnmanagedMethods.SIZE(), DirectXPixelFormat.B8G8R8A8UIntNormalized, DirectXAlphaMode.Premultiplied); using var surface = drawingSurface.QueryInterface(); using var surfaceInterop = drawingSurface.QueryInterface(); diff --git a/src/Windows/Avalonia.Win32/WinRT/winrt.idl b/src/Windows/Avalonia.Win32/WinRT/winrt.idl index 851df9dae6..ffb98fafc8 100644 --- a/src/Windows/Avalonia.Win32/WinRT/winrt.idl +++ b/src/Windows/Avalonia.Win32/WinRT/winrt.idl @@ -8,6 +8,7 @@ @clr-map Matrix4x4 System.Numerics.Matrix4x4 @clr-map RECT Avalonia.Win32.Interop.UnmanagedMethods.RECT @clr-map SIZE Avalonia.Win32.Interop.UnmanagedMethods.SIZE +@clr-map SIZE_F Avalonia.Win32.Interop.UnmanagedMethods.SIZE_F @clr-map POINT Avalonia.Win32.Interop.UnmanagedMethods.POINT @clr-map HWND IntPtr @clr-map BOOL int @@ -442,12 +443,18 @@ interface IDesktopWindowContentBridgeInterop : IUnknown [uuid(FB22C6E1-80A2-4667-9936-DBEAF6EEFE95)] interface ICompositionGraphicsDevice : IInspectable { - HRESULT CreateDrawingSurface([in] SIZE sizePixels, [in] DirectXPixelFormat pixelFormat, + HRESULT CreateDrawingSurface([in] SIZE_F sizePixels, [in] DirectXPixelFormat pixelFormat, [in] DirectXAlphaMode alphaMode, [out] [retval] ICompositionDrawingSurface** result); HRESULT AddRenderingDeviceReplaced(void* handler, void* token); HRESULT RemoveRenderingDeviceReplaced([in] int token); } +[uuid(0FB8BDF6-C0F0-4BCC-9FB8-084982490D7D)] +interface ICompositionGraphicsDevice2 : IInspectable +{ + HRESULT CreateDrawingSurface2([in] SIZE sizePixels, [in] DirectXPixelFormat pixelFormat, [in] DirectXAlphaMode alphaMode, [out] [retval] ICompositionDrawingSurface** result); +} + [uuid(1527540D-42C7-47A6-A408-668F79A90DFB)] interface ICompositionSurface : IInspectable { @@ -465,7 +472,7 @@ interface ICompositionDrawingSurface : IInspectable { [propget] HRESULT GetAlphaMode([out] [retval] DirectXAlphaMode* value); [propget] HRESULT GetPixelFormat([out] [retval] DirectXPixelFormat* value); - [propget] HRESULT GetSize([out] [retval] POINT* value); + [propget] HRESULT GetSize([out] [retval] SIZE_F* value); } enum CompositionBitmapInterpolationMode From 2f5c5494b41e858c814182af311db891d75eac0c Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 3 Nov 2022 09:37:07 +0100 Subject: [PATCH 29/65] Add failing test --- .../TextBlockTests.cs | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs index be8062322e..84ae0b6453 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs @@ -3,6 +3,7 @@ using Avalonia.Controls.Documents; using Avalonia.Controls.Templates; using Avalonia.Data; using Avalonia.Media; +using Avalonia.Metadata; using Avalonia.Rendering; using Avalonia.UnitTests; using Moq; @@ -165,9 +166,9 @@ namespace Avalonia.Controls.UnitTests }.RegisterInNameScope(scope) ); - target.Inlines!.Add("123456"); + target.Inlines!.AddText("123456"); target.Inlines.Add(new InlineUIContainer(button)); - target.Inlines.Add("123456"); + target.Inlines.AddText("123456"); target.Measure(Size.Infinity); @@ -181,5 +182,26 @@ namespace Avalonia.Controls.UnitTests Assert.Equal(60, button.Bounds.Left); } } + + [Fact] + public void Setting_Text_Should_Reset_Inlines() + { + using (UnitTestApplication.Start(TestServices.StyledWindow)) + { + var target = new TextBlock(); + + target.Inlines.Add(new Run("Hello World")); + + Assert.Equal("Hello World", target.Text); + + Assert.Equal(1, target.Inlines.Count); + + target.Text = "1234"; + + Assert.Equal("1234", target.Text); + + Assert.Equal(0, target.Inlines.Count); + } + } } } From b370a0b6494a0b8d4d15152590a9e9e73482ab16 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 3 Nov 2022 09:37:42 +0100 Subject: [PATCH 30/65] Fix TextBlock TextProperty update handling for non complex content --- .../Documents/InlineCollection.cs | 52 ++++++++----------- src/Avalonia.Controls/TextBlock.cs | 19 ++----- 2 files changed, 27 insertions(+), 44 deletions(-) diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index a265f88e21..0329429051 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -89,55 +89,49 @@ namespace Avalonia.Controls.Documents } + public override void Add(Inline inline) + { + if (InlineHost is TextBlock textBlock && !string.IsNullOrEmpty(textBlock._text)) + { + base.Add(new Run(textBlock._text)); + + textBlock._text = null; + } + + base.Add(inline); + } + /// - /// Add a text segment to the collection. + /// Adds a text segment to the collection. /// /// For non complex content this appends the text to the end of currently held text. /// For complex content this adds a to the collection. /// /// - /// + /// The to be added text. public void Add(string text) { AddText(text); } - public override void Add(Inline inline) - { - OnAdd(); - - base.Add(inline); - } - - public void Add(IControl child) + /// + /// Adds a control wrapped inside a to the collection. + /// + /// The to be added control. + public void Add(IControl control) { - OnAdd(); - - base.Add(new InlineUIContainer(child)); + Add(new InlineUIContainer(control)); } - private void AddText(string text) + internal void AddText(string text) { - if (LogicalChildren is TextBlock textBlock && !textBlock.HasComplexContent) + if (InlineHost is TextBlock textBlock && !textBlock.HasComplexContent) { textBlock._text += text; } else { - base.Add(new Run(text)); - } - } - - private void OnAdd() - { - if (LogicalChildren is TextBlock textBlock) - { - if (!textBlock.HasComplexContent && !string.IsNullOrEmpty(textBlock._text)) - { - base.Add(new Run(textBlock._text)); - - textBlock._text = null; - } + Add(new Run(text)); } } diff --git a/src/Avalonia.Controls/TextBlock.cs b/src/Avalonia.Controls/TextBlock.cs index 0492c2c1e3..c8e05e5cb3 100644 --- a/src/Avalonia.Controls/TextBlock.cs +++ b/src/Avalonia.Controls/TextBlock.cs @@ -597,23 +597,12 @@ namespace Avalonia.Controls protected virtual void SetText(string? text) { - if (Inlines != null && Inlines.Count > 0) - { - var oldValue = Inlines.Text; - - if (!string.IsNullOrEmpty(text)) - { - Inlines.Add(text); - } - - text = Inlines.Text; - - RaisePropertyChanged(TextProperty, oldValue, text); - } - else + if (HasComplexContent) { - SetAndRaise(TextProperty, ref _text, text); + Inlines?.Clear(); } + + SetAndRaise(TextProperty, ref _text, text); } /// From 0d889da820ed1acf49b265a223cdfdb9245fe923 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Thu, 3 Nov 2022 09:42:10 +0100 Subject: [PATCH 31/65] Remove redundant member --- .../Documents/InlineCollection.cs | 21 +++++++------------ .../TextBlockTests.cs | 4 ++-- 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/Avalonia.Controls/Documents/InlineCollection.cs b/src/Avalonia.Controls/Documents/InlineCollection.cs index 0329429051..5b68402f87 100644 --- a/src/Avalonia.Controls/Documents/InlineCollection.cs +++ b/src/Avalonia.Controls/Documents/InlineCollection.cs @@ -111,7 +111,14 @@ namespace Avalonia.Controls.Documents /// The to be added text. public void Add(string text) { - AddText(text); + if (InlineHost is TextBlock textBlock && !textBlock.HasComplexContent) + { + textBlock._text += text; + } + else + { + Add(new Run(text)); + } } /// @@ -123,18 +130,6 @@ namespace Avalonia.Controls.Documents Add(new InlineUIContainer(control)); } - internal void AddText(string text) - { - if (InlineHost is TextBlock textBlock && !textBlock.HasComplexContent) - { - textBlock._text += text; - } - else - { - Add(new Run(text)); - } - } - /// /// Raised when an inline in the collection changes. /// diff --git a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs index 84ae0b6453..de5e5a8ea3 100644 --- a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs +++ b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs @@ -166,9 +166,9 @@ namespace Avalonia.Controls.UnitTests }.RegisterInNameScope(scope) ); - target.Inlines!.AddText("123456"); + target.Inlines!.Add("123456"); target.Inlines.Add(new InlineUIContainer(button)); - target.Inlines.AddText("123456"); + target.Inlines.Add("123456"); target.Measure(Size.Infinity); From 9a50b9ee0ccab7d96bac6e144c55aa0422303f02 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Thu, 3 Nov 2022 15:36:02 +0100 Subject: [PATCH 32/65] Added integration tests for tapped gestures. Some skipped on Windows as right button actions aren't supported by WinAppDriver. --- samples/IntegrationTestApp/MainWindow.axaml | 12 ++ .../IntegrationTestApp/MainWindow.axaml.cs | 10 ++ .../GestureTests.cs | 151 ++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 tests/Avalonia.IntegrationTests.Appium/GestureTests.cs diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml index 3377979199..2a0a758852 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml +++ b/samples/IntegrationTestApp/MainWindow.axaml @@ -69,6 +69,18 @@ + + + + + + + + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index f72f83fcb8..791e221d50 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -4,6 +4,7 @@ using Avalonia; using Avalonia.Automation; using Avalonia.Controls; using Avalonia.Controls.ApplicationLifetimes; +using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Markup.Xaml; using Avalonia.VisualTree; @@ -20,6 +21,15 @@ namespace IntegrationTestApp this.AttachDevTools(); AddHandler(Button.ClickEvent, OnButtonClick); ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList(); + + var gestureBorder = this.GetControl("GestureBorder"); + var lastGesture = this.GetControl("LastGesture"); + var clearLastGesture = this.GetControl + - + + + + diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs index 791e221d50..e8fb455c35 100644 --- a/samples/IntegrationTestApp/MainWindow.axaml.cs +++ b/samples/IntegrationTestApp/MainWindow.axaml.cs @@ -18,18 +18,10 @@ namespace IntegrationTestApp { InitializeComponent(); InitializeViewMenu(); + InitializeGesturesTab(); this.AttachDevTools(); AddHandler(Button.ClickEvent, OnButtonClick); ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList(); - - var gestureBorder = this.GetControl("GestureBorder"); - var lastGesture = this.GetControl("LastGesture"); - var clearLastGesture = this.GetControl void SetPreeditText(string? text); + + void SetComposingRegion(ComposingRegion? region); /// /// Indicates if text input client is capable of providing the text around the cursor /// @@ -51,4 +53,26 @@ namespace Avalonia.Input.TextInput public int CursorOffset { get; set; } public int AnchorOffset { get; set; } } + + public readonly struct ComposingRegion + { + private readonly int _start = -1; + private readonly int _end = -1; + + public ComposingRegion(int start, int end) + { + _start = start; + _end = end; + } + + public int Start => _start; + public int End => _end; + + public bool Intersects(ComposingRegion region) + { + return _start <= region.Start && _end >= region.Start || + _end >= region.End && _start <= region.End || + _start >= region.Start && _end <= region.End; + } + } } diff --git a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs index 7ab67ea34d..fb8e699d8e 100644 --- a/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs +++ b/src/Avalonia.Base/Media/TextFormatting/FormattedTextSource.cs @@ -140,7 +140,7 @@ namespace Avalonia.Media.TextFormatting } } - return length; + return Math.Min(length, text.Length); } } } diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index bcfa35ae30..efe6a94856 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -74,15 +74,6 @@ namespace Avalonia.Media.TextFormatting if (TryGetShapeableLength(text, currentTypeface, null, out var count, out var script)) { - if (script == Script.Common && previousTypeface is not null) - { - if (TryGetShapeableLength(text, previousTypeface.Value, null, out var fallbackCount, out _)) - { - return new ShapeableTextCharacters(text.Take(fallbackCount), - defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel); - } - } - return new ShapeableTextCharacters(text.Take(count), defaultProperties.WithTypeface(currentTypeface), biDiLevel); } @@ -182,7 +173,7 @@ namespace Avalonia.Media.TextFormatting var currentScript = currentGrapheme.FirstCodepoint.Script; - if (!currentGrapheme.FirstCodepoint.IsWhiteSpace && defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) + if (defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) { break; } diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index adf0569551..230c57d34b 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -9,6 +9,7 @@ using Avalonia.VisualTree; using Avalonia.Layout; using Avalonia.Media.Immutable; using Avalonia.Controls.Documents; +using Avalonia.Input.TextInput; namespace Avalonia.Controls.Presenters { @@ -331,6 +332,8 @@ namespace Avalonia.Controls.Presenters protected override bool BypassFlowDirectionPolicies => true; + public ComposingRegion? ComposingRegion { get; internal set; } + /// /// Creates the used to render the text. /// @@ -514,7 +517,12 @@ namespace Avalonia.Controls.Presenters { if (!string.IsNullOrEmpty(_preeditText)) { - var text = _text?.Substring(0, _caretIndex) + _preeditText + _text?.Substring(_caretIndex); + if (string.IsNullOrEmpty(_text) || _caretIndex > _text.Length) + { + return _preeditText; + } + + var text = _text.Substring(0, _caretIndex) + _preeditText + _text.Substring(_caretIndex); return text; } @@ -545,7 +553,7 @@ namespace Avalonia.Controls.Presenters if (!string.IsNullOrEmpty(_preeditText)) { - var preeditHighlight = new ValueSpan(_caretIndex, _preeditText.Length, + var preeditHighlight = new ValueSpan(ComposingRegion.HasValue ? ComposingRegion.Value.Start : _caretIndex, _preeditText.Length, new GenericTextRunProperties(typeface, FontSize, foregroundBrush: foreground, textDecorations: TextDecorations.Underline)); @@ -868,28 +876,11 @@ namespace Avalonia.Controls.Presenters if (string.IsNullOrEmpty(newValue)) { - if (!string.IsNullOrEmpty(oldValue)) - { - var textPosition = _compositionStartHit.FirstCharacterIndex + _compositionStartHit.TrailingLength + newValue?.Length ?? 0; - - var characterHit = GetCharacterHitFromTextPosition(textPosition); - - UpdateCaret(characterHit, true); - } - - _compositionStartHit = new CharacterHit(-1); + UpdateCaret(_lastCharacterHit); } else { - if (_compositionStartHit.FirstCharacterIndex == -1) - { - _compositionStartHit = _lastCharacterHit; - } - } - - if (_compositionStartHit.FirstCharacterIndex != -1) - { - var textPosition = _compositionStartHit.FirstCharacterIndex + _compositionStartHit.TrailingLength + newValue?.Length ?? 0; + var textPosition = (ComposingRegion.HasValue? ComposingRegion.Value.Start : _caretIndex) + newValue?.Length ?? 0; var characterHit = GetCharacterHitFromTextPosition(textPosition); diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 85c1c9a9d1..d5b45398e7 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1212,7 +1212,7 @@ namespace Avalonia.Controls protected override void OnPointerPressed(PointerPressedEventArgs e) { - if (_presenter == null || !string.IsNullOrEmpty(_presenter.PreeditText)) + if (_presenter == null ) { return; } diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs index 5d5ffcc381..61ac1d3c1f 100644 --- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs +++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs @@ -108,6 +108,11 @@ namespace Avalonia.Controls } _presenter.PreeditText = text; + + if(text == null) + { + _presenter.ComposingRegion = null; + } } public void SelectInSurroundingText(int start, int end) @@ -182,5 +187,15 @@ namespace Avalonia.Controls }, DispatcherPriority.Input); } + + public void SetComposingRegion(ComposingRegion? region) + { + if(_presenter == null) + { + return; + } + + _presenter.ComposingRegion = region; + } } } From a589d2e43b762a1d51c8be4142c85a57bb3e5fc2 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 7 Nov 2022 18:17:09 +0100 Subject: [PATCH 46/65] Relocate CompositionRegion --- .../Avalonia.Android/AndroidInputMethod.cs | 15 ++++++++++++ .../Platform/SkiaPlatform/TopLevelImpl.cs | 2 -- .../Input/TextInput/ITextInputMethodClient.cs | 23 ------------------- .../TextBoxTextInputMethodClient.cs | 10 -------- 4 files changed, 15 insertions(+), 35 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidInputMethod.cs b/src/Android/Avalonia.Android/AndroidInputMethod.cs index 22d2f812bc..b6adbde738 100644 --- a/src/Android/Avalonia.Android/AndroidInputMethod.cs +++ b/src/Android/Avalonia.Android/AndroidInputMethod.cs @@ -162,4 +162,19 @@ namespace Avalonia.Android }); } } + + public readonly struct ComposingRegion + { + private readonly int _start = -1; + private readonly int _end = -1; + + public ComposingRegion(int start, int end) + { + _start = start; + _end = end; + } + + public int Start => _start; + public int End => _end; + } } diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index e69780b1b3..932dc185ff 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -287,8 +287,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform ComposingRegion = new ComposingRegion(start, end); - _inputMethod.Client?.SetComposingRegion(ComposingRegion); - return base.SetComposingRegion(start, end); } diff --git a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs index 0ff9299873..325d745782 100644 --- a/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs +++ b/src/Avalonia.Base/Input/TextInput/ITextInputMethodClient.cs @@ -30,7 +30,6 @@ namespace Avalonia.Input.TextInput /// void SetPreeditText(string? text); - void SetComposingRegion(ComposingRegion? region); /// /// Indicates if text input client is capable of providing the text around the cursor /// @@ -53,26 +52,4 @@ namespace Avalonia.Input.TextInput public int CursorOffset { get; set; } public int AnchorOffset { get; set; } } - - public readonly struct ComposingRegion - { - private readonly int _start = -1; - private readonly int _end = -1; - - public ComposingRegion(int start, int end) - { - _start = start; - _end = end; - } - - public int Start => _start; - public int End => _end; - - public bool Intersects(ComposingRegion region) - { - return _start <= region.Start && _end >= region.Start || - _end >= region.End && _start <= region.End || - _start >= region.Start && _end <= region.End; - } - } } diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs index 61ac1d3c1f..79a16281cd 100644 --- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs +++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs @@ -187,15 +187,5 @@ namespace Avalonia.Controls }, DispatcherPriority.Input); } - - public void SetComposingRegion(ComposingRegion? region) - { - if(_presenter == null) - { - return; - } - - _presenter.ComposingRegion = region; - } } } From 84c04bcfeb3f25805b656fe1e70eae90ad3e0339 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Tue, 8 Nov 2022 00:08:56 +0600 Subject: [PATCH 47/65] [OSX] Fixed multi-window freeze on Show when another window is rendering --- native/Avalonia.Native/src/OSX/rendertarget.mm | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm index 2075cc85ab..1c22c91207 100644 --- a/native/Avalonia.Native/src/OSX/rendertarget.mm +++ b/native/Avalonia.Native/src/OSX/rendertarget.mm @@ -183,8 +183,11 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta [_layer setContents: (__bridge IOSurface*) surface->surface]; } [CATransaction commit]; - [CATransaction flush]; } + // This can trigger event processing on the main thread + // which might need to lock the renderer + // which can cause a deadlock. So flush call is outside of the lock + [CATransaction flush]; } else dispatch_async(dispatch_get_main_queue(), ^{ From 7e3024ede20490eadb0511a50a7059f8bd436ab2 Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Mon, 7 Nov 2022 19:46:02 +0100 Subject: [PATCH 48/65] Fix fallback handling --- .../Media/TextFormatting/TextCharacters.cs | 11 ++++++++++- src/Avalonia.Controls/Presenters/TextPresenter.cs | 6 ++---- src/Avalonia.Controls/TextBoxTextInputMethodClient.cs | 5 ----- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs index efe6a94856..bcfa35ae30 100644 --- a/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs +++ b/src/Avalonia.Base/Media/TextFormatting/TextCharacters.cs @@ -74,6 +74,15 @@ namespace Avalonia.Media.TextFormatting if (TryGetShapeableLength(text, currentTypeface, null, out var count, out var script)) { + if (script == Script.Common && previousTypeface is not null) + { + if (TryGetShapeableLength(text, previousTypeface.Value, null, out var fallbackCount, out _)) + { + return new ShapeableTextCharacters(text.Take(fallbackCount), + defaultProperties.WithTypeface(previousTypeface.Value), biDiLevel); + } + } + return new ShapeableTextCharacters(text.Take(count), defaultProperties.WithTypeface(currentTypeface), biDiLevel); } @@ -173,7 +182,7 @@ namespace Avalonia.Media.TextFormatting var currentScript = currentGrapheme.FirstCodepoint.Script; - if (defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) + if (!currentGrapheme.FirstCodepoint.IsWhiteSpace && defaultFont != null && defaultFont.TryGetGlyph(currentGrapheme.FirstCodepoint, out _)) { break; } diff --git a/src/Avalonia.Controls/Presenters/TextPresenter.cs b/src/Avalonia.Controls/Presenters/TextPresenter.cs index 230c57d34b..cc1fa6c513 100644 --- a/src/Avalonia.Controls/Presenters/TextPresenter.cs +++ b/src/Avalonia.Controls/Presenters/TextPresenter.cs @@ -332,8 +332,6 @@ namespace Avalonia.Controls.Presenters protected override bool BypassFlowDirectionPolicies => true; - public ComposingRegion? ComposingRegion { get; internal set; } - /// /// Creates the used to render the text. /// @@ -553,7 +551,7 @@ namespace Avalonia.Controls.Presenters if (!string.IsNullOrEmpty(_preeditText)) { - var preeditHighlight = new ValueSpan(ComposingRegion.HasValue ? ComposingRegion.Value.Start : _caretIndex, _preeditText.Length, + var preeditHighlight = new ValueSpan(_caretIndex, _preeditText.Length, new GenericTextRunProperties(typeface, FontSize, foregroundBrush: foreground, textDecorations: TextDecorations.Underline)); @@ -880,7 +878,7 @@ namespace Avalonia.Controls.Presenters } else { - var textPosition = (ComposingRegion.HasValue? ComposingRegion.Value.Start : _caretIndex) + newValue?.Length ?? 0; + var textPosition = _caretIndex + newValue?.Length ?? 0; var characterHit = GetCharacterHitFromTextPosition(textPosition); diff --git a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs index 79a16281cd..5d5ffcc381 100644 --- a/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs +++ b/src/Avalonia.Controls/TextBoxTextInputMethodClient.cs @@ -108,11 +108,6 @@ namespace Avalonia.Controls } _presenter.PreeditText = text; - - if(text == null) - { - _presenter.ComposingRegion = null; - } } public void SelectInSurroundingText(int start, int end) From 562d687daab221ef8666e0c9bab9127006d1eebd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 7 Nov 2022 22:58:47 +0300 Subject: [PATCH 49/65] Use 7.0.100 SDK --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index dc6da556b3..a9318b212f 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "7.0.100-rc.2.22477.23", + "version": "7.0.100", "rollForward": "latestFeature" }, "msbuild-sdks": { From b5cf18bd4d148809918ff68cb263fa87c6d603dd Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 7 Nov 2022 23:01:29 +0300 Subject: [PATCH 50/65] Install 7.0.100 --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 903f9e3843..ee78a79e89 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -37,7 +37,7 @@ jobs: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' inputs: - version: 7.0.100-rc.2.22477.23 + version: 7.0.100 - task: CmdLine@2 displayName: 'Install Workloads' @@ -74,7 +74,7 @@ jobs: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' inputs: - version: 7.0.100-rc.2.22477.23 + version: 7.0.100 - task: CmdLine@2 displayName: 'Install Workloads' @@ -145,7 +145,7 @@ jobs: - task: UseDotNet@2 displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' inputs: - version: 7.0.100-rc.2.22477.23 + version: 7.0.100 - task: CmdLine@2 displayName: 'Install Workloads' From 6c32c97ec5f5871507691f1439fbcb8483cdb9f2 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Mon, 7 Nov 2022 23:04:35 +0300 Subject: [PATCH 51/65] Update azure-pipelines.yml --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ee78a79e89..a3bbc33418 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -35,7 +35,7 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' + displayName: 'Use .NET Core SDK 7.0' inputs: version: 7.0.100 @@ -72,7 +72,7 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' + displayName: 'Use .NET Core SDK 7.0.100' inputs: version: 7.0.100 @@ -143,7 +143,7 @@ jobs: version: 6.0.401 - task: UseDotNet@2 - displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23' + displayName: 'Use .NET Core SDK 7.0.100' inputs: version: 7.0.100 From ff620db5bbd072094e56aa9e9f143b95f3ea9a8e Mon Sep 17 00:00:00 2001 From: Tristan Labelle Date: Mon, 7 Nov 2022 20:36:27 -0500 Subject: [PATCH 52/65] Android: Force punch a hole in the SurfaceView to workaround issue #9230 --- .../Platform/SkiaPlatform/TopLevelImpl.cs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 984eb775b5..4e972d504a 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -165,6 +165,28 @@ namespace Avalonia.Android.Platform.SkiaPlatform _tl.Draw(); } + protected override void DispatchDraw(global::Android.Graphics.Canvas canvas) + { + // Workaround issue #9230 on where screen remains gray after splash screen. + // base.DispatchDraw should punch a hole into the canvas so the surface + // can be seen below, but it does not. + if (OperatingSystem.IsAndroidVersionAtLeast(29)) + { + // Android 10+ does this (BlendMode was new) + var paint = new Paint(); + paint.SetColor(0); + paint.BlendMode = BlendMode.Clear; + canvas.DrawRect(0, 0, Width, Height, paint); + } + else + { + // Android 9 did this + canvas.DrawColor(Color.Transparent, PorterDuff.Mode.Clear); + } + + base.DispatchDraw(canvas); + } + protected override bool DispatchGenericPointerEvent(MotionEvent e) { bool callBase; From f6f89f4e33b86f8a9ae0f6ccb5ade9ac8d69e20e Mon Sep 17 00:00:00 2001 From: Benedikt Stebner Date: Tue, 8 Nov 2022 06:59:16 +0100 Subject: [PATCH 53/65] Remove redundant method call --- .../Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs index 932dc185ff..24ee418480 100644 --- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs +++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs @@ -268,8 +268,6 @@ namespace Avalonia.Android.Platform.SkiaPlatform { _topLevel = topLevel; _inputMethod = inputMethod; - - _inputMethod.Client?.SetComposingRegion(null); } public TextInputMethodSurroundingText SurroundingText { get; set; } From a52696bcd6efbdc74e294ab41a267a4092bd9e53 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 8 Nov 2022 13:41:08 -0500 Subject: [PATCH 54/65] Update Microsoft.Build.Framework --- nukebuild/_build.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 865d935ad7..efad0d69f4 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -17,7 +17,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 9003dfde5164f5ba7ca47b87fbc6827e8c7bb554 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 8 Nov 2022 13:52:43 -0500 Subject: [PATCH 55/65] Parse target framework manually --- nukebuild/Build.cs | 19 ++++++++++++++++++- nukebuild/_build.csproj | 3 +-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs index 7425c344c3..2295c0beda 100644 --- a/nukebuild/Build.cs +++ b/nukebuild/Build.cs @@ -145,8 +145,25 @@ partial class Build : NukeBuild { Information($"Running tests from {projectName}"); var project = Solution.GetProject(projectName).NotNull("project != null"); + // Nuke and MSBuild tools have build-in helpers to get target frameworks from the project. + // Unfortunately, it gets broken with every second SDK update, so we had to do it manually. + var fileXml = XDocument.Parse(File.ReadAllText(project.Path)); + var targetFrameworks = fileXml.Descendants("TargetFrameworks") + .FirstOrDefault()?.Value.Split(';').Select(f => f.Trim()); + if (targetFrameworks is null) + { + var targetFramework = fileXml.Descendants("TargetFramework").FirstOrDefault()?.Value; + if (targetFramework is not null) + { + targetFrameworks = new[] { targetFramework }; + } + } + if (targetFrameworks is null) + { + throw new InvalidOperationException("No target frameworks were found in the test project"); + } - foreach (var fw in project.GetTargetFrameworks()) + foreach (var fw in targetFrameworks) { if (fw.StartsWith("net4") && RuntimeInformation.IsOSPlatform(OSPlatform.Linux) diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index efad0d69f4..591c72445b 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -16,8 +16,7 @@ - - + all runtime; build; native; contentfiles; analyzers; buildtransitive From 6b080eabe02588d3cd28e7c7b389ba7c40b70197 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Tue, 8 Nov 2022 14:28:16 -0500 Subject: [PATCH 56/65] Restore Microsoft.Build.Framework dependency --- nukebuild/_build.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index 591c72445b..92d9732e91 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -17,6 +17,7 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive From 4808846d82c0cae6ae572495e4e6b725f99cd48a Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Tue, 8 Nov 2022 22:37:57 +0000 Subject: [PATCH 57/65] Update Avalonia.Web.targets --- src/Web/Avalonia.Web/Avalonia.Web.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Web/Avalonia.Web/Avalonia.Web.targets b/src/Web/Avalonia.Web/Avalonia.Web.targets index b6a09b33ef..b5db8e5dd3 100644 --- a/src/Web/Avalonia.Web/Avalonia.Web.targets +++ b/src/Web/Avalonia.Web/Avalonia.Web.targets @@ -12,7 +12,7 @@ true - + true full true From ace159369ef6313d4b65e0284bf30c9f099a7d30 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 9 Nov 2022 09:56:47 +0100 Subject: [PATCH 58/65] Update ncrunch config due to recent changes. --- .ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject | 5 +++++ .ncrunch/Avalonia.Web.v3.ncrunchproject | 5 +++++ .ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject | 6 +----- .ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject | 6 +----- .ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject | 5 +++++ .ncrunch/MobileSandbox.Android.v3.ncrunchproject | 5 +++++ .ncrunch/MobileSandbox.Desktop.v3.ncrunchproject | 5 +++++ .ncrunch/MobileSandbox.iOS.v3.ncrunchproject | 5 +++++ .ncrunch/MobileSandbox.net6.0.v3.ncrunchproject | 5 +++++ .ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject | 5 +++++ .ncrunch/_build.v3.ncrunchproject | 5 +++++ 11 files changed, 47 insertions(+), 10 deletions(-) create mode 100644 .ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject create mode 100644 .ncrunch/Avalonia.Web.v3.ncrunchproject create mode 100644 .ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject create mode 100644 .ncrunch/MobileSandbox.Android.v3.ncrunchproject create mode 100644 .ncrunch/MobileSandbox.Desktop.v3.ncrunchproject create mode 100644 .ncrunch/MobileSandbox.iOS.v3.ncrunchproject create mode 100644 .ncrunch/MobileSandbox.net6.0.v3.ncrunchproject create mode 100644 .ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject create mode 100644 .ncrunch/_build.v3.ncrunchproject diff --git a/.ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject b/.ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Web.v3.ncrunchproject b/.ncrunch/Avalonia.Web.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/Avalonia.Web.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject index 28b692bb51..95a483b433 100644 --- a/.ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject @@ -1,7 +1,3 @@  - - - ..\..\tools\MicroComGenerator\bin\Debug\net6.0\**.* - - + \ No newline at end of file diff --git a/.ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject index 28b692bb51..95a483b433 100644 --- a/.ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject +++ b/.ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject @@ -1,7 +1,3 @@  - - - ..\..\tools\MicroComGenerator\bin\Debug\net6.0\**.* - - + \ No newline at end of file diff --git a/.ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject b/.ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/MobileSandbox.Android.v3.ncrunchproject b/.ncrunch/MobileSandbox.Android.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/MobileSandbox.Android.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/MobileSandbox.Desktop.v3.ncrunchproject b/.ncrunch/MobileSandbox.Desktop.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/MobileSandbox.Desktop.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/MobileSandbox.iOS.v3.ncrunchproject b/.ncrunch/MobileSandbox.iOS.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/MobileSandbox.iOS.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/MobileSandbox.net6.0.v3.ncrunchproject b/.ncrunch/MobileSandbox.net6.0.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/MobileSandbox.net6.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject b/.ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file diff --git a/.ncrunch/_build.v3.ncrunchproject b/.ncrunch/_build.v3.ncrunchproject new file mode 100644 index 0000000000..319cd523ce --- /dev/null +++ b/.ncrunch/_build.v3.ncrunchproject @@ -0,0 +1,5 @@ + + + True + + \ No newline at end of file From cbf072efb6454b0725d6cdcd1dcb936833ee7497 Mon Sep 17 00:00:00 2001 From: Dan Walmsley Date: Wed, 9 Nov 2022 12:50:10 +0000 Subject: [PATCH 59/65] fix condition in targets file. --- src/Web/Avalonia.Web/Avalonia.Web.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Web/Avalonia.Web/Avalonia.Web.targets b/src/Web/Avalonia.Web/Avalonia.Web.targets index b5db8e5dd3..22363b33d8 100644 --- a/src/Web/Avalonia.Web/Avalonia.Web.targets +++ b/src/Web/Avalonia.Web/Avalonia.Web.targets @@ -12,7 +12,7 @@ true - + true full true From a62c56186fef420cb6326c862a218ac172aaaf30 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 9 Nov 2022 15:22:30 +0100 Subject: [PATCH 60/65] Use GetRequiredService in touch/mouse device. --- src/Avalonia.Base/Input/MouseDevice.cs | 6 +++--- src/Avalonia.Base/Input/TouchDevice.cs | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/Avalonia.Base/Input/MouseDevice.cs b/src/Avalonia.Base/Input/MouseDevice.cs index 808efb3931..b4bc419223 100644 --- a/src/Avalonia.Base/Input/MouseDevice.cs +++ b/src/Avalonia.Base/Input/MouseDevice.cs @@ -127,9 +127,9 @@ namespace Avalonia.Input _pointer.Capture(source); if (source != null) { - var settings = AvaloniaLocator.Current.GetService(); - var doubleClickTime = settings?.GetDoubleTapTime(PointerType.Mouse).TotalMilliseconds ?? 500; - var doubleClickSize = settings?.GetDoubleTapSize(PointerType.Mouse) ?? new Size(4, 4); + var settings = AvaloniaLocator.Current.GetRequiredService(); + var doubleClickTime = settings.GetDoubleTapTime(PointerType.Mouse).TotalMilliseconds; + var doubleClickSize = settings.GetDoubleTapSize(PointerType.Mouse); if (!_lastClickRect.Contains(p) || timestamp - _lastClickTime > doubleClickTime) { diff --git a/src/Avalonia.Base/Input/TouchDevice.cs b/src/Avalonia.Base/Input/TouchDevice.cs index 05b0a89e01..c98944359d 100644 --- a/src/Avalonia.Base/Input/TouchDevice.cs +++ b/src/Avalonia.Base/Input/TouchDevice.cs @@ -60,8 +60,8 @@ namespace Avalonia.Input else { var settings = AvaloniaLocator.Current.GetRequiredService(); - var doubleClickTime = settings?.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds ?? 500; - var doubleClickSize = settings?.GetDoubleTapSize(PointerType.Touch) ?? new Size(4, 4); + var doubleClickTime = settings.GetDoubleTapTime(PointerType.Touch).TotalMilliseconds; + var doubleClickSize = settings.GetDoubleTapSize(PointerType.Touch); if (!_lastClickRect.Contains(args.Position) || ev.Timestamp - _lastClickTime > doubleClickTime) From 0e951befdf45cb66422fb31a10d8193b57b4fc21 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Wed, 9 Nov 2022 15:27:46 +0100 Subject: [PATCH 61/65] Use DefaultPlatformSettings in Web platform. --- src/Web/Avalonia.Web/WindowingPlatform.cs | 24 ++--------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/src/Web/Avalonia.Web/WindowingPlatform.cs b/src/Web/Avalonia.Web/WindowingPlatform.cs index 29c797660f..c399c22c61 100644 --- a/src/Web/Avalonia.Web/WindowingPlatform.cs +++ b/src/Web/Avalonia.Web/WindowingPlatform.cs @@ -8,7 +8,7 @@ using Avalonia.Threading; namespace Avalonia.Web { - internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface + internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformThreadingInterface { private bool _signaled; private static KeyboardDevice? s_keyboard; @@ -36,7 +36,7 @@ namespace Avalonia.Web .Bind().ToSingleton() .Bind().ToSingleton() .Bind().ToConstant(s_keyboard) - .Bind().ToConstant(instance) + .Bind().ToSingleton() .Bind().ToConstant(instance) .Bind().ToConstant(new RenderLoop()) .Bind().ToConstant(ManualTriggerRenderTimer.Instance) @@ -94,25 +94,5 @@ namespace Avalonia.Web { return AvaloniaLocator.Current.GetRequiredService(); } - - Size IPlatformSettings.GetTapSize(PointerType type) - { - return type switch - { - PointerType.Touch => new(10, 10), - _ => new(4, 4), - }; - } - - Size IPlatformSettings.GetDoubleTapSize(PointerType type) - { - return type switch - { - PointerType.Touch => new(16, 16), - _ => new(4, 4), - }; - } - - TimeSpan IPlatformSettings.GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(500); } } From e1f9809de6338f68aba3ca83cffc3a94ff4d65d0 Mon Sep 17 00:00:00 2001 From: Tim Date: Wed, 9 Nov 2022 15:32:11 +0100 Subject: [PATCH 62/65] Add missing ClipToBounds="True" --- src/Avalonia.Themes.Fluent/Controls/TextBox.xaml | 6 ++++-- src/Avalonia.Themes.Simple/Controls/TextBox.xaml | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml index db487ef76b..224264b51d 100644 --- a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml +++ b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml @@ -235,7 +235,8 @@ From 1fc811f82c0d31ae956effe9e4017307d6120b14 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 9 Nov 2022 18:55:14 -0500 Subject: [PATCH 63/65] Fix touch tests --- tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs index 1070138221..c0c0182622 100644 --- a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs +++ b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs @@ -210,6 +210,8 @@ namespace Avalonia.Input.UnitTests new TestServices(inputManager: new InputManager())); var iSettingsMock = new Mock(); iSettingsMock.Setup(x => x.GetDoubleTapTime(It.IsAny())).Returns(doubleClickTime); + iSettingsMock.Setup(x => x.GetDoubleTapSize(It.IsAny())).Returns(new Size(16, 16)); + iSettingsMock.Setup(x => x.GetTapSize(It.IsAny())).Returns(new Size(16, 16)); AvaloniaLocator.CurrentMutable.BindToSelf(this) .Bind().ToConstant(iSettingsMock.Object); return unitTestApp; From 0b3f2adce91af8e732b7dd8fc8af19afb70d76a7 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 9 Nov 2022 18:55:27 -0500 Subject: [PATCH 64/65] Use DefaultPlatformSettings for android too, fix build error --- src/Android/Avalonia.Android/AndroidPlatform.cs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index ed5b46d398..0d77a4c905 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -31,16 +31,6 @@ namespace Avalonia.Android public static readonly AndroidPlatform Instance = new AndroidPlatform(); public static AndroidPlatformOptions Options { get; private set; } - /// - public Size TouchDoubleClickSize => new Size(4, 4); - - /// - public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(200); - - public Size DoubleClickSize => TouchDoubleClickSize; - - public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); - internal static Compositor Compositor { get; private set; } public static void Initialize() @@ -52,7 +42,7 @@ namespace Avalonia.Android .Bind().ToTransient() .Bind().ToConstant(new WindowingPlatformStub()) .Bind().ToSingleton() - .Bind().ToConstant(Instance) + .Bind().ToSingleton() .Bind().ToConstant(new AndroidThreadingInterface()) .Bind().ToSingleton() .Bind().ToConstant(new ChoreographerTimer()) From bb0c4d399b11fc69cb9dea4b78532e73ea398bd4 Mon Sep 17 00:00:00 2001 From: Max Katz Date: Wed, 9 Nov 2022 19:02:00 -0500 Subject: [PATCH 65/65] Remove missed interface --- src/Android/Avalonia.Android/AndroidPlatform.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Android/Avalonia.Android/AndroidPlatform.cs b/src/Android/Avalonia.Android/AndroidPlatform.cs index 0d77a4c905..2b6d29e7c5 100644 --- a/src/Android/Avalonia.Android/AndroidPlatform.cs +++ b/src/Android/Avalonia.Android/AndroidPlatform.cs @@ -26,7 +26,7 @@ namespace Avalonia namespace Avalonia.Android { - class AndroidPlatform : IPlatformSettings + class AndroidPlatform { public static readonly AndroidPlatform Instance = new AndroidPlatform(); public static AndroidPlatformOptions Options { get; private set; }