Browse Source

Merge branch 'master' into listbox_control_selection

pull/9144/head
Max Katz 4 years ago
committed by GitHub
parent
commit
3ced7a35bc
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 30
      src/Avalonia.Controls/TreeViewItem.cs
  2. 1
      src/Web/Avalonia.Web/Avalonia.Web.csproj
  3. 76
      src/Web/Avalonia.Web/AvaloniaView.cs
  4. 11
      src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
  5. 6
      src/Web/Avalonia.Web/Interop/InputHelper.cs
  6. 12
      src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts

30
src/Avalonia.Controls/TreeViewItem.cs

@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.LogicalTree; using Avalonia.LogicalTree;
using Avalonia.Threading;
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
@ -45,6 +46,8 @@ namespace Avalonia.Controls
private IControl? _header; private IControl? _header;
private bool _isExpanded; private bool _isExpanded;
private int _level; private int _level;
private bool _templateApplied;
private bool _deferredBringIntoViewFlag;
/// <summary> /// <summary>
/// Initializes static members of the <see cref="TreeViewItem"/> class. /// Initializes static members of the <see cref="TreeViewItem"/> class.
@ -136,15 +139,24 @@ namespace Avalonia.Controls
protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e) protected virtual void OnRequestBringIntoView(RequestBringIntoViewEventArgs e)
{ {
if (e.TargetObject == this && _header != null) if (e.TargetObject == this)
{ {
var m = _header.TransformToVisual(this); if (!_templateApplied)
{
_deferredBringIntoViewFlag = true;
return;
}
if (m.HasValue) if (_header != null)
{ {
var bounds = new Rect(_header.Bounds.Size); var m = _header.TransformToVisual(this);
var rect = bounds.TransformToAABB(m.Value);
e.TargetRect = rect; if (m.HasValue)
{
var bounds = new Rect(_header.Bounds.Size);
var rect = bounds.TransformToAABB(m.Value);
e.TargetRect = rect;
}
} }
} }
} }
@ -187,6 +199,12 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{ {
_header = e.NameScope.Find<IControl>("PART_Header"); _header = e.NameScope.Find<IControl>("PART_Header");
_templateApplied = true;
if (_deferredBringIntoViewFlag)
{
_deferredBringIntoViewFlag = false;
Dispatcher.UIThread.Post(this.BringIntoView); // must use the Dispatcher, otherwise the TreeView doesn't scroll
}
} }
private static int CalculateDistanceFromLogicalParent<T>(ILogical? logical, int @default = -1) where T : class private static int CalculateDistanceFromLogicalParent<T>(ILogical? logical, int @default = -1) where T : class

1
src/Web/Avalonia.Web/Avalonia.Web.csproj

@ -7,6 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<SupportedPlatform Remove="@(SupportedPlatform)" />
<SupportedPlatform Include="browser" /> <SupportedPlatform Include="browser" />
</ItemGroup> </ItemGroup>

76
src/Web/Avalonia.Web/AvaloniaView.cs

@ -1,5 +1,9 @@
using System; using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.InteropServices.JavaScript; using System.Runtime.InteropServices.JavaScript;
using Avalonia.Collections.Pooled;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Embedding; using Avalonia.Controls.Embedding;
using Avalonia.Controls.Platform; using Avalonia.Controls.Platform;
@ -18,6 +22,7 @@ namespace Avalonia.Web
[System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings [System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings
public partial class AvaloniaView : ITextInputMethodImpl public partial class AvaloniaView : ITextInputMethodImpl
{ {
private static readonly PooledList<RawPointerPoint> s_intermediatePointsPooledList = new(ClearMode.Never);
private readonly BrowserTopLevelImpl _topLevelImpl; private readonly BrowserTopLevelImpl _topLevelImpl;
private EmbeddableControlRoot _topLevel; private EmbeddableControlRoot _topLevel;
@ -52,13 +57,13 @@ namespace Avalonia.Web
} }
_containerElement = hostContent.GetPropertyAsJSObject("host") _containerElement = hostContent.GetPropertyAsJSObject("host")
?? throw new InvalidOperationException("Host cannot be null"); ?? throw new InvalidOperationException("Host cannot be null");
_canvas = hostContent.GetPropertyAsJSObject("canvas") _canvas = hostContent.GetPropertyAsJSObject("canvas")
?? throw new InvalidOperationException("Canvas cannot be null"); ?? throw new InvalidOperationException("Canvas cannot be null");
_nativeControlsContainer = hostContent.GetPropertyAsJSObject("nativeHost") _nativeControlsContainer = hostContent.GetPropertyAsJSObject("nativeHost")
?? throw new InvalidOperationException("NativeHost cannot be null"); ?? throw new InvalidOperationException("NativeHost cannot be null");
_inputElement = hostContent.GetPropertyAsJSObject("inputElement") _inputElement = hostContent.GetPropertyAsJSObject("inputElement")
?? throw new InvalidOperationException("InputElement cannot be null"); ?? throw new InvalidOperationException("InputElement cannot be null");
_splash = DomHelper.GetElementById("avalonia-splash"); _splash = DomHelper.GetElementById("avalonia-splash");
@ -96,7 +101,8 @@ namespace Avalonia.Web
OnCompositionUpdate, OnCompositionUpdate,
OnCompositionEnd); OnCompositionEnd);
InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, OnWheel); InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp,
OnPointerCancel, OnWheel);
var skiaOptions = AvaloniaLocator.Current.GetService<SkiaOptions>(); var skiaOptions = AvaloniaLocator.Current.GetService<SkiaOptions>();
@ -117,7 +123,12 @@ namespace Avalonia.Web
_context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024); _context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024);
} }
_topLevelImpl.Surfaces = new[] { new BrowserSkiaSurface(_context, _jsGlInfo, ColorType, new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, GRSurfaceOrigin.BottomLeft) }; _topLevelImpl.Surfaces = new[]
{
new BrowserSkiaSurface(_context, _jsGlInfo, ColorType,
new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi,
GRSurfaceOrigin.BottomLeft)
};
} }
else else
{ {
@ -135,7 +146,7 @@ namespace Avalonia.Web
DomHelper.ObserveSize(host, null, OnSizeChanged); DomHelper.ObserveSize(host, null, OnSizeChanged);
CanvasHelper.RequestAnimationFrame(_canvas, true); CanvasHelper.RequestAnimationFrame(_canvas, true);
InputHelper.FocusElement(_containerElement); InputHelper.FocusElement(_containerElement);
} }
@ -155,17 +166,36 @@ namespace Avalonia.Web
private bool OnPointerMove(JSObject args) private bool OnPointerMove(JSObject args)
{ {
var type = args.GetPropertyAsString("pointertype"); var pointerType = args.GetPropertyAsString("pointerType");
var point = ExtractRawPointerFromJSArgs(args); var point = ExtractRawPointerFromJSArgs(args);
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchUpdate,
_ => RawPointerEventType.Move
};
var coalescedEvents = new Lazy<IReadOnlyList<RawPointerPoint>?>(() =>
{
var points = InputHelper.GetCoalescedEvents(args);
s_intermediatePointsPooledList.Clear();
s_intermediatePointsPooledList.Capacity = points.Length - 1;
// Skip the last one, as it is already processed point.
for (var i = 0; i < points.Length - 1; i++)
{
var point = points[i];
s_intermediatePointsPooledList.Add(ExtractRawPointerFromJSArgs(point));
}
return s_intermediatePointsPooledList;
});
return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, type!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"), coalescedEvents);
} }
private bool OnPointerDown(JSObject args) private bool OnPointerDown(JSObject args)
{ {
var pointerType = args.GetPropertyAsString("pointerType"); var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
var type = pointerType switch var type = pointerType switch
{ {
"touch" => RawPointerEventType.TouchBegin, "touch" => RawPointerEventType.TouchBegin,
@ -176,20 +206,18 @@ namespace Avalonia.Web
2 => RawPointerEventType.RightButtonDown, 2 => RawPointerEventType.RightButtonDown,
3 => RawPointerEventType.XButton1Down, 3 => RawPointerEventType.XButton1Down,
4 => RawPointerEventType.XButton2Down, 4 => RawPointerEventType.XButton2Down,
// 5 => Pen eraser button, 5 => RawPointerEventType.XButton1Down, // should be pen eraser button,
_ => RawPointerEventType.Move _ => RawPointerEventType.Move
} }
}; };
var point = ExtractRawPointerFromJSArgs(args); var point = ExtractRawPointerFromJSArgs(args);
return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
} }
private bool OnPointerUp(JSObject args) private bool OnPointerUp(JSObject args)
{ {
var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse"; var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
var type = pointerType switch var type = pointerType switch
{ {
"touch" => RawPointerEventType.TouchEnd, "touch" => RawPointerEventType.TouchEnd,
@ -200,15 +228,27 @@ namespace Avalonia.Web
2 => RawPointerEventType.RightButtonUp, 2 => RawPointerEventType.RightButtonUp,
3 => RawPointerEventType.XButton1Up, 3 => RawPointerEventType.XButton1Up,
4 => RawPointerEventType.XButton2Up, 4 => RawPointerEventType.XButton2Up,
// 5 => Pen eraser button, 5 => RawPointerEventType.XButton1Up, // should be pen eraser button,
_ => RawPointerEventType.Move _ => RawPointerEventType.Move
} }
}; };
var point = ExtractRawPointerFromJSArgs(args); var point = ExtractRawPointerFromJSArgs(args);
return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId")); return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
} }
private bool OnPointerCancel(JSObject args)
{
var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
if (pointerType == "touch")
{
var point = ExtractRawPointerFromJSArgs(args);
_topLevelImpl.RawPointerEvent(RawPointerEventType.TouchCancel, pointerType, point,
GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
}
return false;
}
private bool OnWheel(JSObject args) private bool OnWheel(JSObject args)
{ {

11
src/Web/Avalonia.Web/BrowserTopLevelImpl.cs

@ -67,17 +67,22 @@ namespace Avalonia.Web
public bool RawPointerEvent( public bool RawPointerEvent(
RawPointerEventType eventType, string pointerType, RawPointerEventType eventType, string pointerType,
RawPointerPoint p, RawInputModifiers modifiers, long touchPointId) RawPointerPoint p, RawInputModifiers modifiers, long touchPointId,
Lazy<IReadOnlyList<RawPointerPoint>?>? intermediatePoints = null)
{ {
if (_inputRoot is { } if (_inputRoot is { }
&& Input is { } input) && Input is { } input)
{ {
var device = GetPointerDevice(pointerType); var device = GetPointerDevice(pointerType);
var args = device is TouchDevice ? var args = device is TouchDevice ?
new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId) : new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId)
{
IntermediatePoints = intermediatePoints
} :
new RawPointerEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers) new RawPointerEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers)
{ {
RawPointerId = touchPointId RawPointerId = touchPointId,
IntermediatePoints = intermediatePoints
}; };
input.Invoke(args); input.Invoke(args);

6
src/Web/Avalonia.Web/Interop/InputHelper.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript; using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks; using System.Threading.Tasks;
@ -36,6 +37,8 @@ internal static partial class InputHelper
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>] [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
Func<JSObject, bool> pointerUp, Func<JSObject, bool> pointerUp,
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>] [JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
Func<JSObject, bool> pointerCancel,
[JSMarshalAs<JSType.Function<JSType.Object, JSType.Boolean>>]
Func<JSObject, bool> wheel); Func<JSObject, bool> wheel);
@ -45,6 +48,9 @@ internal static partial class InputHelper
[JSMarshalAs<JSType.Function<JSType.String, JSType.Boolean>>] [JSMarshalAs<JSType.Function<JSType.String, JSType.Boolean>>]
Func<string, bool> input); Func<string, bool> input);
[JSImport("InputHelper.getCoalescedEvents", AvaloniaModule.MainModuleName)]
[return: JSMarshalAs<JSType.Array<JSType.Object>>]
public static partial JSObject[] GetCoalescedEvents(JSObject pointerEvent);
[JSImport("InputHelper.clearInput", AvaloniaModule.MainModuleName)] [JSImport("InputHelper.clearInput", AvaloniaModule.MainModuleName)]
public static partial void ClearInputElement(JSObject htmlElement); public static partial void ClearInputElement(JSObject htmlElement);

12
src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts

@ -95,6 +95,7 @@ export class InputHelper {
pointerMoveCallback: (args: PointerEvent) => boolean, pointerMoveCallback: (args: PointerEvent) => boolean,
pointerDownCallback: (args: PointerEvent) => boolean, pointerDownCallback: (args: PointerEvent) => boolean,
pointerUpCallback: (args: PointerEvent) => boolean, pointerUpCallback: (args: PointerEvent) => boolean,
pointerCancelCallback: (args: PointerEvent) => boolean,
wheelCallback: (args: WheelEvent) => boolean wheelCallback: (args: WheelEvent) => boolean
) { ) {
const pointerMoveHandler = (args: PointerEvent) => { const pointerMoveHandler = (args: PointerEvent) => {
@ -112,6 +113,11 @@ export class InputHelper {
args.preventDefault(); args.preventDefault();
}; };
const pointerCancelHandler = (args: PointerEvent) => {
pointerCancelCallback(args);
args.preventDefault();
};
const wheelHandler = (args: WheelEvent) => { const wheelHandler = (args: WheelEvent) => {
wheelCallback(args); wheelCallback(args);
args.preventDefault(); args.preventDefault();
@ -121,11 +127,13 @@ export class InputHelper {
element.addEventListener("pointerdown", pointerDownHandler); element.addEventListener("pointerdown", pointerDownHandler);
element.addEventListener("pointerup", pointerUpHandler); element.addEventListener("pointerup", pointerUpHandler);
element.addEventListener("wheel", wheelHandler); element.addEventListener("wheel", wheelHandler);
element.addEventListener("pointercancel", pointerCancelHandler);
return () => { return () => {
element.removeEventListener("pointerover", pointerMoveHandler); element.removeEventListener("pointerover", pointerMoveHandler);
element.removeEventListener("pointerdown", pointerDownHandler); element.removeEventListener("pointerdown", pointerDownHandler);
element.removeEventListener("pointerup", pointerUpHandler); element.removeEventListener("pointerup", pointerUpHandler);
element.removeEventListener("pointercancel", pointerCancelHandler);
element.removeEventListener("wheel", wheelHandler); element.removeEventListener("wheel", wheelHandler);
}; };
} }
@ -146,6 +154,10 @@ export class InputHelper {
}; };
} }
public static getCoalescedEvents(pointerEvent: PointerEvent): PointerEvent[] {
return pointerEvent.getCoalescedEvents();
}
public static clearInput(inputElement: HTMLInputElement) { public static clearInput(inputElement: HTMLInputElement) {
inputElement.value = ""; inputElement.value = "";
} }

Loading…
Cancel
Save