committed by
GitHub
24 changed files with 1385 additions and 756 deletions
@ -1,5 +1,5 @@ |
|||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
||||
<ItemGroup> |
<ItemGroup> |
||||
<PackageReference Include="System.Reactive" Version="4.1.6" /> |
<PackageReference Include="System.Reactive" Version="4.4.1" /> |
||||
</ItemGroup> |
</ItemGroup> |
||||
</Project> |
</Project> |
||||
|
|||||
@ -0,0 +1,107 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using Avalonia.Rendering; |
||||
|
using Avalonia.Threading; |
||||
|
using Avalonia.VisualTree; |
||||
|
|
||||
|
namespace Avalonia.X11 |
||||
|
{ |
||||
|
public class X11ImmediateRendererProxy : IRenderer, IRenderLoopTask |
||||
|
{ |
||||
|
private readonly IRenderLoop _loop; |
||||
|
private ImmediateRenderer _renderer; |
||||
|
private bool _invalidated; |
||||
|
private object _lock = new object(); |
||||
|
|
||||
|
public X11ImmediateRendererProxy(IVisual root, IRenderLoop loop) |
||||
|
{ |
||||
|
_loop = loop; |
||||
|
_renderer = new ImmediateRenderer(root); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
_renderer.Dispose(); |
||||
|
} |
||||
|
|
||||
|
public bool DrawFps |
||||
|
{ |
||||
|
get => _renderer.DrawFps; |
||||
|
set => _renderer.DrawFps = value; |
||||
|
} |
||||
|
|
||||
|
public bool DrawDirtyRects |
||||
|
{ |
||||
|
get => _renderer.DrawDirtyRects; |
||||
|
set => _renderer.DrawDirtyRects = value; |
||||
|
} |
||||
|
|
||||
|
public event EventHandler<SceneInvalidatedEventArgs> SceneInvalidated |
||||
|
{ |
||||
|
add => _renderer.SceneInvalidated += value; |
||||
|
remove => _renderer.SceneInvalidated -= value; |
||||
|
} |
||||
|
|
||||
|
public void AddDirty(IVisual visual) |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
_invalidated = true; |
||||
|
_renderer.AddDirty(visual); |
||||
|
} |
||||
|
|
||||
|
public IEnumerable<IVisual> HitTest(Point p, IVisual root, Func<IVisual, bool> filter) |
||||
|
{ |
||||
|
return _renderer.HitTest(p, root, filter); |
||||
|
} |
||||
|
|
||||
|
public IVisual HitTestFirst(Point p, IVisual root, Func<IVisual, bool> filter) |
||||
|
{ |
||||
|
return _renderer.HitTestFirst(p, root, filter); |
||||
|
} |
||||
|
|
||||
|
public void RecalculateChildren(IVisual visual) |
||||
|
{ |
||||
|
_renderer.RecalculateChildren(visual); |
||||
|
} |
||||
|
|
||||
|
public void Resized(Size size) |
||||
|
{ |
||||
|
_renderer.Resized(size); |
||||
|
} |
||||
|
|
||||
|
public void Paint(Rect rect) |
||||
|
{ |
||||
|
_invalidated = false; |
||||
|
_renderer.Paint(rect); |
||||
|
} |
||||
|
|
||||
|
public void Start() |
||||
|
{ |
||||
|
_loop.Add(this); |
||||
|
_renderer.Start(); |
||||
|
} |
||||
|
|
||||
|
public void Stop() |
||||
|
{ |
||||
|
_loop.Remove(this); |
||||
|
_renderer.Stop(); |
||||
|
} |
||||
|
|
||||
|
public bool NeedsUpdate => false; |
||||
|
public void Update(TimeSpan time) |
||||
|
{ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void Render() |
||||
|
{ |
||||
|
if (_invalidated) |
||||
|
{ |
||||
|
lock (_lock) |
||||
|
_invalidated = false; |
||||
|
Dispatcher.UIThread.Post(() => Paint(new Rect(0, 0, 100000, 100000))); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,527 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System; |
||||
|
using System.Diagnostics.CodeAnalysis; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using Avalonia.Controls; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Input.Raw; |
||||
|
using Avalonia.Win32.Input; |
||||
|
using static Avalonia.Win32.Interop.UnmanagedMethods; |
||||
|
|
||||
|
namespace Avalonia.Win32 |
||||
|
{ |
||||
|
public partial class WindowImpl |
||||
|
{ |
||||
|
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", |
||||
|
Justification = "Using Win32 naming for consistency.")] |
||||
|
protected virtual unsafe IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam) |
||||
|
{ |
||||
|
const double wheelDelta = 120.0; |
||||
|
uint timestamp = unchecked((uint)GetMessageTime()); |
||||
|
|
||||
|
RawInputEventArgs e = null; |
||||
|
|
||||
|
switch ((WindowsMessage)msg) |
||||
|
{ |
||||
|
case WindowsMessage.WM_ACTIVATE: |
||||
|
{ |
||||
|
var wa = (WindowActivate)(ToInt32(wParam) & 0xffff); |
||||
|
|
||||
|
switch (wa) |
||||
|
{ |
||||
|
case WindowActivate.WA_ACTIVE: |
||||
|
case WindowActivate.WA_CLICKACTIVE: |
||||
|
{ |
||||
|
Activated?.Invoke(); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowActivate.WA_INACTIVE: |
||||
|
{ |
||||
|
Deactivated?.Invoke(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_NCCALCSIZE: |
||||
|
{ |
||||
|
if (ToInt32(wParam) == 1 && !HasFullDecorations) |
||||
|
{ |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_CLOSE: |
||||
|
{ |
||||
|
bool? preventClosing = Closing?.Invoke(); |
||||
|
if (preventClosing == true) |
||||
|
{ |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_DESTROY: |
||||
|
{ |
||||
|
//Window doesn't exist anymore
|
||||
|
_hwnd = IntPtr.Zero; |
||||
|
//Remove root reference to this class, so unmanaged delegate can be collected
|
||||
|
s_instances.Remove(this); |
||||
|
Closed?.Invoke(); |
||||
|
|
||||
|
if (_parent != null) |
||||
|
{ |
||||
|
_parent._disabledBy.Remove(this); |
||||
|
_parent.UpdateEnabled(); |
||||
|
} |
||||
|
|
||||
|
_mouseDevice.Dispose(); |
||||
|
_touchDevice?.Dispose(); |
||||
|
//Free other resources
|
||||
|
Dispose(); |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_DPICHANGED: |
||||
|
{ |
||||
|
var dpi = ToInt32(wParam) & 0xffff; |
||||
|
var newDisplayRect = Marshal.PtrToStructure<RECT>(lParam); |
||||
|
_scaling = dpi / 96.0; |
||||
|
ScalingChanged?.Invoke(_scaling); |
||||
|
SetWindowPos(hWnd, |
||||
|
IntPtr.Zero, |
||||
|
newDisplayRect.left, |
||||
|
newDisplayRect.top, |
||||
|
newDisplayRect.right - newDisplayRect.left, |
||||
|
newDisplayRect.bottom - newDisplayRect.top, |
||||
|
SetWindowPosFlags.SWP_NOZORDER | |
||||
|
SetWindowPosFlags.SWP_NOACTIVATE); |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_KEYDOWN: |
||||
|
case WindowsMessage.WM_SYSKEYDOWN: |
||||
|
{ |
||||
|
e = new RawKeyEventArgs( |
||||
|
WindowsKeyboardDevice.Instance, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
RawKeyEventType.KeyDown, |
||||
|
KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), |
||||
|
WindowsKeyboardDevice.Instance.Modifiers); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_MENUCHAR: |
||||
|
{ |
||||
|
// mute the system beep
|
||||
|
return (IntPtr)((int)MenuCharParam.MNC_CLOSE << 16); |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_KEYUP: |
||||
|
case WindowsMessage.WM_SYSKEYUP: |
||||
|
{ |
||||
|
e = new RawKeyEventArgs( |
||||
|
WindowsKeyboardDevice.Instance, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
RawKeyEventType.KeyUp, |
||||
|
KeyInterop.KeyFromVirtualKey(ToInt32(wParam), ToInt32(lParam)), |
||||
|
WindowsKeyboardDevice.Instance.Modifiers); |
||||
|
break; |
||||
|
} |
||||
|
case WindowsMessage.WM_CHAR: |
||||
|
{ |
||||
|
// Ignore control chars
|
||||
|
if (ToInt32(wParam) >= 32) |
||||
|
{ |
||||
|
e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp, _owner, |
||||
|
new string((char)ToInt32(wParam), 1)); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_LBUTTONDOWN: |
||||
|
case WindowsMessage.WM_RBUTTONDOWN: |
||||
|
case WindowsMessage.WM_MBUTTONDOWN: |
||||
|
case WindowsMessage.WM_XBUTTONDOWN: |
||||
|
{ |
||||
|
if (ShouldIgnoreTouchEmulatedMessage()) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
e = new RawPointerEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
(WindowsMessage)msg switch |
||||
|
{ |
||||
|
WindowsMessage.WM_LBUTTONDOWN => RawPointerEventType.LeftButtonDown, |
||||
|
WindowsMessage.WM_RBUTTONDOWN => RawPointerEventType.RightButtonDown, |
||||
|
WindowsMessage.WM_MBUTTONDOWN => RawPointerEventType.MiddleButtonDown, |
||||
|
WindowsMessage.WM_XBUTTONDOWN => |
||||
|
HighWord(ToInt32(wParam)) == 1 ? |
||||
|
RawPointerEventType.XButton1Down : |
||||
|
RawPointerEventType.XButton2Down |
||||
|
}, |
||||
|
DipFromLParam(lParam), GetMouseModifiers(wParam)); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_LBUTTONUP: |
||||
|
case WindowsMessage.WM_RBUTTONUP: |
||||
|
case WindowsMessage.WM_MBUTTONUP: |
||||
|
case WindowsMessage.WM_XBUTTONUP: |
||||
|
{ |
||||
|
if (ShouldIgnoreTouchEmulatedMessage()) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
e = new RawPointerEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
(WindowsMessage)msg switch |
||||
|
{ |
||||
|
WindowsMessage.WM_LBUTTONUP => RawPointerEventType.LeftButtonUp, |
||||
|
WindowsMessage.WM_RBUTTONUP => RawPointerEventType.RightButtonUp, |
||||
|
WindowsMessage.WM_MBUTTONUP => RawPointerEventType.MiddleButtonUp, |
||||
|
WindowsMessage.WM_XBUTTONUP => |
||||
|
HighWord(ToInt32(wParam)) == 1 ? |
||||
|
RawPointerEventType.XButton1Up : |
||||
|
RawPointerEventType.XButton2Up, |
||||
|
}, |
||||
|
DipFromLParam(lParam), GetMouseModifiers(wParam)); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_MOUSEMOVE: |
||||
|
{ |
||||
|
if (ShouldIgnoreTouchEmulatedMessage()) |
||||
|
{ |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
if (!_trackingMouse) |
||||
|
{ |
||||
|
var tm = new TRACKMOUSEEVENT |
||||
|
{ |
||||
|
cbSize = Marshal.SizeOf<TRACKMOUSEEVENT>(), |
||||
|
dwFlags = 2, |
||||
|
hwndTrack = _hwnd, |
||||
|
dwHoverTime = 0, |
||||
|
}; |
||||
|
|
||||
|
TrackMouseEvent(ref tm); |
||||
|
} |
||||
|
|
||||
|
e = new RawPointerEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
RawPointerEventType.Move, |
||||
|
DipFromLParam(lParam), GetMouseModifiers(wParam)); |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_MOUSEWHEEL: |
||||
|
{ |
||||
|
e = new RawMouseWheelEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
PointToClient(PointFromLParam(lParam)), |
||||
|
new Vector(0, (ToInt32(wParam) >> 16) / wheelDelta), GetMouseModifiers(wParam)); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_MOUSEHWHEEL: |
||||
|
{ |
||||
|
e = new RawMouseWheelEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
PointToClient(PointFromLParam(lParam)), |
||||
|
new Vector(-(ToInt32(wParam) >> 16) / wheelDelta, 0), GetMouseModifiers(wParam)); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_MOUSELEAVE: |
||||
|
{ |
||||
|
_trackingMouse = false; |
||||
|
e = new RawPointerEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
RawPointerEventType.LeaveWindow, |
||||
|
new Point(-1, -1), WindowsKeyboardDevice.Instance.Modifiers); |
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_NCLBUTTONDOWN: |
||||
|
case WindowsMessage.WM_NCRBUTTONDOWN: |
||||
|
case WindowsMessage.WM_NCMBUTTONDOWN: |
||||
|
case WindowsMessage.WM_NCXBUTTONDOWN: |
||||
|
{ |
||||
|
e = new RawPointerEventArgs( |
||||
|
_mouseDevice, |
||||
|
timestamp, |
||||
|
_owner, |
||||
|
(WindowsMessage)msg switch |
||||
|
{ |
||||
|
WindowsMessage.WM_NCLBUTTONDOWN => RawPointerEventType |
||||
|
.NonClientLeftButtonDown, |
||||
|
WindowsMessage.WM_NCRBUTTONDOWN => RawPointerEventType.RightButtonDown, |
||||
|
WindowsMessage.WM_NCMBUTTONDOWN => RawPointerEventType.MiddleButtonDown, |
||||
|
WindowsMessage.WM_NCXBUTTONDOWN => |
||||
|
HighWord(ToInt32(wParam)) == 1 ? |
||||
|
RawPointerEventType.XButton1Down : |
||||
|
RawPointerEventType.XButton2Down, |
||||
|
}, |
||||
|
PointToClient(PointFromLParam(lParam)), GetMouseModifiers(wParam)); |
||||
|
break; |
||||
|
} |
||||
|
case WindowsMessage.WM_TOUCH: |
||||
|
{ |
||||
|
var touchInputCount = wParam.ToInt32(); |
||||
|
|
||||
|
var pTouchInputs = stackalloc TOUCHINPUT[touchInputCount]; |
||||
|
var touchInputs = new Span<TOUCHINPUT>(pTouchInputs, touchInputCount); |
||||
|
|
||||
|
if (GetTouchInputInfo(lParam, (uint)touchInputCount, pTouchInputs, Marshal.SizeOf<TOUCHINPUT>())) |
||||
|
{ |
||||
|
foreach (var touchInput in touchInputs) |
||||
|
{ |
||||
|
Input?.Invoke(new RawTouchEventArgs(_touchDevice, touchInput.Time, |
||||
|
_owner, |
||||
|
touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_UP) ? |
||||
|
RawPointerEventType.TouchEnd : |
||||
|
touchInput.Flags.HasFlagCustom(TouchInputFlags.TOUCHEVENTF_DOWN) ? |
||||
|
RawPointerEventType.TouchBegin : |
||||
|
RawPointerEventType.TouchUpdate, |
||||
|
PointToClient(new PixelPoint(touchInput.X / 100, touchInput.Y / 100)), |
||||
|
WindowsKeyboardDevice.Instance.Modifiers, |
||||
|
touchInput.Id)); |
||||
|
} |
||||
|
|
||||
|
CloseTouchInputHandle(lParam); |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
case WindowsMessage.WM_NCPAINT: |
||||
|
{ |
||||
|
if (!HasFullDecorations) |
||||
|
{ |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_NCACTIVATE: |
||||
|
{ |
||||
|
if (!HasFullDecorations) |
||||
|
{ |
||||
|
return new IntPtr(1); |
||||
|
} |
||||
|
|
||||
|
break; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_PAINT: |
||||
|
{ |
||||
|
using (_rendererLock.Lock()) |
||||
|
{ |
||||
|
if (BeginPaint(_hwnd, out PAINTSTRUCT ps) != IntPtr.Zero) |
||||
|
{ |
||||
|
var f = Scaling; |
||||
|
var r = ps.rcPaint; |
||||
|
Paint?.Invoke(new Rect(r.left / f, r.top / f, (r.right - r.left) / f, |
||||
|
(r.bottom - r.top) / f)); |
||||
|
EndPaint(_hwnd, ref ps); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_SIZE: |
||||
|
{ |
||||
|
using (_rendererLock.Lock()) |
||||
|
{ |
||||
|
// Do nothing here, just block until the pending frame render is completed on the render thread
|
||||
|
} |
||||
|
|
||||
|
var size = (SizeCommand)wParam; |
||||
|
|
||||
|
if (Resized != null && |
||||
|
(size == SizeCommand.Restored || |
||||
|
size == SizeCommand.Maximized)) |
||||
|
{ |
||||
|
var clientSize = new Size(ToInt32(lParam) & 0xffff, ToInt32(lParam) >> 16); |
||||
|
Resized(clientSize / Scaling); |
||||
|
} |
||||
|
|
||||
|
var windowState = size == SizeCommand.Maximized ? |
||||
|
WindowState.Maximized : |
||||
|
(size == SizeCommand.Minimized ? WindowState.Minimized : WindowState.Normal); |
||||
|
|
||||
|
if (windowState != _lastWindowState) |
||||
|
{ |
||||
|
_lastWindowState = windowState; |
||||
|
WindowStateChanged?.Invoke(windowState); |
||||
|
} |
||||
|
|
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_MOVE: |
||||
|
{ |
||||
|
PositionChanged?.Invoke(new PixelPoint((short)(ToInt32(lParam) & 0xffff), |
||||
|
(short)(ToInt32(lParam) >> 16))); |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_GETMINMAXINFO: |
||||
|
{ |
||||
|
MINMAXINFO mmi = Marshal.PtrToStructure<MINMAXINFO>(lParam); |
||||
|
|
||||
|
if (_minSize.Width > 0) |
||||
|
{ |
||||
|
mmi.ptMinTrackSize.X = |
||||
|
(int)((_minSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); |
||||
|
} |
||||
|
|
||||
|
if (_minSize.Height > 0) |
||||
|
{ |
||||
|
mmi.ptMinTrackSize.Y = |
||||
|
(int)((_minSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); |
||||
|
} |
||||
|
|
||||
|
if (!double.IsInfinity(_maxSize.Width) && _maxSize.Width > 0) |
||||
|
{ |
||||
|
mmi.ptMaxTrackSize.X = |
||||
|
(int)((_maxSize.Width * Scaling) + BorderThickness.Left + BorderThickness.Right); |
||||
|
} |
||||
|
|
||||
|
if (!double.IsInfinity(_maxSize.Height) && _maxSize.Height > 0) |
||||
|
{ |
||||
|
mmi.ptMaxTrackSize.Y = |
||||
|
(int)((_maxSize.Height * Scaling) + BorderThickness.Top + BorderThickness.Bottom); |
||||
|
} |
||||
|
|
||||
|
Marshal.StructureToPtr(mmi, lParam, true); |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
|
||||
|
case WindowsMessage.WM_DISPLAYCHANGE: |
||||
|
{ |
||||
|
(Screen as ScreenImpl)?.InvalidateScreensCache(); |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
#if USE_MANAGED_DRAG
|
||||
|
if (_managedDrag.PreprocessInputEvent(ref e)) |
||||
|
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam); |
||||
|
#endif
|
||||
|
|
||||
|
if (e != null && Input != null) |
||||
|
{ |
||||
|
Input(e); |
||||
|
|
||||
|
if (e.Handled) |
||||
|
{ |
||||
|
return IntPtr.Zero; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
using (_rendererLock.Lock()) |
||||
|
{ |
||||
|
return DefWindowProc(hWnd, msg, wParam, lParam); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static int ToInt32(IntPtr ptr) |
||||
|
{ |
||||
|
if (IntPtr.Size == 4) |
||||
|
return ptr.ToInt32(); |
||||
|
|
||||
|
return (int)(ptr.ToInt64() & 0xffffffff); |
||||
|
} |
||||
|
|
||||
|
private static int HighWord(int param) => param >> 16; |
||||
|
|
||||
|
private Point DipFromLParam(IntPtr lParam) |
||||
|
{ |
||||
|
return new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)) / Scaling; |
||||
|
} |
||||
|
|
||||
|
private PixelPoint PointFromLParam(IntPtr lParam) |
||||
|
{ |
||||
|
return new PixelPoint((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)); |
||||
|
} |
||||
|
|
||||
|
private bool ShouldIgnoreTouchEmulatedMessage() |
||||
|
{ |
||||
|
if (!_multitouch) |
||||
|
{ |
||||
|
return false; |
||||
|
} |
||||
|
|
||||
|
// MI_WP_SIGNATURE
|
||||
|
// https://docs.microsoft.com/en-us/windows/win32/tablet/system-events-and-mouse-messages
|
||||
|
const long marker = 0xFF515700L; |
||||
|
|
||||
|
var info = GetMessageExtraInfo().ToInt64(); |
||||
|
return (info & marker) == marker; |
||||
|
} |
||||
|
|
||||
|
private static RawInputModifiers GetMouseModifiers(IntPtr wParam) |
||||
|
{ |
||||
|
var keys = (ModifierKeys)ToInt32(wParam); |
||||
|
var modifiers = WindowsKeyboardDevice.Instance.Modifiers; |
||||
|
|
||||
|
if (keys.HasFlagCustom(ModifierKeys.MK_LBUTTON)) |
||||
|
{ |
||||
|
modifiers |= RawInputModifiers.LeftMouseButton; |
||||
|
} |
||||
|
|
||||
|
if (keys.HasFlagCustom(ModifierKeys.MK_RBUTTON)) |
||||
|
{ |
||||
|
modifiers |= RawInputModifiers.RightMouseButton; |
||||
|
} |
||||
|
|
||||
|
if (keys.HasFlagCustom(ModifierKeys.MK_MBUTTON)) |
||||
|
{ |
||||
|
modifiers |= RawInputModifiers.MiddleMouseButton; |
||||
|
} |
||||
|
|
||||
|
if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON1)) |
||||
|
{ |
||||
|
modifiers |= RawInputModifiers.XButton1MouseButton; |
||||
|
} |
||||
|
|
||||
|
if (keys.HasFlagCustom(ModifierKeys.MK_XBUTTON2)) |
||||
|
{ |
||||
|
modifiers |= RawInputModifiers.XButton2MouseButton; |
||||
|
} |
||||
|
|
||||
|
return modifiers; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1,178 @@ |
|||||
|
// Copyright (c) The Avalonia Project. All rights reserved.
|
||||
|
// Licensed under the MIT license. See licence.md file in the project root for full license information.
|
||||
|
|
||||
|
using System.Collections.Generic; |
||||
|
using System.Globalization; |
||||
|
using System.Linq; |
||||
|
using System.Reactive.Linq; |
||||
|
using System.Reactive.Subjects; |
||||
|
using Avalonia.Data; |
||||
|
using Avalonia.Data.Converters; |
||||
|
using Xunit; |
||||
|
|
||||
|
namespace Avalonia.Base.UnitTests |
||||
|
{ |
||||
|
public class AvaloniaObjectTests_MultiBinding |
||||
|
{ |
||||
|
[Fact] |
||||
|
public void Should_Update() |
||||
|
{ |
||||
|
var target = new Class1(); |
||||
|
|
||||
|
var b = new Subject<object>(); |
||||
|
|
||||
|
var mb = new MultiBinding() |
||||
|
{ |
||||
|
Converter = StringJoinConverter, |
||||
|
Bindings = new[] |
||||
|
{ |
||||
|
b.ToBinding() |
||||
|
} |
||||
|
}; |
||||
|
target.Bind(Class1.FooProperty, mb); |
||||
|
|
||||
|
Assert.Equal(null, target.Foo); |
||||
|
|
||||
|
b.OnNext("Foo"); |
||||
|
|
||||
|
Assert.Equal("Foo", target.Foo); |
||||
|
|
||||
|
b.OnNext("Bar"); |
||||
|
|
||||
|
Assert.Equal("Bar", target.Foo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Update_With_Multiple_Bindings() |
||||
|
{ |
||||
|
var target = new Class1(); |
||||
|
|
||||
|
var bindings = Enumerable.Range(0, 3).Select(i => new BehaviorSubject<object>("Empty")).ToArray(); |
||||
|
|
||||
|
var mb = new MultiBinding() |
||||
|
{ |
||||
|
Converter = StringJoinConverter, |
||||
|
Bindings = bindings.Select(b => b.ToBinding()).ToArray() |
||||
|
}; |
||||
|
target.Bind(Class1.FooProperty, mb); |
||||
|
|
||||
|
Assert.Equal("Empty,Empty,Empty", target.Foo); |
||||
|
|
||||
|
bindings[0].OnNext("Foo"); |
||||
|
|
||||
|
Assert.Equal("Foo,Empty,Empty", target.Foo); |
||||
|
|
||||
|
bindings[1].OnNext("Bar"); |
||||
|
|
||||
|
Assert.Equal("Foo,Bar,Empty", target.Foo); |
||||
|
|
||||
|
bindings[2].OnNext("Baz"); |
||||
|
|
||||
|
Assert.Equal("Foo,Bar,Baz", target.Foo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Update_When_Null_Value_In_Bindings() |
||||
|
{ |
||||
|
var target = new Class1(); |
||||
|
|
||||
|
var b = new Subject<object>(); |
||||
|
|
||||
|
var mb = new MultiBinding() |
||||
|
{ |
||||
|
Converter = StringJoinConverter, |
||||
|
Bindings = new[] |
||||
|
{ |
||||
|
b.ToBinding() |
||||
|
} |
||||
|
}; |
||||
|
target.Bind(Class1.FooProperty, mb); |
||||
|
|
||||
|
Assert.Equal(null, target.Foo); |
||||
|
|
||||
|
b.OnNext("Foo"); |
||||
|
|
||||
|
Assert.Equal("Foo", target.Foo); |
||||
|
|
||||
|
b.OnNext(null); |
||||
|
|
||||
|
Assert.Equal("", target.Foo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void Should_Update_When_Null_Value_In_Bindings_With_StringFormat() |
||||
|
{ |
||||
|
var target = new Class1(); |
||||
|
|
||||
|
var b = new Subject<object>(); |
||||
|
|
||||
|
var mb = new MultiBinding() |
||||
|
{ |
||||
|
StringFormat = "Converted: {0}", |
||||
|
Bindings = new[] |
||||
|
{ |
||||
|
b.ToBinding() |
||||
|
} |
||||
|
}; |
||||
|
target.Bind(Class1.FooProperty, mb); |
||||
|
|
||||
|
Assert.Equal(null, target.Foo); |
||||
|
b.OnNext("Foo"); |
||||
|
Assert.Equal("Converted: Foo", target.Foo); |
||||
|
b.OnNext(null); |
||||
|
Assert.Equal("Converted: ", target.Foo); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void MultiValueConverter_Should_Not_Skip_Valid_Null_ReferenceType_Value() |
||||
|
{ |
||||
|
var target = new FuncMultiValueConverter<string, string>(v => string.Join(",", v.ToArray())); |
||||
|
|
||||
|
object value = target.Convert(new[] { "Foo", "Bar", "Baz" }, typeof(string), null, CultureInfo.InvariantCulture); |
||||
|
|
||||
|
Assert.Equal("Foo,Bar,Baz", value); |
||||
|
|
||||
|
value = target.Convert(new[] { null, "Bar", "Baz" }, typeof(string), null, CultureInfo.InvariantCulture); |
||||
|
|
||||
|
Assert.Equal(",Bar,Baz", value); |
||||
|
} |
||||
|
|
||||
|
[Fact] |
||||
|
public void MultiValueConverter_Should_Not_Skip_Valid_Default_ValueType_Value() |
||||
|
{ |
||||
|
var target = new FuncMultiValueConverter<StringValueTypeWrapper, string>(v => string.Join(",", v.ToArray())); |
||||
|
|
||||
|
IList<object> create(string[] values) => |
||||
|
values.Select(v => (object)(v != null ? new StringValueTypeWrapper() { Value = v } : default)).ToList(); |
||||
|
|
||||
|
object value = target.Convert(create(new[] { "Foo", "Bar", "Baz" }), typeof(string), null, CultureInfo.InvariantCulture); |
||||
|
|
||||
|
Assert.Equal("Foo,Bar,Baz", value); |
||||
|
|
||||
|
value = target.Convert(create(new[] { null, "Bar", "Baz" }), typeof(string), null, CultureInfo.InvariantCulture); |
||||
|
|
||||
|
Assert.Equal(",Bar,Baz", value); |
||||
|
} |
||||
|
|
||||
|
private struct StringValueTypeWrapper |
||||
|
{ |
||||
|
public string Value; |
||||
|
|
||||
|
public override string ToString() => Value; |
||||
|
} |
||||
|
|
||||
|
private static IMultiValueConverter StringJoinConverter = new FuncMultiValueConverter<object, string>(v => string.Join(",", v.ToArray())); |
||||
|
|
||||
|
private class Class1 : AvaloniaObject |
||||
|
{ |
||||
|
public static readonly StyledProperty<string> FooProperty = |
||||
|
AvaloniaProperty.Register<Class1, string>("Foo"); |
||||
|
|
||||
|
public string Foo |
||||
|
{ |
||||
|
get => GetValue(FooProperty); |
||||
|
set => SetValue(FooProperty, value); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue