1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-23 10:07:25 +08:00
osu-lazer/osu.Game/Screens/Select/Carousel/DrawableCarouselItem.cs
Dean Herbert c3e3b2019d Reduce overhead of ApplyState by tracking previous values
Even with pooling applied, there are overheads involved with transforms
when quickly cycling through the carousel.

The main goal here is to reduce the transforms in cases the reuse is
still in the same state. Avoiding firing `FadeIn` and `FadeOut` are the
main areas of saving.
2022-01-31 14:46:20 +09:00

188 lines
5.2 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 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(float headerHeight = MAX_HEIGHT)
{
RelativeSizeAxes = Axes.X;
InternalChildren = new Drawable[]
{
MovementContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
Header = new CarouselHeader
{
Height = headerHeight,
},
Content = new Container
{
RelativeSizeAxes = Axes.Both,
Y = headerHeight,
}
}
},
};
}
public void SetMultiplicativeAlpha(float alpha) => Header.BorderContainer.Alpha = alpha;
protected override void LoadComplete()
{
base.LoadComplete();
UpdateItem();
}
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);
private CarouselItemState? lastAppliedState;
protected virtual void ApplyState()
{
Debug.Assert(Item != null);
if (lastAppliedState != Item.State.Value)
{
lastAppliedState = Item.State.Value;
// 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;
switch (lastAppliedState)
{
case CarouselItemState.NotSelected:
Deselected();
break;
case CarouselItemState.Selected:
Selected();
break;
}
}
if (!Item.Visible)
Hide();
else
Show();
}
private bool isVisible = true;
public override void Show()
{
if (isVisible)
return;
isVisible = true;
this.FadeIn(250);
}
public override void Hide()
{
if (!isVisible)
return;
isVisible = false;
this.FadeOut(300, Easing.OutQuint);
}
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;
}
}
}