mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 12:17:26 +08:00
Merge pull request #26723 from EVAST9919/progress-bar-improvements
Reduce allocation overhead of song progress bars
This commit is contained in:
commit
8922190055
@ -114,12 +114,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
protected override void UpdateProgress(double progress, bool isIntro)
|
protected override void UpdateProgress(double progress, bool isIntro)
|
||||||
{
|
{
|
||||||
bar.TrackTime = GameplayClock.CurrentTime;
|
bar.Progress = isIntro ? 0 : progress;
|
||||||
|
|
||||||
if (isIntro)
|
|
||||||
bar.CurrentTime = 0;
|
|
||||||
else
|
|
||||||
bar.CurrentTime = FrameStableClock.CurrentTime;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,96 +3,59 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Threading;
|
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play.HUD
|
namespace osu.Game.Screens.Play.HUD
|
||||||
{
|
{
|
||||||
public partial class ArgonSongProgressBar : SliderBar<double>
|
public partial class ArgonSongProgressBar : SongProgressBar
|
||||||
{
|
{
|
||||||
public Action<double>? OnSeek { get; set; }
|
|
||||||
|
|
||||||
// Parent will handle restricting the area of valid input.
|
// Parent will handle restricting the area of valid input.
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||||
|
|
||||||
private readonly float barHeight;
|
private readonly float barHeight;
|
||||||
|
|
||||||
private readonly RoundedBar playfieldBar;
|
private readonly RoundedBar playfieldBar;
|
||||||
private readonly RoundedBar catchupBar;
|
private readonly RoundedBar audioBar;
|
||||||
|
|
||||||
private readonly Box background;
|
private readonly Box background;
|
||||||
|
|
||||||
private readonly ColourInfo mainColour;
|
private readonly ColourInfo mainColour;
|
||||||
private ColourInfo catchUpColour;
|
private ColourInfo catchUpColour;
|
||||||
|
|
||||||
public double StartTime
|
public double Progress { get; set; }
|
||||||
{
|
|
||||||
private get => CurrentNumber.MinValue;
|
|
||||||
set => CurrentNumber.MinValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double EndTime
|
private double trackTime => (EndTime - StartTime) * Progress;
|
||||||
{
|
|
||||||
private get => CurrentNumber.MaxValue;
|
|
||||||
set => CurrentNumber.MaxValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double CurrentTime
|
|
||||||
{
|
|
||||||
private get => CurrentNumber.Value;
|
|
||||||
set => CurrentNumber.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double TrackTime
|
|
||||||
{
|
|
||||||
private get => currentTrackTime.Value;
|
|
||||||
set => currentTrackTime.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private double length => EndTime - StartTime;
|
|
||||||
|
|
||||||
private readonly BindableNumber<double> currentTrackTime;
|
|
||||||
|
|
||||||
public bool Interactive { get; set; }
|
|
||||||
|
|
||||||
public ArgonSongProgressBar(float barHeight)
|
public ArgonSongProgressBar(float barHeight)
|
||||||
{
|
{
|
||||||
currentTrackTime = new BindableDouble();
|
|
||||||
setupAlternateValue();
|
|
||||||
|
|
||||||
StartTime = 0;
|
|
||||||
EndTime = 1;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = this.barHeight = barHeight;
|
Height = this.barHeight = barHeight;
|
||||||
|
|
||||||
CornerRadius = 5;
|
CornerRadius = 5;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
|
|
||||||
Children = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
background = new Box
|
background = new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
Colour = OsuColour.Gray(0.2f),
|
Colour = OsuColour.Gray(0.2f),
|
||||||
|
Depth = float.MaxValue,
|
||||||
},
|
},
|
||||||
catchupBar = new RoundedBar
|
audioBar = new RoundedBar
|
||||||
{
|
{
|
||||||
Name = "Audio bar",
|
Name = "Audio bar",
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.BottomLeft,
|
Origin = Anchor.BottomLeft,
|
||||||
CornerRadius = 5,
|
CornerRadius = 5,
|
||||||
AlwaysPresent = true,
|
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
playfieldBar = new RoundedBar
|
playfieldBar = new RoundedBar
|
||||||
@ -107,24 +70,6 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupAlternateValue()
|
|
||||||
{
|
|
||||||
CurrentNumber.MaxValueChanged += v => currentTrackTime.MaxValue = v;
|
|
||||||
CurrentNumber.MinValueChanged += v => currentTrackTime.MinValue = v;
|
|
||||||
CurrentNumber.PrecisionChanged += v => currentTrackTime.Precision = v;
|
|
||||||
}
|
|
||||||
|
|
||||||
private float normalizedReference
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (EndTime - StartTime == 0)
|
|
||||||
return 1;
|
|
||||||
|
|
||||||
return (float)((TrackTime - StartTime) / length);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
@ -153,47 +98,28 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateValue(float value)
|
|
||||||
{
|
|
||||||
// Handled in Update
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
playfieldBar.Length = (float)Interpolation.Lerp(playfieldBar.Length, NormalizedValue, Math.Clamp(Time.Elapsed / 40, 0, 1));
|
playfieldBar.Length = (float)Interpolation.Lerp(playfieldBar.Length, Progress, Math.Clamp(Time.Elapsed / 40, 0, 1));
|
||||||
catchupBar.Length = (float)Interpolation.Lerp(catchupBar.Length, normalizedReference, Math.Clamp(Time.Elapsed / 40, 0, 1));
|
audioBar.Length = (float)Interpolation.Lerp(audioBar.Length, AudioProgress, Math.Clamp(Time.Elapsed / 40, 0, 1));
|
||||||
|
|
||||||
if (TrackTime < CurrentTime)
|
if (trackTime > AudioTime)
|
||||||
ChangeChildDepth(catchupBar, -1);
|
ChangeInternalChildDepth(audioBar, -1);
|
||||||
else
|
else
|
||||||
ChangeChildDepth(catchupBar, 0);
|
ChangeInternalChildDepth(audioBar, 1);
|
||||||
|
|
||||||
float timeDelta = (float)(Math.Abs(CurrentTime - TrackTime));
|
float timeDelta = (float)Math.Abs(AudioTime - trackTime);
|
||||||
|
|
||||||
const float colour_transition_threshold = 20000;
|
const float colour_transition_threshold = 20000;
|
||||||
|
|
||||||
catchupBar.AccentColour = Interpolation.ValueAt(
|
audioBar.AccentColour = Interpolation.ValueAt(
|
||||||
Math.Min(timeDelta, colour_transition_threshold),
|
Math.Min(timeDelta, colour_transition_threshold),
|
||||||
mainColour,
|
mainColour,
|
||||||
catchUpColour,
|
catchUpColour,
|
||||||
0, colour_transition_threshold,
|
0, colour_transition_threshold,
|
||||||
Easing.OutQuint);
|
Easing.OutQuint);
|
||||||
|
|
||||||
catchupBar.Alpha = Math.Max(1, catchupBar.Length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ScheduledDelegate? scheduledSeek;
|
|
||||||
|
|
||||||
protected override void OnUserChange(double value)
|
|
||||||
{
|
|
||||||
scheduledSeek?.Cancel();
|
|
||||||
scheduledSeek = Schedule(() =>
|
|
||||||
{
|
|
||||||
if (Interactive)
|
|
||||||
OnSeek?.Invoke(value);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class RoundedBar : Container
|
private partial class RoundedBar : Container
|
||||||
|
@ -98,12 +98,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
protected override void UpdateProgress(double progress, bool isIntro)
|
protected override void UpdateProgress(double progress, bool isIntro)
|
||||||
{
|
{
|
||||||
bar.CurrentTime = GameplayClock.CurrentTime;
|
graph.Progress = isIntro ? 0 : (int)(graph.ColumnCount * progress);
|
||||||
|
|
||||||
if (isIntro)
|
|
||||||
graph.Progress = 0;
|
|
||||||
else
|
|
||||||
graph.Progress = (int)(graph.ColumnCount * progress);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -7,71 +7,27 @@ using osuTK.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Framework.Threading;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play.HUD
|
namespace osu.Game.Screens.Play.HUD
|
||||||
{
|
{
|
||||||
public partial class DefaultSongProgressBar : SliderBar<double>
|
public partial class DefaultSongProgressBar : SongProgressBar
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// Action which is invoked when a seek is requested, with the proposed millisecond value for the seek operation.
|
|
||||||
/// </summary>
|
|
||||||
public Action<double>? OnSeek { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Whether the progress bar should allow interaction, ie. to perform seek operations.
|
|
||||||
/// </summary>
|
|
||||||
public bool Interactive
|
|
||||||
{
|
|
||||||
get => showHandle;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value == showHandle)
|
|
||||||
return;
|
|
||||||
|
|
||||||
showHandle = value;
|
|
||||||
|
|
||||||
handleBase.FadeTo(showHandle ? 1 : 0, 200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Color4 FillColour
|
public Color4 FillColour
|
||||||
{
|
{
|
||||||
set => fill.Colour = value;
|
set => fill.Colour = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public double StartTime
|
|
||||||
{
|
|
||||||
set => CurrentNumber.MinValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double EndTime
|
|
||||||
{
|
|
||||||
set => CurrentNumber.MaxValue = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public double CurrentTime
|
|
||||||
{
|
|
||||||
set => CurrentNumber.Value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Box fill;
|
private readonly Box fill;
|
||||||
private readonly Container handleBase;
|
private readonly Container handleBase;
|
||||||
private readonly Container handleContainer;
|
private readonly Container handleContainer;
|
||||||
|
|
||||||
private bool showHandle;
|
|
||||||
|
|
||||||
public DefaultSongProgressBar(float barHeight, float handleBarHeight, Vector2 handleSize)
|
public DefaultSongProgressBar(float barHeight, float handleBarHeight, Vector2 handleSize)
|
||||||
{
|
{
|
||||||
CurrentNumber.MinValue = 0;
|
|
||||||
CurrentNumber.MaxValue = 1;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = barHeight + handleBarHeight + handleSize.Y;
|
Height = barHeight + handleBarHeight + handleSize.Y;
|
||||||
|
|
||||||
Children = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
@ -130,9 +86,14 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateValue(float value)
|
public override bool Interactive
|
||||||
{
|
{
|
||||||
// handled in update
|
get => base.Interactive;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
base.Interactive = value;
|
||||||
|
handleBase.FadeTo(value ? 1 : 0, 200);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -140,22 +101,10 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
handleBase.Height = Height - handleContainer.Height;
|
handleBase.Height = Height - handleContainer.Height;
|
||||||
float newX = (float)Interpolation.Lerp(handleBase.X, NormalizedValue * UsableWidth, Math.Clamp(Time.Elapsed / 40, 0, 1));
|
float newX = (float)Interpolation.Lerp(handleBase.X, AudioProgress * DrawWidth, Math.Clamp(Time.Elapsed / 40, 0, 1));
|
||||||
|
|
||||||
fill.Width = newX;
|
fill.Width = newX;
|
||||||
handleBase.X = newX;
|
handleBase.X = newX;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ScheduledDelegate? scheduledSeek;
|
|
||||||
|
|
||||||
protected override void OnUserChange(double value)
|
|
||||||
{
|
|
||||||
scheduledSeek?.Cancel();
|
|
||||||
scheduledSeek = Schedule(() =>
|
|
||||||
{
|
|
||||||
if (showHandle)
|
|
||||||
OnSeek?.Invoke(value);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// 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;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -70,7 +71,13 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
|
|
||||||
protected double LastHitTime { get; private set; }
|
protected double LastHitTime { get; private set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Called every update frame with current progress information.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="progress">Current (visual) progress through the beatmap (0..1).</param>
|
||||||
|
/// <param name="isIntro">If <c>true</c>, progress is (0..1) through the intro.</param>
|
||||||
protected abstract void UpdateProgress(double progress, bool isIntro);
|
protected abstract void UpdateProgress(double progress, bool isIntro);
|
||||||
|
|
||||||
protected virtual void UpdateObjects(IEnumerable<HitObject> objects) { }
|
protected virtual void UpdateObjects(IEnumerable<HitObject> objects) { }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -96,7 +103,7 @@ namespace osu.Game.Screens.Play.HUD
|
|||||||
if (objects == null)
|
if (objects == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
double currentTime = FrameStableClock.CurrentTime;
|
double currentTime = Math.Min(FrameStableClock.CurrentTime, LastHitTime);
|
||||||
|
|
||||||
bool isInIntro = currentTime < FirstHitTime;
|
bool isInIntro = currentTime < FirstHitTime;
|
||||||
|
|
||||||
|
97
osu.Game/Screens/Play/HUD/SongProgressBar.cs
Normal file
97
osu.Game/Screens/Play/HUD/SongProgressBar.cs
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
// 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;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Play.HUD
|
||||||
|
{
|
||||||
|
public abstract partial class SongProgressBar : CompositeDrawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The current seek position of the audio, on a (0..1) range.
|
||||||
|
/// This is generally the seek target, which will eventually match the gameplay clock when it catches up.
|
||||||
|
/// </summary>
|
||||||
|
protected double AudioProgress => length == 0 ? 1 : AudioTime / length;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current (non-frame-stable) audio time.
|
||||||
|
/// </summary>
|
||||||
|
protected double AudioTime => Math.Clamp(GameplayClock.CurrentTime - StartTime, 0.0, length);
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected IGameplayClock GameplayClock { get; private set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Action which is invoked when a seek is requested, with the proposed millisecond value for the seek operation.
|
||||||
|
/// </summary>
|
||||||
|
public Action<double>? OnSeek { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the progress bar should allow interaction, ie. to perform seek operations.
|
||||||
|
/// </summary>
|
||||||
|
public virtual bool Interactive { get; set; }
|
||||||
|
|
||||||
|
public double StartTime { get; set; }
|
||||||
|
|
||||||
|
public double EndTime { get; set; } = 1.0;
|
||||||
|
|
||||||
|
private double length => EndTime - StartTime;
|
||||||
|
|
||||||
|
private bool handleClick;
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(MouseDownEvent e)
|
||||||
|
{
|
||||||
|
handleClick = true;
|
||||||
|
return base.OnMouseDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnClick(ClickEvent e)
|
||||||
|
{
|
||||||
|
if (handleClick)
|
||||||
|
handleMouseInput(e);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDrag(DragEvent e)
|
||||||
|
{
|
||||||
|
handleMouseInput(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
|
{
|
||||||
|
Vector2 posDiff = e.MouseDownPosition - e.MousePosition;
|
||||||
|
|
||||||
|
if (Math.Abs(posDiff.X) < Math.Abs(posDiff.Y))
|
||||||
|
{
|
||||||
|
handleClick = false;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleMouseInput(e);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleMouseInput(UIEvent e)
|
||||||
|
{
|
||||||
|
if (!Interactive)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double relativeX = Math.Clamp(ToLocalSpace(e.ScreenSpaceMousePosition).X / DrawWidth, 0, 1);
|
||||||
|
onUserChange(StartTime + (EndTime - StartTime) * relativeX);
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScheduledDelegate? scheduledSeek;
|
||||||
|
|
||||||
|
private void onUserChange(double value)
|
||||||
|
{
|
||||||
|
scheduledSeek?.Cancel();
|
||||||
|
scheduledSeek = Schedule(() => OnSeek?.Invoke(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user