A game framework written with osu! in mind.

Improve usability of custom tooltips

+108 -45
+34 -16
osu.Framework.Tests/Visual/UserInterface/TestSceneTooltip.cs
··· 89 89 { 90 90 new TooltipSpriteText("this text has a tooltip!"), 91 91 new InstantTooltipSpriteText("this text has an instant tooltip!"), 92 - new TooltipSpriteText("this one too!"), 93 - new CustomTooltipSpriteText("this text has an empty tooltip!", string.Empty), 94 - new CustomTooltipSpriteText("this text has a nulled tooltip!", null), 92 + new CustomTooltipSpriteText("this one is custom!"), 93 + new TooltipSpriteText("this text has an empty tooltip!", string.Empty), 94 + new TooltipSpriteText("this text has a nulled tooltip!", null), 95 95 new TooltipTextbox 96 96 { 97 97 Text = "with real time updates!", ··· 143 143 ttc.Add(makeBox(Anchor.BottomRight)); 144 144 } 145 145 146 - private class CustomTooltipSpriteText : Container, IHasTooltip 146 + private class CustomTooltipSpriteText : Container, IHasCustomTooltip 147 147 { 148 - private readonly string tooltipText; 149 - 150 - public string TooltipText => tooltipText; 148 + public object TooltipContent { get; } 151 149 152 - public CustomTooltipSpriteText(string displayedText, string tooltipText) 150 + public CustomTooltipSpriteText(string displayedContent, object tooltipContent = null) 153 151 { 154 - this.tooltipText = tooltipText; 152 + TooltipContent = tooltipContent ?? displayedContent; 155 153 156 154 AutoSizeAxes = Axes.Both; 157 155 Children = new[] 158 156 { 159 157 new SpriteText 160 158 { 161 - Text = displayedText, 159 + Text = displayedContent, 162 160 } 163 161 }; 164 162 } 163 + 164 + public ITooltip GetCustomTooltip() => new TooltipContainer.Tooltip { Colour = Color4.Blue }; 165 165 } 166 166 167 - private class TooltipSpriteText : CustomTooltipSpriteText 167 + private class TooltipSpriteText : Container, IHasTooltip 168 168 { 169 - public TooltipSpriteText(string tooltipText) 170 - : base(tooltipText, tooltipText) 169 + private readonly string tooltipContent; 170 + 171 + public string TooltipText => tooltipContent; 172 + 173 + public TooltipSpriteText(string displayedContent) 174 + : this(displayedContent, displayedContent) 171 175 { 172 176 } 177 + 178 + public TooltipSpriteText(string displayedContent, string tooltipContent) 179 + { 180 + this.tooltipContent = tooltipContent; 181 + 182 + AutoSizeAxes = Axes.Both; 183 + Children = new[] 184 + { 185 + new SpriteText 186 + { 187 + Text = displayedContent, 188 + } 189 + }; 190 + } 173 191 } 174 192 175 - private class InstantTooltipSpriteText : CustomTooltipSpriteText, IHasAppearDelay 193 + private class InstantTooltipSpriteText : TooltipSpriteText, IHasAppearDelay 176 194 { 177 - public InstantTooltipSpriteText(string tooltipText) 178 - : base(tooltipText, tooltipText) 195 + public InstantTooltipSpriteText(string tooltipContent) 196 + : base(tooltipContent, tooltipContent) 179 197 { 180 198 } 181 199
+1 -1
osu.Framework/Graphics/Cursor/IHasAppearDelay.cs
··· 6 6 /// <summary> 7 7 /// A tooltip which provides a custom delay until it appears, override the <see cref="TooltipContainer"/>-wide default. 8 8 /// </summary> 9 - public interface IHasAppearDelay : IHasTooltip 9 + public interface IHasAppearDelay : ITooltipContentProvider 10 10 { 11 11 /// <summary> 12 12 /// The delay until the tooltip should be displayed.
+6 -1
osu.Framework/Graphics/Cursor/IHasCustomTooltip.cs
··· 7 7 /// Implementing this interface allows the implementing <see cref="Drawable"/> to display a custom tooltip if it is the child of a <see cref="TooltipContainer"/>. 8 8 /// Keep in mind that tooltips can only be displayed by a <see cref="TooltipContainer"/> if the <see cref="Drawable"/> implementing <see cref="IHasCustomTooltip"/> has <see cref="Drawable.HandlePositionalInput"/> set to true. 9 9 /// </summary> 10 - public interface IHasCustomTooltip : IHasTooltip 10 + public interface IHasCustomTooltip : ITooltipContentProvider 11 11 { 12 12 /// <summary> 13 13 /// The custom tooltip that should be displayed. 14 14 /// </summary> 15 15 /// <returns>The custom tooltip that should be displayed.</returns> 16 16 ITooltip GetCustomTooltip(); 17 + 18 + /// <summary> 19 + /// Tooltip text that shows when hovering the drawable. 20 + /// </summary> 21 + object TooltipContent { get; } 17 22 } 18 23 }
+2 -2
osu.Framework/Graphics/Cursor/IHasTooltip.cs
··· 7 7 /// Implementing this interface allows the implementing <see cref="Drawable"/> to display a tooltip if it is the child of a <see cref="TooltipContainer"/>. The tooltip used is 8 8 /// dependent on the implementation of the <see cref="TooltipContainer.CreateTooltip"/> method of the <see cref="TooltipContainer"/> containing this <see cref="Drawable"/>. 9 9 /// </summary> 10 - public interface IHasTooltip : IDrawable 10 + public interface IHasTooltip : ITooltipContentProvider 11 11 { 12 12 /// <summary> 13 - /// Tooltip that shows when hovering the drawable. 13 + /// Tooltip text that shows when hovering the drawable. 14 14 /// </summary> 15 15 string TooltipText { get; } 16 16 }
+9
osu.Framework/Graphics/Cursor/ITooltip.cs
··· 1 1 // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 2 // See the LICENCE file in the repository root for full licence text. 3 3 4 + using System; 4 5 using osuTK; 5 6 6 7 namespace osu.Framework.Graphics.Cursor ··· 13 14 /// <summary> 14 15 /// The text to display on the tooltip. 15 16 /// </summary> 17 + [Obsolete] 16 18 string TooltipText { set; } 19 + 20 + /// <summary> 21 + /// Set new content be displayed on this tooltip. 22 + /// </summary> 23 + /// <param name="content">The content to be displayed.</param> 24 + /// <returns>Whether this <see cref="ITooltip"/> can display the provided content.</returns> 25 + bool SetContent(object content); 17 26 18 27 /// <summary> 19 28 /// Refreshes the tooltip, updating potential non-text elements such as textures and colours.
+12
osu.Framework/Graphics/Cursor/ITooltipContentProvider.cs
··· 1 + // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2 + // See the LICENCE file in the repository root for full licence text. 3 + 4 + namespace osu.Framework.Graphics.Cursor 5 + { 6 + /// <summary> 7 + /// Marker interface for interfaces that provide tooltip content. 8 + /// </summary> 9 + public interface ITooltipContentProvider : IDrawable 10 + { 11 + } 12 + }
+44 -25
osu.Framework/Graphics/Cursor/TooltipContainer.cs
··· 17 17 /// <summary> 18 18 /// Displays Tooltips for all its children that inherit from the <see cref="IHasTooltip"/> or <see cref="IHasCustomTooltip"/> interfaces. Keep in mind that only children with <see cref="Drawable.HandlePositionalInput"/> set to true will be checked for their tooltips. 19 19 /// </summary> 20 - public class TooltipContainer : CursorEffectContainer<TooltipContainer, IHasTooltip>, IHandleGlobalInput 20 + public class TooltipContainer : CursorEffectContainer<TooltipContainer, ITooltipContentProvider>, IHandleGlobalInput 21 21 { 22 22 private readonly CursorContainer cursorContainer; 23 23 private readonly ITooltip defaultTooltip; ··· 38 38 /// </summary> 39 39 protected virtual float AppearRadius => 20; 40 40 41 - private IHasTooltip currentlyDisplayed; 41 + private ITooltipContentProvider currentlyDisplayed; 42 42 43 43 /// <summary> 44 44 /// Creates a new tooltip. Can be overridden to supply custom subclass of <see cref="Tooltip"/>. 45 45 /// </summary> 46 46 protected virtual ITooltip CreateTooltip() => new Tooltip(); 47 47 48 - private bool hasValidTooltip(IHasTooltip target) => !string.IsNullOrEmpty(target?.TooltipText); 48 + private bool hasValidTooltip(ITooltipContentProvider target) 49 + { 50 + var targetContent = getTargetContent(target); 51 + 52 + if (targetContent is string strContent) 53 + return !string.IsNullOrEmpty(strContent); 54 + 55 + return targetContent != null; 56 + } 49 57 50 58 private readonly Container content; 51 59 protected override Container<Drawable> Content => content; ··· 135 143 public Vector2 Position; 136 144 } 137 145 146 + private object getTargetContent(ITooltipContentProvider target) => (target as IHasCustomTooltip)?.TooltipContent ?? (target as IHasTooltip)?.TooltipText; 147 + 138 148 protected override void Update() 139 149 { 140 150 base.Update(); 141 151 142 - IHasTooltip target = findTooltipTarget(); 152 + ITooltipContentProvider target = findTooltipTarget(); 143 153 144 154 if (target != null && target != currentlyDisplayed) 145 155 { 146 156 currentlyDisplayed = target; 147 157 148 - var newTooltip = getTooltip(target); 158 + if (CurrentTooltip?.SetContent(getTargetContent(target)) != true) 159 + { 160 + var newTooltip = getTooltip(target); 149 161 150 - if (newTooltip != CurrentTooltip) 151 - { 152 162 RemoveInternal((Drawable)CurrentTooltip); 153 163 CurrentTooltip = newTooltip; 154 164 AddInternal((Drawable)newTooltip); ··· 161 171 } 162 172 } 163 173 174 + protected override void UpdateAfterChildren() 175 + { 176 + base.UpdateAfterChildren(); 177 + 178 + RefreshTooltip(CurrentTooltip, currentlyDisplayed); 179 + 180 + if (currentlyDisplayed != null && ShallHideTooltip(currentlyDisplayed)) 181 + hideTooltip(); 182 + } 183 + 164 184 private readonly List<TimedPosition> recentMousePositions = new List<TimedPosition>(); 165 185 private double lastRecordedPositionTime; 166 186 167 - private IHasTooltip lastCandidate; 187 + private ITooltipContentProvider lastCandidate; 168 188 169 189 /// <summary> 170 190 /// Determines which drawable should currently receive a tooltip, taking into account ··· 172 192 /// target is found. 173 193 /// </summary> 174 194 /// <returns>The tooltip target. null if no valid one is found.</returns> 175 - private IHasTooltip findTooltipTarget() 195 + private ITooltipContentProvider findTooltipTarget() 176 196 { 177 197 // While we are dragging a tooltipped drawable we should show a tooltip for it. 178 198 if (inputManager.DraggedDrawable is IHasTooltip draggedTarget) 179 199 return hasValidTooltip(draggedTarget) ? draggedTarget : null; 180 200 181 - IHasTooltip targetCandidate = FindTargets().Find(t => t.TooltipText != null); 201 + if (inputManager.DraggedDrawable is IHasCustomTooltip customDraggedTarget) 202 + return customDraggedTarget; 203 + 204 + ITooltipContentProvider targetCandidate = FindTargets().Find(hasValidTooltip); 182 205 183 206 // check this first - if we find no target candidate we still want to clear the recorded positions and update the lastCandidate. 184 207 if (targetCandidate != lastCandidate) ··· 234 257 /// </summary> 235 258 /// <param name="tooltip">The tooltip that is refreshed.</param> 236 259 /// <param name="tooltipTarget">The target of the tooltip.</param> 237 - protected virtual void RefreshTooltip(ITooltip tooltip, IHasTooltip tooltipTarget) 260 + protected virtual void RefreshTooltip(ITooltip tooltip, ITooltipContentProvider tooltipTarget) 238 261 { 239 262 bool isValid = tooltipTarget != null && hasValidTooltip(tooltipTarget); 240 263 241 264 if (isValid) 242 265 { 243 - tooltip.TooltipText = tooltipTarget.TooltipText; 266 + tooltip.SetContent(getTargetContent(tooltipTarget)); 244 267 tooltip.Refresh(); 245 268 } 246 269 ··· 248 271 tooltip.Move(computeTooltipPosition()); 249 272 } 250 273 251 - protected override void UpdateAfterChildren() 252 - { 253 - base.UpdateAfterChildren(); 254 - 255 - RefreshTooltip(CurrentTooltip, currentlyDisplayed); 256 - 257 - if (currentlyDisplayed != null && ShallHideTooltip(currentlyDisplayed)) 258 - hideTooltip(); 259 - } 260 - 261 274 private void hideTooltip() 262 275 { 263 276 CurrentTooltip.Hide(); ··· 269 282 /// </summary> 270 283 /// <param name="tooltipTarget">The target of the tooltip.</param> 271 284 /// <returns>True if the currently visible tooltip should be hidden, false otherwise.</returns> 272 - protected virtual bool ShallHideTooltip(IHasTooltip tooltipTarget) => !hasValidTooltip(tooltipTarget) || !tooltipTarget.IsHovered && !tooltipTarget.IsDragged; 285 + protected virtual bool ShallHideTooltip(ITooltipContentProvider tooltipTarget) => !hasValidTooltip(tooltipTarget) || !tooltipTarget.IsHovered && !tooltipTarget.IsDragged; 273 286 274 - private ITooltip getTooltip(IHasTooltip target) => (target as IHasCustomTooltip)?.GetCustomTooltip() ?? defaultTooltip; 287 + private ITooltip getTooltip(ITooltipContentProvider target) => (target as IHasCustomTooltip)?.GetCustomTooltip() ?? defaultTooltip; 275 288 276 289 /// <summary> 277 290 /// The default tooltip. Simply displays its text on a gray background and performs no easing. ··· 285 298 /// </summary> 286 299 public virtual string TooltipText 287 300 { 288 - set => text.Text = value; 301 + set => SetContent(value); 302 + } 303 + 304 + public bool SetContent(object content) 305 + { 306 + text.Text = content.ToString(); 307 + return true; 289 308 } 290 309 291 310 private const float text_size = 16;