Skip to content

Commit

Permalink
feat: Add support for Keyboard Accelerator tooltips
Browse files Browse the repository at this point in the history
  • Loading branch information
MartinZikmund committed Jun 20, 2024
1 parent 1860843 commit 5643e23
Show file tree
Hide file tree
Showing 4 changed files with 117 additions and 65 deletions.
6 changes: 1 addition & 5 deletions src/Uno.UI/DirectUI/DXamlTestHooks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,5 @@
namespace Uno.UI.DirectUI;
internal static class DXamlTestHooks
{
public static ToolTip TestGetActualToolTip(UIElement element)
{
// TODO:MZ: Should be GetActualToolTipObject
return ToolTipService.GetToolTipReference(element);
}
public static ToolTip TestGetActualToolTip(UIElement element) => ToolTipService.GetActualToolTipObject(element);
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ public partial class ToolTipService
"KeyboardAcceleratorToolTipObject",
typeof(ToolTip),
typeof(ToolTipService),
new FrameworkPropertyMetadata(default, OnToolTipChanged));
new FrameworkPropertyMetadata(default));

internal static ToolTip GetKeyboardAcceleratorToolTipObject(DependencyObject element) => (ToolTip)element.GetValue(KeyboardAcceleratorToolTipObjectProperty);

Expand Down
106 changes: 47 additions & 59 deletions src/Uno.UI/UI/Xaml/Controls/ToolTip/ToolTipService.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
using System;
using System.Threading.Tasks;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Input;
using Uno.UI;
using Uno.Disposables;
using Uno.UI;
using Windows.System;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;


#if __IOS__
using UIKit;
Expand All @@ -16,83 +14,73 @@

namespace Microsoft.UI.Xaml.Controls;

/// <summary>
/// Represents a service that provides static methods to display a ToolTip.
/// </summary>
public partial class ToolTipService
{
private static ToolTip m_CurrentToolTip;
private static uint m_LastEnteredFrameId;
private static DispatcherTimer m_OpenTimer;
private static DispatcherTimer m_CloseTimer;

private static void OnToolTipChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
private static void RegisterToolTip(
DependencyObject owner,
FrameworkElement container,
object toolTipAsObject,
bool isKeyboardAcceleratorToolTip)
{
if (!FeatureConfiguration.ToolTip.UseToolTips)
{
return; // ToolTips are disabled
// ToolTips are disabled.
return;
}

if (!(dependencyObject is FrameworkElement owner))
if (owner is null || container is null)
{
// ToolTip must have an owner.
return;
}

if (e.NewValue is null)
var toolTip = ConvertToToolTip(toolTipAsObject);

toolTip.Placement = GetPlacement(toolTip);
toolTip.SetAnchor(GetPlacementTarget(container) ?? container);

if (isKeyboardAcceleratorToolTip)
{
DisposePreviousToolTip();
ToolTipService.SetKeyboardAcceleratorToolTipObject(owner, toolTip);
}
else if (e.NewValue is ToolTip newToolTip)
else
{
var previousToolTip = GetToolTipReference(owner);
ToolTipService.SetToolTipReference(owner, toolTip);
}

// dispose the previous tooltip
if (previousToolTip != null && newToolTip != previousToolTip)
{
DisposePreviousToolTip(previousToolTip);
}
toolTip.OwnerEventSubscriptions = SubscribeToEvents(container, toolTip);
}

// setup new tooltip
if (newToolTip != previousToolTip)
{
SetupToolTip(newToolTip);
}
private static void UnregisterToolTip(DependencyObject owner, FrameworkElement container, bool isKeyboardAcceleratorToolTip)
{
ToolTip toolTipReference = null;
if (isKeyboardAcceleratorToolTip)
{
toolTipReference = ToolTipService.GetKeyboardAcceleratorToolTipObject(owner);
}
else
{
var previousToolTip = GetToolTipReference(owner);
if (e.OldValue is ToolTip oldPrevious && oldPrevious == previousToolTip)
{
// dispose and setup a new tooltip
// to avoid corrupting previous tooltip's content with new value
DisposePreviousToolTip(previousToolTip);
SetupToolTip(new ToolTip { Content = e.NewValue });
}
else if (previousToolTip != null)
{
// update the old tooltip with new content
previousToolTip.Content = e.NewValue;
}
else
{
// setup a new tooltip
SetupToolTip(new ToolTip { Content = e.NewValue });
}
toolTipReference = ToolTipService.GetToolTipReference(owner);
}

void SetupToolTip(ToolTip toolTip)
if (toolTipReference is null)
{
toolTip.Placement = GetPlacement(toolTip);
toolTip.SetAnchor(GetPlacementTarget(owner) ?? owner);

SetToolTipReference(owner, toolTip);
toolTip.OwnerEventSubscriptions = SubscribeToEvents(owner, toolTip);
return;
}
void DisposePreviousToolTip(ToolTip toolTip = null)
{
toolTip ??= GetToolTipReference(owner);

toolTip.OwnerEventSubscriptions?.Dispose();
toolTip.OwnerEventSubscriptions = null;
SetToolTipReference(owner, null);
}
toolTipReference.OwnerEventSubscriptions?.Dispose();
toolTipReference.OwnerEventSubscriptions = null;
CloseToolTipImpl(toolTipReference);

owner.ClearValue(isKeyboardAcceleratorToolTip ? KeyboardAcceleratorToolTipObjectProperty : ToolTipReferenceProperty);
}

private static void OnPlacementChanged(DependencyObject dependencyobject, DependencyPropertyChangedEventArgs e)
Expand Down Expand Up @@ -196,7 +184,7 @@ private static void OnOwnerVisibilityChanged(DependencyObject sender, Dependency

private static void OnOwnerLoaded(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement owner && GetToolTipReference(owner) is { } toolTip)
if (sender is FrameworkElement owner && GetActualToolTipObject(owner) is { } toolTip)
{
owner.PointerEntered += OnPointerEntered;
owner.PointerExited += OnPointerExited;
Expand All @@ -216,7 +204,7 @@ private static void OnOwnerLoaded(object sender, RoutedEventArgs e)

private static void OnOwnerUnloaded(object sender, RoutedEventArgs e)
{
if (sender is FrameworkElement owner && GetToolTipReference(owner) is { } toolTip)
if (sender is FrameworkElement owner && GetActualToolTipObject(owner) is { } toolTip)
{
CloseToolTipImpl(toolTip);

Expand All @@ -240,7 +228,7 @@ private static void OnPointerEntered(object sender, PointerRoutedEventArgs e)
// so we are dropping any subsequent events from this frame-id.
if (e.FrameId == m_LastEnteredFrameId) return;

if (sender is FrameworkElement owner && GetToolTipReference(owner) is { } toolTip)
if (sender is FrameworkElement owner && GetActualToolTipObject(owner) is { } toolTip)
{
if (toolTip.IsOpen) return;

Expand All @@ -257,23 +245,23 @@ private static void OnPointerEntered(object sender, PointerRoutedEventArgs e)

private static void OnPointerExited(object sender, PointerRoutedEventArgs e)
{
if (sender is FrameworkElement owner && GetToolTipReference(owner) is { } toolTip)
if (sender is FrameworkElement owner && GetActualToolTipObject(owner) is { } toolTip)
{
CloseToolTipImpl(toolTip);
}
}

private static void OnTapped(object sender, TappedRoutedEventArgs e)
{
if (sender is FrameworkElement owner && GetToolTipReference(owner) is { } toolTip)
if (sender is FrameworkElement owner && GetActualToolTipObject(owner) is { } toolTip)
{
CloseToolTipImpl(toolTip);
}
}

private static void OnKeyDown(object sender, KeyRoutedEventArgs args)
{
if (sender is FrameworkElement owner && GetToolTipReference(owner) is { } toolTip)
if (sender is FrameworkElement owner && GetActualToolTipObject(owner) is { } toolTip)
{
switch (args.Key)
{
Expand All @@ -290,7 +278,7 @@ private static void OnKeyDown(object sender, KeyRoutedEventArgs args)

private static void OnPointerPressed(object sender, PointerRoutedEventArgs e)
{
if (sender is FrameworkElement owner && GetToolTipReference(owner) is { } toolTip)
if (sender is FrameworkElement owner && GetActualToolTipObject(owner) is { } toolTip)
{
if (e.GetCurrentPoint(owner).Properties.IsLeftButtonPressed)
{
Expand Down
68 changes: 68 additions & 0 deletions src/Uno.UI/UI/Xaml/Controls/ToolTip/ToolTipService.mux.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See LICENSE in the project root for license information.
// MUX Reference dxaml\xcp\dxaml\lib\ToolTipService_Partial.cpp, tag winui3/release/1.5.4, commit 98a60c8f30c84f297a175dd2884d54ecd1c8a4a9
// Contains ported portions of dxaml\xcp\dxaml\lib\ToolTipService_Partial.cpp

#nullable enable

using static Uno.UI.FeatureConfiguration;

namespace Microsoft.UI.Xaml.Controls;

partial class ToolTipService
{
private static void OnToolTipChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
if (sender is FrameworkElement senderAsFe)
{
bool isKeyboardAcceleratorToolTip = args.Property == ToolTipService.KeyboardAcceleratorToolTipProperty;
if (args.OldValue is not UnsetValue && args.OldValue is not null)
{
ToolTipService.UnregisterToolTip(sender, senderAsFe, isKeyboardAcceleratorToolTip);
}

if (args.NewValue is { } toolTip)
{
ToolTipService.RegisterToolTip(sender, senderAsFe, toolTip, isKeyboardAcceleratorToolTip);
}
}
}

private static ToolTip ConvertToToolTip(object objectIn)
{
if (objectIn is not ToolTip toolTip)
{
if (objectIn is FrameworkElement frameworkElement)
{
var objectInParent = objectIn.GetParent();
if (objectInParent is ToolTip parentToolTip)
{
return parentToolTip;
}
}

var newToolTip = new ToolTip
{
Content = objectIn
};

toolTip = newToolTip;
}

return toolTip;
}

internal static ToolTip? GetActualToolTipObject(DependencyObject element)
{
// Try to get the actual public tooltip object
var toolTip = ToolTipService.GetToolTipReference(element);

// If public tooltip doesn't exist, then look for keyboard accelerator tooltip.
if (toolTip is null)
{
toolTip = ToolTipService.GetKeyboardAcceleratorToolTipObject(element);
}

return toolTip;
}
}

0 comments on commit 5643e23

Please sign in to comment.