1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 16:02:55 +08:00

Refactor ExpandingControlContainer to no longer rely on controls

This commit is contained in:
Salman Ahmed 2022-02-04 05:45:12 +03:00
parent b2efce2656
commit bbef12e72c
6 changed files with 28 additions and 109 deletions

View File

@ -1,20 +1,16 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Settings.Sections; using osu.Game.Overlays.Settings.Sections;
using osuTK; using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface namespace osu.Game.Tests.Visual.UserInterface
{ {
public class TestSceneExpandingControlContainer : OsuManualInputManagerTestScene public class TestSceneExpandingContainer : OsuManualInputManagerTestScene
{ {
private TestExpandingContainer container; private TestExpandingContainer container;
private SettingsToolboxGroup toolboxGroup; private SettingsToolboxGroup toolboxGroup;
@ -84,44 +80,22 @@ namespace osu.Game.Tests.Visual.UserInterface
} }
/// <summary> /// <summary>
/// Tests hovering over controls expands the parenting container appropriately and does not contract until hover is lost from container. /// Tests hovering expands the container and does not contract until hover is lost.
/// </summary> /// </summary>
[Test] [Test]
public void TestHoveringControlExpandsContainer() public void TestHoveringExpandsContainer()
{ {
AddAssert("ensure container contracted", () => !container.Expanded.Value); AddAssert("ensure container contracted", () => !container.Expanded.Value);
AddStep("hover slider", () => InputManager.MoveMouseTo(slider1)); AddStep("hover container", () => InputManager.MoveMouseTo(container));
AddAssert("container expanded", () => container.Expanded.Value); AddAssert("container expanded", () => container.Expanded.Value);
AddAssert("controls expanded", () => slider1.Expanded.Value && slider2.Expanded.Value); AddAssert("controls expanded", () => slider1.Expanded.Value && slider2.Expanded.Value);
AddStep("hover group top", () => InputManager.MoveMouseTo(toolboxGroup.ScreenSpaceDrawQuad.TopLeft + new Vector2(5)));
AddAssert("container still expanded", () => container.Expanded.Value);
AddAssert("controls still expanded", () => slider1.Expanded.Value && slider2.Expanded.Value);
AddStep("hover away", () => InputManager.MoveMouseTo(Vector2.Zero)); AddStep("hover away", () => InputManager.MoveMouseTo(Vector2.Zero));
AddAssert("container contracted", () => !container.Expanded.Value); AddAssert("container contracted", () => !container.Expanded.Value);
AddAssert("controls contracted", () => !slider1.Expanded.Value && !slider2.Expanded.Value); AddAssert("controls contracted", () => !slider1.Expanded.Value && !slider2.Expanded.Value);
} }
/// <summary>
/// Tests dragging a UI control (e.g. <see cref="OsuSliderBar{T}"/>) outside its parenting container does not contract it until dragging is finished.
/// </summary>
[Test]
public void TestDraggingControlOutsideDoesntContractContainer()
{
AddStep("hover slider", () => InputManager.MoveMouseTo(slider1));
AddAssert("container expanded", () => container.Expanded.Value);
AddStep("hover slider nub", () => InputManager.MoveMouseTo(slider1.ChildrenOfType<Nub>().Single()));
AddStep("hold slider nub", () => InputManager.PressButton(MouseButton.Left));
AddStep("drag outside container", () => InputManager.MoveMouseTo(Vector2.Zero));
AddAssert("container still expanded", () => container.Expanded.Value);
AddStep("release slider nub", () => InputManager.ReleaseButton(MouseButton.Left));
AddAssert("container contracted", () => !container.Expanded.Value);
}
/// <summary> /// <summary>
/// Tests expanding a container will expand underlying groups if contracted. /// Tests expanding a container will expand underlying groups if contracted.
/// </summary> /// </summary>
@ -157,21 +131,21 @@ namespace osu.Game.Tests.Visual.UserInterface
} }
/// <summary> /// <summary>
/// Tests expanding a container via <see cref="ExpandingControlContainer{TControl}.Expanded"/> does not get contracted by losing hover. /// Tests expanding a container via <see cref="ExpandingContainer.Expanded"/> does not get contracted by losing hover.
/// </summary> /// </summary>
[Test] [Test]
public void TestExpandingContainerDoesntGetContractedByHover() public void TestExpandingContainerDoesntGetContractedByHover()
{ {
AddStep("expand container", () => container.Expanded.Value = true); AddStep("expand container", () => container.Expanded.Value = true);
AddStep("hover control", () => InputManager.MoveMouseTo(slider1)); AddStep("hover container", () => InputManager.MoveMouseTo(container));
AddAssert("container still expanded", () => container.Expanded.Value); AddAssert("container still expanded", () => container.Expanded.Value);
AddStep("hover away", () => InputManager.MoveMouseTo(Vector2.Zero)); AddStep("hover away", () => InputManager.MoveMouseTo(Vector2.Zero));
AddAssert("container still expanded", () => container.Expanded.Value); AddAssert("container still expanded", () => container.Expanded.Value);
} }
private class TestExpandingContainer : ExpandingControlContainer<IExpandableControl> private class TestExpandingContainer : ExpandingContainer
{ {
public TestExpandingContainer() public TestExpandingContainer()
: base(120, 250) : base(120, 250)

View File

@ -17,7 +17,7 @@ namespace osu.Game.Overlays
/// <summary> /// <summary>
/// An <see cref="IExpandable"/> implementation for the UI slider bar control. /// An <see cref="IExpandable"/> implementation for the UI slider bar control.
/// </summary> /// </summary>
public class ExpandableSlider<T, TSlider> : CompositeDrawable, IExpandableControl, IHasCurrentValue<T> public class ExpandableSlider<T, TSlider> : CompositeDrawable, IExpandable, IHasCurrentValue<T>
where T : struct, IEquatable<T>, IComparable<T>, IConvertible where T : struct, IEquatable<T>, IComparable<T>, IConvertible
where TSlider : OsuSliderBar<T>, new() where TSlider : OsuSliderBar<T>, new()
{ {
@ -72,8 +72,6 @@ namespace osu.Game.Overlays
public BindableBool Expanded { get; } = new BindableBool(); public BindableBool Expanded { get; } = new BindableBool();
bool IExpandable.ShouldBeExpanded => IsHovered || slider.IsDragged;
public override bool HandlePositionalInput => true; public override bool HandlePositionalInput => true;
public ExpandableSlider() public ExpandableSlider()

View File

@ -1,17 +1,15 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
/// <summary> /// <summary>
/// An <see cref="ExpandingControlContainer{TControl}"/> with a long hover expansion delay for buttons. /// An <see cref="ExpandingContainer"/> with a long hover expansion delay.
/// </summary> /// </summary>
/// <remarks> /// <remarks>
/// Mostly used for buttons with explanatory labels, in which the label would display after a "long hover". /// Mostly used for buttons with explanatory labels, in which the label would display after a "long hover".
/// </remarks> /// </remarks>
public class ExpandingButtonContainer : ExpandingControlContainer<OsuButton> public class ExpandingButtonContainer : ExpandingContainer
{ {
protected ExpandingButtonContainer(float contractedWidth, float expandedWidth) protected ExpandingButtonContainer(float contractedWidth, float expandedWidth)
: base(contractedWidth, expandedWidth) : base(contractedWidth, expandedWidth)

View File

@ -1,23 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Testing;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
/// <summary> /// <summary>
/// Represents a <see cref="Container"/> with the ability to expand/contract when hovering the controls within it. /// Represents a <see cref="Container"/> with the ability to expand/contract on hover.
/// </summary> /// </summary>
/// <typeparam name="TControl">The type of UI control to lookup for hover expansion.</typeparam> public class ExpandingContainer : Container, IExpandingContainer
public class ExpandingControlContainer<TControl> : Container, IExpandingContainer
where TControl : class, IDrawable
{ {
private readonly float contractedWidth; private readonly float contractedWidth;
private readonly float expandedWidth; private readonly float expandedWidth;
@ -33,7 +29,7 @@ namespace osu.Game.Overlays
protected FillFlowContainer FillFlow { get; } protected FillFlowContainer FillFlow { get; }
protected ExpandingControlContainer(float contractedWidth, float expandedWidth) protected ExpandingContainer(float contractedWidth, float expandedWidth)
{ {
this.contractedWidth = contractedWidth; this.contractedWidth = contractedWidth;
this.expandedWidth = expandedWidth; this.expandedWidth = expandedWidth;
@ -57,7 +53,6 @@ namespace osu.Game.Overlays
} }
private ScheduledDelegate hoverExpandEvent; private ScheduledDelegate hoverExpandEvent;
private TControl activeControl;
protected override void LoadComplete() protected override void LoadComplete()
{ {
@ -69,61 +64,38 @@ namespace osu.Game.Overlays
}, true); }, true);
} }
protected override void Update()
{
base.Update();
// if the container was expanded from hovering over a control, we have to check per-frame whether we can contract it back.
// that's because contracting the container depends not only on whether it's no longer hovered,
// but also on whether the hovered control is no longer in a dragged state (if it was).
if (hoverExpandEvent != null && !IsHovered && (activeControl == null || !isControlActive(activeControl)))
{
hoverExpandEvent?.Cancel();
Expanded.Value = false;
hoverExpandEvent = null;
activeControl = null;
}
}
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {
queueExpandIfHovering(); updateHoverExpansion();
return true; return true;
} }
protected override bool OnMouseMove(MouseMoveEvent e) protected override bool OnMouseMove(MouseMoveEvent e)
{ {
queueExpandIfHovering(); updateHoverExpansion();
return base.OnMouseMove(e); return base.OnMouseMove(e);
} }
private void queueExpandIfHovering() protected override void OnHoverLost(HoverLostEvent e)
{ {
// if the same control is hovered or dragged, let the scheduled expand play out.. if (hoverExpandEvent != null)
if (activeControl != null && isControlActive(activeControl)) {
hoverExpandEvent?.Cancel();
hoverExpandEvent = null;
Expanded.Value = false;
return; return;
}
// ..otherwise check whether a new control is hovered, and if so, queue a new hover operation. base.OnHoverLost(e);
hoverExpandEvent?.Cancel();
// usually we wouldn't use ChildrenOfType in implementations, but this is the simplest way
// to handle cases like the editor where the controls may be nested within a child hierarchy.
activeControl = FillFlow.ChildrenOfType<TControl>().FirstOrDefault(isControlActive);
if (activeControl != null && !Expanded.Value)
hoverExpandEvent = Scheduler.AddDelayed(() => Expanded.Value = true, HoverExpansionDelay);
} }
/// <summary> private void updateHoverExpansion()
/// Whether the given control is currently active, by checking whether it's hovered or dragged.
/// </summary>
private bool isControlActive(TControl control)
{ {
if (control is IExpandable expandable) hoverExpandEvent?.Cancel();
return expandable.ShouldBeExpanded;
return control.IsHovered || control.IsDragged; if (IsHovered && !Expanded.Value)
hoverExpandEvent = Scheduler.AddDelayed(() => Expanded.Value = true, HoverExpansionDelay);
} }
} }
} }

View File

@ -3,7 +3,6 @@
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
@ -16,15 +15,5 @@ namespace osu.Game.Overlays
/// Whether this drawable is in an expanded state. /// Whether this drawable is in an expanded state.
/// </summary> /// </summary>
BindableBool Expanded { get; } BindableBool Expanded { get; }
/// <summary>
/// Whether this drawable should be/stay expanded by a parenting <see cref="IExpandingContainer"/>.
/// By default, this is <see langword="true"/> when this drawable is in a hovered or dragged state.
/// </summary>
/// <remarks>
/// This is defined for certain controls which may have a child handling dragging instead.
/// (e.g. <see cref="ExpandableSlider{T,TSlider}"/> in which dragging is handled by their underlying <see cref="OsuSliderBar{T}"/> control).
/// </remarks>
bool ShouldBeExpanded => IsHovered || IsDragged;
} }
} }

View File

@ -1,12 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Overlays
{
/// <summary>
/// An interface for UI controls with the ability to expand/contract.
/// </summary>
public interface IExpandableControl : IExpandable
{
}
}