From 0445cf502ab149f72462d13eb46d0d42d41649e9 Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Sun, 10 Aug 2025 18:59:42 -0700 Subject: [PATCH 1/2] Fix tooltip position abruptly changing when content reaches edge of the screen --- osu.Framework/Graphics/Cursor/TooltipContainer.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Framework/Graphics/Cursor/TooltipContainer.cs b/osu.Framework/Graphics/Cursor/TooltipContainer.cs index 3f4c65ab5b..890fea6615 100644 --- a/osu.Framework/Graphics/Cursor/TooltipContainer.cs +++ b/osu.Framework/Graphics/Cursor/TooltipContainer.cs @@ -131,13 +131,7 @@ private Vector2 computeMouseTooltipPosition() // Clamp position to tooltip container tooltipPos.X = Math.Min(tooltipPos.X, DrawWidth - CurrentTooltip.DrawSize.X - 5); - float dX = Math.Max(0, tooltipPos.X - cursorCentre.X); - float dY = MathF.Sqrt(boundingRadius * boundingRadius - dX * dX); - - if (tooltipPos.Y > DrawHeight - CurrentTooltip.DrawSize.Y - 5) - tooltipPos.Y = cursorCentre.Y - dY - CurrentTooltip.DrawSize.Y; - else - tooltipPos.Y = cursorCentre.Y + dY; + tooltipPos.Y = Math.Min(tooltipPos.Y, DrawHeight - CurrentTooltip.DrawSize.Y - 5); return tooltipPos; } From d34fea9946de0d8330f6a63a3c9fdfa132a3f3ec Mon Sep 17 00:00:00 2001 From: Joseph Madamba Date: Fri, 15 Aug 2025 16:00:48 -0700 Subject: [PATCH 2/2] Add more tooltip visual tests and `AllowCursorOverlap` bool to `ITooltip` --- .../Visual/UserInterface/TestSceneTooltip.cs | 52 +++++++++++++++++-- osu.Framework/Graphics/Cursor/ITooltip.cs | 10 ++++ .../Graphics/Cursor/TooltipContainer.cs | 20 ++++++- 3 files changed, 77 insertions(+), 5 deletions(-) diff --git a/osu.Framework.Tests/Visual/UserInterface/TestSceneTooltip.cs b/osu.Framework.Tests/Visual/UserInterface/TestSceneTooltip.cs index fc5ae4856c..34f81f9a00 100644 --- a/osu.Framework.Tests/Visual/UserInterface/TestSceneTooltip.cs +++ b/osu.Framework.Tests/Visual/UserInterface/TestSceneTooltip.cs @@ -265,6 +265,16 @@ private void generateTest(bool cursorlessTooltip) Text = "with real time updates!", Size = new Vector2(400, 30), }, + new CustomTallTooltipSpriteTextAlt("this one doesn't overlap!") + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + new CustomTallTooltipSpriteText("this one is anchored down and overlaps!") + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, new TooltipContainer { AutoSizeAxes = Axes.Both, @@ -353,6 +363,26 @@ public CustomTooltipSpriteTextAlt(string displayedContent, string tooltipContent public override ITooltip GetCustomTooltip() => new CustomTooltipAlt(); } + private partial class CustomTallTooltipSpriteText : CustomTooltipSpriteText + { + public CustomTallTooltipSpriteText(string displayedContent, string tooltipContent = null) + : base(displayedContent, tooltipContent) + { + } + + public override ITooltip GetCustomTooltip() => new CustomTallTooltip(); + } + + private partial class CustomTallTooltipSpriteTextAlt : CustomTooltipSpriteText + { + public CustomTallTooltipSpriteTextAlt(string displayedContent, string tooltipContent = null) + : base(displayedContent, tooltipContent) + { + } + + public override ITooltip GetCustomTooltip() => new CustomTallTooltipAlt(); + } + private class CustomContent { public readonly LocalisableString Text; @@ -367,7 +397,7 @@ private partial class CustomTooltip : CompositeDrawable, ITooltip { private static int i; - private readonly SpriteText text; + protected readonly SpriteText Text; public CustomTooltip() { @@ -380,7 +410,7 @@ public CustomTooltip() RelativeSizeAxes = Axes.Both, Colour = FrameworkColour.GreenDark, }, - text = new SpriteText + Text = new SpriteText { Font = FrameworkFont.Regular.With(size: 16), Padding = new MarginPadding(5), @@ -395,9 +425,10 @@ public CustomTooltip() }; } - public void SetContent(CustomContent content) => text.Text = content.Text; + public void SetContent(CustomContent content) => Text.Text = content.Text; public void Move(Vector2 pos) => Position = pos; + public virtual bool AllowCursorOverlap => true; } private partial class CustomTooltipAlt : CustomTooltip @@ -410,6 +441,21 @@ public CustomTooltipAlt() } } + private partial class CustomTallTooltip : CustomTooltip + { + public CustomTallTooltip() + { + AutoSizeAxes = Axes.Both; + + Text.Width = 0; + } + } + + private partial class CustomTallTooltipAlt : CustomTallTooltip + { + public override bool AllowCursorOverlap => false; + } + private partial class TooltipSpriteText : Container, IHasTooltip { public LocalisableString TooltipText { get; } diff --git a/osu.Framework/Graphics/Cursor/ITooltip.cs b/osu.Framework/Graphics/Cursor/ITooltip.cs index 2cec948cf0..30afa4b155 100644 --- a/osu.Framework/Graphics/Cursor/ITooltip.cs +++ b/osu.Framework/Graphics/Cursor/ITooltip.cs @@ -21,6 +21,16 @@ public interface ITooltip : IDrawable /// /// The position the tooltip should be moved to. void Move(Vector2 pos); + + /// + /// Whether to allow the cursor to overlap the tooltip. If true, the tooltip will try to stay anchored + /// to the bottom-right of the cursor while keeping itself on screen, potentially overlapping the cursor. + /// If false, the tooltip will move to the top-right when the content doesn't fit with the current cursor location. + /// + /// + /// If true, this can be used to avoid abrupt position changes when the content is near the bottom window edge. + /// + bool AllowCursorOverlap { get; } } /// diff --git a/osu.Framework/Graphics/Cursor/TooltipContainer.cs b/osu.Framework/Graphics/Cursor/TooltipContainer.cs index 890fea6615..8dc9d84dfc 100644 --- a/osu.Framework/Graphics/Cursor/TooltipContainer.cs +++ b/osu.Framework/Graphics/Cursor/TooltipContainer.cs @@ -129,9 +129,23 @@ private Vector2 computeMouseTooltipPosition() Vector2 southEast = new Vector2(1).Normalized(); Vector2 tooltipPos = cursorCentre + southEast * boundingRadius; + const float edge_padding = 5; + // Clamp position to tooltip container - tooltipPos.X = Math.Min(tooltipPos.X, DrawWidth - CurrentTooltip.DrawSize.X - 5); - tooltipPos.Y = Math.Min(tooltipPos.Y, DrawHeight - CurrentTooltip.DrawSize.Y - 5); + tooltipPos.X = Math.Min(tooltipPos.X, DrawWidth - CurrentTooltip.DrawSize.X - edge_padding); + + if (CurrentTooltip.AllowCursorOverlap) + tooltipPos.Y = Math.Min(tooltipPos.Y, DrawHeight - CurrentTooltip.DrawSize.Y - edge_padding); + else + { + float dX = Math.Max(0, tooltipPos.X - cursorCentre.X); + float dY = MathF.Sqrt(boundingRadius * boundingRadius - dX * dX); + + if (tooltipPos.Y > DrawHeight - CurrentTooltip.DrawSize.Y - edge_padding) + tooltipPos.Y = Math.Max(cursorCentre.Y - dY - CurrentTooltip.DrawSize.Y, edge_padding); + else + tooltipPos.Y = cursorCentre.Y + dY; + } return tooltipPos; } @@ -412,6 +426,8 @@ public virtual void Refresh() /// /// The new position of the tooltip. public virtual void Move(Vector2 pos) => Position = pos; + + public bool AllowCursorOverlap => false; } } }