1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-18 23:50:42 +08:00
Files
osu-lazer/osu.Game/Screens/SelectV2/Panel.cs
T

339 lines
11 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Pooling;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Carousel;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.SelectV2
{
public abstract partial class Panel : PoolableDrawable, ICarouselPanel, IHasContextMenu
{
private const float corner_radius = 10;
private const float active_x_offset = 25f;
protected const float DURATION = 400;
protected float PanelXOffset { get; init; }
private Box backgroundBorder = null!;
private Box backgroundGradient = null!;
private Container backgroundLayerHorizontalPadding = null!;
private Container backgroundContainer = null!;
private Container iconContainer = null!;
private Box activationFlash = null!;
private Box hoverLayer = null!;
private Box keyboardSelectionLayer = null!;
private Box selectionLayer = null!;
public Container TopLevelContent { get; private set; } = null!;
protected Container Content { get; private set; } = null!;
public Drawable Background
{
set => backgroundContainer.Child = value;
}
public Drawable Icon
{
set => iconContainer.Child = value;
}
private Color4? accentColour;
public Color4? AccentColour
{
get => accentColour;
set
{
accentColour = value;
updateAccentColour();
}
}
// content is offset by PanelXOffset, make sure we only handle input at the actual visible
// offset region.
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
TopLevelContent.ReceivePositionalInputAt(screenSpacePos);
[Resolved]
private BeatmapCarousel? carousel { get; set; }
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider, OsuColour colours)
{
Anchor = Anchor.TopRight;
Origin = Anchor.TopRight;
RelativeSizeAxes = Axes.X;
Height = CarouselItem.DEFAULT_HEIGHT;
InternalChild = TopLevelContent = new Container
{
Masking = true,
CornerRadius = corner_radius,
RelativeSizeAxes = Axes.Both,
X = corner_radius,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Hollow = true,
Radius = 2,
},
Children = new Drawable[]
{
new BufferedContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
backgroundBorder = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
backgroundLayerHorizontalPadding = new Container
{
RelativeSizeAxes = Axes.Both,
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
CornerRadius = corner_radius,
Children = new Drawable[]
{
backgroundGradient = new Box
{
RelativeSizeAxes = Axes.Both,
},
backgroundContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
}
},
}
},
},
iconContainer = new Container
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
},
Content = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = PanelXOffset + corner_radius },
},
hoverLayer = new Box
{
Alpha = 0,
Colour = colours.Blue.Opacity(0.1f),
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
},
selectionLayer = new Box
{
Alpha = 0,
RelativeSizeAxes = Axes.Both,
Width = 0.6f,
Blending = BlendingParameters.Additive,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
keyboardSelectionLayer = new Box
{
Alpha = 0,
Colour = colourProvider.Highlight1.Opacity(0.1f),
Blending = BlendingParameters.Additive,
RelativeSizeAxes = Axes.Both,
},
activationFlash = new Box
{
Colour = Color4.White.Opacity(0.4f),
Blending = BlendingParameters.Additive,
Alpha = 0f,
RelativeSizeAxes = Axes.Both,
},
new HoverSounds(),
}
};
backgroundGradient.Colour = ColourInfo.GradientHorizontal(colourProvider.Background3, colourProvider.Background4);
}
protected override void LoadComplete()
{
base.LoadComplete();
Expanded.BindValueChanged(_ =>
{
updateSelectedState();
updateXOffset();
});
Selected.BindValueChanged(_ =>
{
updateSelectedState();
updateXOffset();
}, true);
KeyboardSelected.BindValueChanged(selected =>
{
if (selected.NewValue)
keyboardSelectionLayer.FadeIn(100, Easing.OutQuint);
else
keyboardSelectionLayer.FadeOut(1000, Easing.OutQuint);
updateXOffset();
}, true);
}
protected override void PrepareForUse()
{
base.PrepareForUse();
updateAccentColour();
updateXOffset();
this.FadeIn(DURATION, Easing.OutQuint);
}
protected override void FreeAfterUse()
{
base.FreeAfterUse();
Hide();
// Important to set this to null to handle reuse scenarios correctly, see `Item` implementation.
item = null;
}
protected override bool OnClick(ClickEvent e)
{
carousel?.Activate(Item!);
return true;
}
private void updateAccentColour()
{
var backgroundColour = accentColour ?? Color4.White;
backgroundBorder.Colour = backgroundColour;
selectionLayer.Colour = ColourInfo.GradientHorizontal(backgroundColour.Opacity(0), backgroundColour.Opacity(0.5f));
updateSelectedState(animated: false);
}
private void updateSelectedState(bool animated = true)
{
bool selectedOrExpanded = Expanded.Value || Selected.Value;
var edgeEffectColour = accentColour ?? Color4Extensions.FromHex(@"4EBFFF");
TopLevelContent.FadeEdgeEffectTo(selectedOrExpanded ? edgeEffectColour.Opacity(0.8f) : Color4.Black.Opacity(0.4f), animated ? DURATION : 0, Easing.OutQuint);
if (selectedOrExpanded)
selectionLayer.FadeIn(100, Easing.OutQuint);
else
selectionLayer.FadeOut(200, Easing.OutQuint);
}
private void updateXOffset()
{
float x = PanelXOffset + corner_radius;
if (!Expanded.Value && !Selected.Value)
{
if (this is PanelBeatmap)
x += active_x_offset * 2;
else
x += active_x_offset * 4;
}
if (!KeyboardSelected.Value)
x += active_x_offset;
TopLevelContent.MoveToX(x, DURATION, Easing.OutQuint);
}
protected override bool OnHover(HoverEvent e)
{
hoverLayer.FadeIn(100, Easing.OutQuint);
return true;
}
protected override void OnHoverLost(HoverLostEvent e)
{
hoverLayer.FadeOut(1000, Easing.OutQuint);
base.OnHoverLost(e);
}
protected override void Update()
{
base.Update();
Content.Padding = Content.Padding with { Left = iconContainer.DrawWidth };
backgroundLayerHorizontalPadding.Padding = new MarginPadding { Left = iconContainer.DrawWidth };
}
public abstract MenuItem[]? ContextMenuItems { get; }
#region ICarouselPanel
private CarouselItem? item;
public CarouselItem? Item
{
get => item;
set
{
if (ReferenceEquals(item, value))
return;
// If a new item is set and we already have an item, this is a case of reuse.
// To keep things simple, assume that we need to do a full refresh.
//
// In the future, this could be more contextual and check whether the associated model has actually changed.
if (item != null && value != null)
{
item = value;
PrepareForUse();
}
else
item = value;
}
}
public BindableBool Selected { get; } = new BindableBool();
public BindableBool Expanded { get; } = new BindableBool();
public BindableBool KeyboardSelected { get; } = new BindableBool();
public double DrawYPosition { get; set; }
public virtual void Activated()
{
activationFlash.FadeOutFromOne(1000, Easing.OutQuint);
}
#endregion
}
}