1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 12:42:54 +08:00
osu-lazer/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs
Dean Herbert 520d2d6cfa Fix beatmap carousel panels accepting input while marked as not-visible
This is an issue as carousel panels manage their own animated state. If
they are marked as not-visible (done at a higher level, from filtering
or update pathways) but clicked while fading out, they will animate back
to a visible state but not be marked as visible.

No tests for this one as it's probably not worthwhile to test (and hard
to do so). Manual testing can be done with the following patch:

```diff
diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs
b/osu.Game/Screens/Select/BeatmapCarousel.cs
index c3d340ac61..3372242acc 100644
--- a/osu.Game/Screens/Select/BeatmapCarousel.cs
+++ b/osu.Game/Screens/Select/BeatmapCarousel.cs
@@ -255,7 +255,7 @@ private void
beatmapSetsChanged(IRealmCollection<BeatmapSetInfo> sender, ChangeS
             }

             foreach (int i in changes.NewModifiedIndices)
-                UpdateBeatmapSet(sender[i].Detach());
+                Scheduler.AddDelayed(() =>
                 UpdateBeatmapSet(sender[i].Detach()), 100, true);

             foreach (int i in changes.InsertedIndices)
                 UpdateBeatmapSet(sender[i].Detach());

```

- Enter gameplay and adjust beatmap offset then return to song select
  and click the flashing panel.
OR
- Enter editor and save then return to song select and click the
  flashing panel.

Closes https://github.com/ppy/osu/discussions/17171.
2022-03-09 16:08:52 +09:00

168 lines
4.7 KiB
C#

// 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.
using System.Diagnostics;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Input.Events;
using osuTK;
namespace osu.Game.Screens.Select.Carousel
{
public abstract class DrawableCarouselItem : PoolableDrawable
{
public const float MAX_HEIGHT = 80;
public override bool IsPresent => base.IsPresent || Item?.Visible == true;
public override bool HandlePositionalInput => Item?.Visible == true;
public override bool PropagatePositionalInputSubTree => Item?.Visible == true;
public readonly CarouselHeader Header;
/// <summary>
/// Optional content which sits below the header.
/// </summary>
protected readonly Container<Drawable> Content;
protected readonly Container MovementContainer;
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
Header.ReceivePositionalInputAt(screenSpacePos);
private CarouselItem item;
public CarouselItem Item
{
get => item;
set
{
if (item == value)
return;
if (item != null)
{
item.Filtered.ValueChanged -= onStateChange;
item.State.ValueChanged -= onStateChange;
Header.State.UnbindFrom(item.State);
if (item is CarouselGroup group)
{
foreach (var c in group.Children)
c.Filtered.ValueChanged -= onStateChange;
}
}
item = value;
if (IsLoaded)
UpdateItem();
}
}
protected DrawableCarouselItem()
{
RelativeSizeAxes = Axes.X;
Alpha = 0;
InternalChildren = new Drawable[]
{
MovementContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
Header = new CarouselHeader(),
Content = new Container
{
RelativeSizeAxes = Axes.Both,
}
}
},
};
}
public void SetMultiplicativeAlpha(float alpha) => Header.BorderContainer.Alpha = alpha;
protected override void LoadComplete()
{
base.LoadComplete();
UpdateItem();
}
protected override void Update()
{
base.Update();
Content.Y = Header.Height;
}
protected virtual void UpdateItem()
{
if (item == null)
return;
Scheduler.AddOnce(ApplyState);
Item.Filtered.ValueChanged += onStateChange;
Item.State.ValueChanged += onStateChange;
Header.State.BindTo(Item.State);
if (Item is CarouselGroup group)
{
foreach (var c in group.Children)
c.Filtered.ValueChanged += onStateChange;
}
}
private void onStateChange(ValueChangedEvent<CarouselItemState> obj) => Scheduler.AddOnce(ApplyState);
private void onStateChange(ValueChangedEvent<bool> _) => Scheduler.AddOnce(ApplyState);
protected virtual void ApplyState()
{
// Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead.
// Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away.
Height = Item.TotalHeight;
Debug.Assert(Item != null);
switch (Item.State.Value)
{
case CarouselItemState.NotSelected:
Deselected();
break;
case CarouselItemState.Selected:
Selected();
break;
}
if (!Item.Visible)
this.FadeOut(300, Easing.OutQuint);
else
this.FadeIn(250);
}
protected virtual void Selected()
{
Debug.Assert(Item != null);
}
protected virtual void Deselected()
{
}
protected override bool OnClick(ClickEvent e)
{
Item.State.Value = CarouselItemState.Selected;
return true;
}
}
}