Skip to content

Commit 14131bb

Browse files
authored
Merge pull request #22743 from ramezgerges/focus_visual_adjust
2 parents 086ac4e + 93cba13 commit 14131bb

File tree

5 files changed

+132
-84
lines changed

5 files changed

+132
-84
lines changed

src/Uno.UI.Composition/Composition/Visual.skia.cs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,39 @@ internal void GetNativeViewPathAndZOrder(SKPath clipFromParent, SKPath clipPath,
574574
}
575575
}
576576

577+
internal void GetTotalClipPath(SKPath dst, bool skipPostPaintingClipping)
578+
{
579+
if (Parent is Visual parent)
580+
{
581+
parent.GetTotalClipPath(dst, false);
582+
}
583+
else
584+
{
585+
dst.Rewind();
586+
dst.AddRect(InfiniteClipRect);
587+
}
588+
589+
var localPath = _pathPool.Allocate();
590+
using var localPathDisposable = new DisposableStruct<SKPath>(static path => _pathPool.Free(path), localPath);
591+
592+
var totalMatrix = TotalMatrix.ToSKMatrix();
593+
if (GetPrePaintingClipping(localPath))
594+
{
595+
// The local clip is in local coordinates. We need to transform it to root coordinates.
596+
localPath.Transform(in totalMatrix);
597+
dst.Op(localPath, SKPathOp.Intersect, dst);
598+
}
599+
600+
if (!skipPostPaintingClipping)
601+
{
602+
if (GetPostPaintingClipping() is { } postClip)
603+
{
604+
postClip.Transform(in totalMatrix);
605+
dst.Op(postClip, SKPathOp.Intersect, dst);
606+
}
607+
}
608+
}
609+
577610
/// <summary>
578611
/// Draws the content of this visual.
579612
/// </summary>

src/Uno.UI.RuntimeTests/Tests/Uno_UI_Xaml_Controls/Given_SystemFocusVisual.cs

Lines changed: 4 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ namespace Uno.UI.RuntimeTests.Tests.Uno_UI_Xaml_Controls;
1717
public class Given_SystemFocusVisual
1818
{
1919
[TestMethod]
20-
[RequiresFullWindow]
2120
public async Task When_Focused_Element_Scrolled()
2221
{
2322
if (TestServices.WindowHelper.IsXamlIsland)
@@ -54,6 +53,7 @@ public async Task When_Focused_Element_Scrolled()
5453

5554
button.Focus(FocusState.Keyboard);
5655
await TestServices.WindowHelper.WaitForIdle();
56+
await UITestHelper.WaitForRender(2);
5757
var visualTree = TestServices.WindowHelper.XamlRoot.VisualTree;
5858
var focusVisualLayer = visualTree?.FocusVisualRoot;
5959

@@ -83,58 +83,6 @@ await TestServices.WindowHelper.WaitFor(() =>
8383
}
8484

8585
[TestMethod]
86-
#if __WASM__
87-
[Ignore("RenderTargetBitmap is not implemented")]
88-
#endif
89-
public async Task When_Focused_Element_Scrolled_Clipping()
90-
{
91-
if (TestServices.WindowHelper.IsXamlIsland)
92-
{
93-
Assert.Inconclusive($"Not supported under XAML islands");
94-
}
95-
var sp = new StackPanel();
96-
var sv = new ScrollViewer
97-
{
98-
Height = 70,
99-
Content = sp
100-
};
101-
var buttons = Enumerable.Range(0, 10).Select(i => new Button
102-
{
103-
Content = $"{i}"
104-
}).ToList();
105-
sp.Children.AddRange(buttons);
106-
107-
var border = new Border
108-
{
109-
Height = 130,
110-
Padding = new Thickness(0, 30, 0, 0),
111-
Child = sv
112-
};
113-
114-
TestServices.WindowHelper.WindowContent = border;
115-
await TestServices.WindowHelper.WaitForIdle();
116-
117-
buttons[2].Focus(FocusState.Keyboard);
118-
await TestServices.WindowHelper.WaitForIdle();
119-
var visualTree = TestServices.WindowHelper.XamlRoot.VisualTree;
120-
var focusVisualLayer = visualTree?.FocusVisualRoot;
121-
122-
Assert.IsNotNull(focusVisualLayer);
123-
Assert.HasCount(1, focusVisualLayer.Children);
124-
125-
for (var i = 0; i < 15; i++)
126-
{
127-
sv.ChangeView(null, 10 * i, null, true);
128-
await TestServices.WindowHelper.WaitForIdle();
129-
130-
var screenShot = await UITestHelper.ScreenShot(border, true);
131-
ImageAssert.DoesNotHaveColorInRectangle(screenShot, new Rectangle(0, 0, 5, 30), ((SolidColorBrush)buttons[1].FocusVisualPrimaryBrush).Color);
132-
ImageAssert.DoesNotHaveColorInRectangle(screenShot, new Rectangle(0, screenShot.Height - 30, 5, 30), ((SolidColorBrush)buttons[2].FocusVisualPrimaryBrush).Color);
133-
}
134-
}
135-
136-
[TestMethod]
137-
[RequiresFullWindow]
13886
#if __ANDROID__ || __APPLE_UIKIT__
13987
[Ignore("Disabled on iOS/Android https://github.com/unoplatform/uno/issues/9080")]
14088
#endif
@@ -160,6 +108,7 @@ public async Task When_Focused_Element_Transformed()
160108

161109
button.Focus(FocusState.Keyboard);
162110
await TestServices.WindowHelper.WaitForIdle();
111+
await UITestHelper.WaitForRender(2);
163112
var visualTree = TestServices.WindowHelper.XamlRoot.VisualTree;
164113
var focusVisualLayer = visualTree?.FocusVisualRoot;
165114

@@ -189,7 +138,6 @@ public async Task When_Keyboard_Focus()
189138
}
190139

191140
[TestMethod]
192-
[RequiresFullWindow]
193141
public async Task When_Focused_Element_In_Scaled_Viewbox()
194142
{
195143
if (TestServices.WindowHelper.IsXamlIsland)
@@ -223,6 +171,7 @@ public async Task When_Focused_Element_In_Scaled_Viewbox()
223171

224172
button.Focus(FocusState.Keyboard);
225173
await TestServices.WindowHelper.WaitForIdle();
174+
await UITestHelper.WaitForRender(2);
226175

227176
var visualTree = TestServices.WindowHelper.XamlRoot.VisualTree;
228177
var focusVisualLayer = visualTree?.FocusVisualRoot;
@@ -245,7 +194,6 @@ public async Task When_Focused_Element_In_Scaled_Viewbox()
245194
}
246195

247196
[TestMethod]
248-
[RequiresFullWindow]
249197
public async Task When_Focused_Element_With_Multiple_Parent_Transforms()
250198
{
251199
if (TestServices.WindowHelper.IsXamlIsland)
@@ -303,6 +251,7 @@ public async Task When_Focused_Element_With_Multiple_Parent_Transforms()
303251

304252
button.Focus(FocusState.Keyboard);
305253
await TestServices.WindowHelper.WaitForIdle();
254+
await UITestHelper.WaitForRender(2);
306255

307256
var visualTree = TestServices.WindowHelper.XamlRoot.VisualTree;
308257
var focusVisualLayer = visualTree?.FocusVisualRoot;

src/Uno.UI/UI/Xaml/Controls/FocusVisual/SystemFocusVisual.cs

Lines changed: 91 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -7,38 +7,24 @@
77
using Microsoft.UI.Xaml.Controls;
88
using Microsoft.UI.Xaml.Media;
99
using System.Numerics;
10-
11-
12-
using WindowSizeChangedEventArgs = Microsoft.UI.Xaml.WindowSizeChangedEventArgs;
10+
using Uno.Extensions;
1311

1412
namespace Uno.UI.Xaml.Controls;
1513

1614
internal partial class SystemFocusVisual : Control
1715
{
16+
#if __SKIA__
17+
private static readonly SkiaSharp.SKPath _spareRenderPath = new SkiaSharp.SKPath();
18+
#endif
1819
private SerialDisposable _focusedElementSubscriptions = new SerialDisposable();
19-
private Rect _lastRect = Rect.Empty;
2020

2121
public SystemFocusVisual()
2222
{
2323
DefaultStyleKey = typeof(SystemFocusVisual);
24+
#if !__SKIA__
2425
Loaded += OnLoaded;
2526
Unloaded += OnUnloaded;
26-
}
27-
28-
private void OnLoaded(object sender, RoutedEventArgs e)
29-
{
30-
if (XamlRoot is not null)
31-
{
32-
XamlRoot.Changed += XamlRootChanged;
33-
}
34-
}
35-
36-
private void OnUnloaded(object sender, RoutedEventArgs e)
37-
{
38-
if (XamlRoot is not null)
39-
{
40-
XamlRoot.Changed -= XamlRootChanged;
41-
}
27+
#endif
4228
}
4329

4430
public UIElement? FocusedElement
@@ -54,16 +40,28 @@ public UIElement? FocusedElement
5440
typeof(SystemFocusVisual),
5541
new FrameworkPropertyMetadata(default, OnFocusedElementChanged));
5642

57-
internal void Redraw() => SetLayoutProperties();
58-
5943
private static void OnFocusedElementChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
6044
{
6145
var focusVisual = (SystemFocusVisual)dependencyObject;
6246

6347
focusVisual._focusedElementSubscriptions.Disposable = null;
6448

49+
#if __SKIA__
50+
focusVisual.Visibility = Visibility.Collapsed;
51+
#endif
52+
6553
if (args.NewValue is FrameworkElement element)
6654
{
55+
#if __SKIA__
56+
if (element.XamlRoot?.VisualTree.ContentRoot.CompositionTarget is { } compositionTarget)
57+
{
58+
compositionTarget.FrameRendered += focusVisual.OnFrameRendered;
59+
focusVisual._focusedElementSubscriptions.Disposable = Disposable.Create(() =>
60+
{
61+
compositionTarget.FrameRendered -= focusVisual.OnFrameRendered;
62+
});
63+
}
64+
#else
6765
element.SizeChanged += focusVisual.FocusedElementSizeChanged;
6866
#if !UNO_HAS_ENHANCED_LIFECYCLE
6967
element.LayoutUpdated += focusVisual.FocusedElementLayoutUpdated;
@@ -75,7 +73,6 @@ private static void OnFocusedElementChanged(DependencyObject dependencyObject, D
7573

7674
focusVisual.AttachVisualPartial();
7775

78-
focusVisual._lastRect = Rect.Empty;
7976
focusVisual.SetLayoutProperties();
8077
var parentViewport = element.GetParentViewport(); // the parent Viewport is used, similar to PropagateEffectiveViewportChange
8178
focusVisual.ApplyClipping(parentViewport.Effective);
@@ -91,6 +88,76 @@ private static void OnFocusedElementChanged(DependencyObject dependencyObject, D
9188

9289
focusVisual.DetachVisualPartial();
9390
});
91+
#endif
92+
}
93+
}
94+
95+
#if __SKIA__
96+
private void OnFrameRendered()
97+
{
98+
if (XamlRoot is null ||
99+
FocusedElement is null ||
100+
FocusedElement.Visibility == Visibility.Collapsed ||
101+
FocusedElement is Control { IsEnabled: false, AllowFocusWhenDisabled: false })
102+
{
103+
Visibility = Visibility.Collapsed;
104+
return;
105+
}
106+
107+
if (VisualTreeHelper.GetParent(FocusedElement) is not UIElement)
108+
{
109+
Visibility = Visibility.Collapsed;
110+
return;
111+
}
112+
113+
var transform = GetTransform(FocusedElement, XamlRoot.VisualTree.RootElement);
114+
115+
FocusedElement.Visual.GetTotalClipPath(_spareRenderPath, true);
116+
var totalClipRect = _spareRenderPath.Bounds;
117+
var inverseMatrix = transform.Inverse();
118+
var topLeft = inverseMatrix.Transform(new Point(totalClipRect.Left, totalClipRect.Top));
119+
var topRight = inverseMatrix.Transform(new Point(totalClipRect.Right, totalClipRect.Top));
120+
var bottomLeft = inverseMatrix.Transform(new Point(totalClipRect.Left, totalClipRect.Bottom));
121+
var bottomRight = inverseMatrix.Transform(new Point(totalClipRect.Right, totalClipRect.Bottom));
122+
123+
var minX = Math.Min(Math.Min(topLeft.X, topRight.X), Math.Min(bottomLeft.X, bottomRight.X));
124+
var maxX = Math.Max(Math.Max(topLeft.X, topRight.X), Math.Max(bottomLeft.X, bottomRight.X));
125+
var minY = Math.Min(Math.Min(topLeft.Y, topRight.Y), Math.Min(bottomLeft.Y, bottomRight.Y));
126+
var maxY = Math.Max(Math.Max(topLeft.Y, topRight.Y), Math.Max(bottomLeft.Y, bottomRight.Y));
127+
128+
var clipRect = new Rect(minX, minY, maxX - minX, maxY - minY);
129+
var layoutRect = new Rect(0, 0, FocusedElement.ActualSize.X, FocusedElement.ActualSize.Y);
130+
var left = Math.Max(clipRect.Left, layoutRect.Left);
131+
var top = Math.Max(clipRect.Top, layoutRect.Top);
132+
var right = Math.Min(clipRect.Right, layoutRect.Right);
133+
var bottom = Math.Min(clipRect.Bottom, layoutRect.Bottom);
134+
135+
var translatedMatrix = new Matrix(Matrix3x2.CreateTranslation((float)left, (float)top) * transform);
136+
if ((RenderTransform as MatrixTransform)?.Matrix != translatedMatrix)
137+
{
138+
RenderTransform = new MatrixTransform { Matrix = translatedMatrix };
139+
}
140+
141+
var newWidth = Math.Max(0, right - left);
142+
var newHeight = Math.Max(0, bottom - top);
143+
Width = newWidth;
144+
Height = newHeight;
145+
Visibility = newWidth <= 0 || newHeight <= 0 ? Visibility.Collapsed : Visibility.Visible;
146+
}
147+
#else
148+
private void OnLoaded(object sender, RoutedEventArgs e)
149+
{
150+
if (XamlRoot is not null)
151+
{
152+
XamlRoot.Changed += XamlRootChanged;
153+
}
154+
}
155+
156+
private void OnUnloaded(object sender, RoutedEventArgs e)
157+
{
158+
if (XamlRoot is not null)
159+
{
160+
XamlRoot.Changed -= XamlRootChanged;
94161
}
95162
}
96163

@@ -192,4 +259,5 @@ private void ApplyClipping(Rect effectiveViewport)
192259

193260
Clip = clip;
194261
}
262+
#endif
195263
}

src/Uno.UI/UI/Xaml/Input/Internal/FocusRectManager.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,9 @@ internal void UpdateFocusRect(
9292
this.Log().LogDebug($"Showing focus rect for {focusedElement?.GetType().Name} and state {(focusedElement as UIElement)?.FocusState} and uses system focus visuals {(focusedElement as UIElement)?.UseSystemFocusVisuals}");
9393
}
9494
_focusVisual.FocusedElement = (focusedElement as FrameworkElement) ?? uiElement;
95+
#if !__SKIA__
9596
_focusVisual.Visibility = Visibility.Visible;
97+
#endif
9698
}
9799
else
98100
{
@@ -101,14 +103,11 @@ internal void UpdateFocusRect(
101103
this.Log().LogDebug($"Hiding focus rect");
102104
}
103105
_focusVisual.FocusedElement = null;
106+
#if !__SKIA__
104107
_focusVisual.Visibility = Visibility.Collapsed;
108+
#endif
105109
}
106110
}
107111
}
108-
109-
internal void RedrawFocusVisual()
110-
{
111-
_focusVisual?.Redraw();
112-
}
113112
}
114113
}

src/Uno.UI/UI/Xaml/XamlRoot.skia.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ partial class XamlRoot
1313
internal void InvalidateOverlays()
1414
{
1515
_focusManager ??= VisualTree.GetFocusManagerForElement(Content);
16-
_focusManager?.FocusRectManager?.RedrawFocusVisual();
1716
if (_focusManager?.FocusedElement is TextBox textBox)
1817
{
1918
textBox.TextBoxView?.Extension?.InvalidateLayout();

0 commit comments

Comments
 (0)