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

Merge pull request #12842 from peppy/song-progress-skinnable

Add layout customisation support for `SongProgress`
This commit is contained in:
Dan Balasescu 2021-05-17 20:15:01 +09:00 committed by GitHub
commit ea4dfd3374
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 106 additions and 120 deletions

View File

@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.Gameplay
private void seekToBreak(int breakIndex)
{
AddStep($"seek to break {breakIndex}", () => Player.GameplayClockContainer.Seek(destBreak().StartTime));
AddUntilStep("wait for seek to complete", () => Player.HUDOverlay.Progress.ReferenceClock.CurrentTime >= destBreak().StartTime);
AddUntilStep("wait for seek to complete", () => Player.DrawableRuleset.FrameStableClock.CurrentTime >= destBreak().StartTime);
BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex);
}

View File

@ -35,8 +35,12 @@ namespace osu.Game.Screens.Play
/// </summary>
public float TopScoringElementsHeight { get; private set; }
/// <summary>
/// The total height of all the bottom of screen scoring elements.
/// </summary>
public float BottomScoringElementsHeight { get; private set; }
public readonly KeyCounterDisplay KeyCounter;
public readonly SongProgress Progress;
public readonly ModDisplay ModDisplay;
public readonly HoldForMenuButton HoldToQuit;
public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
@ -59,8 +63,6 @@ namespace osu.Game.Screens.Play
private static bool hasShownNotificationOnce;
public Action<double> RequestSeek;
private readonly FillFlowContainer bottomRightElements;
private readonly FillFlowContainer topRightElements;
@ -85,45 +87,22 @@ namespace osu.Game.Screens.Play
visibilityContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Child = new GridContainer
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Content = new[]
mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents)
{
new Drawable[]
RelativeSizeAxes = Axes.Both,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents)
{
RelativeSizeAxes = Axes.Both,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
// still need to be migrated; a bit more involved.
new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows),
}
},
}
},
},
new Drawable[]
{
Progress = CreateProgress(),
// still need to be migrated; a bit more involved.
new HitErrorDisplay(this.drawableRuleset?.FirstAvailableHitWindows),
}
},
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize)
}
},
}
},
topRightElements = new FillFlowContainer
{
@ -164,10 +143,6 @@ namespace osu.Game.Screens.Play
if (drawableRuleset != null)
{
BindDrawableRuleset(drawableRuleset);
Progress.Objects = drawableRuleset.Objects;
Progress.RequestSeek = time => RequestSeek(time);
Progress.ReferenceClock = drawableRuleset.FrameStableClock;
}
ModDisplay.Current.Value = mods;
@ -206,26 +181,43 @@ namespace osu.Game.Screens.Play
{
base.Update();
Vector2 lowestScreenSpace = Vector2.Zero;
Vector2? lowestTopScreenSpace = null;
Vector2? highestBottomScreenSpace = null;
// LINQ cast can be removed when IDrawable interface includes Anchor / RelativeSizeAxes.
foreach (var element in mainComponents.Components.Cast<Drawable>())
{
// for now align top-right components with the bottom-edge of the lowest top-anchored hud element.
if (!element.Anchor.HasFlagFast(Anchor.TopRight) && !element.RelativeSizeAxes.HasFlagFast(Axes.X))
if (!element.RelativeSizeAxes.HasFlagFast(Axes.X))
continue;
// health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area.
if (element is LegacyHealthDisplay)
continue;
if (element.Anchor.HasFlagFast(Anchor.TopRight))
{
// health bars are excluded for the sake of hacky legacy skins which extend the health bar to take up the full screen area.
if (element is LegacyHealthDisplay)
continue;
var bottomRight = element.ScreenSpaceDrawQuad.BottomRight;
if (bottomRight.Y > lowestScreenSpace.Y)
lowestScreenSpace = bottomRight;
var bottomRight = element.ScreenSpaceDrawQuad.BottomRight;
if (lowestTopScreenSpace == null || bottomRight.Y > lowestTopScreenSpace.Value.Y)
lowestTopScreenSpace = bottomRight;
}
else if (element.Anchor.HasFlagFast(Anchor.y2))
{
var topLeft = element.ScreenSpaceDrawQuad.TopLeft;
if (highestBottomScreenSpace == null || topLeft.Y < highestBottomScreenSpace.Value.Y)
highestBottomScreenSpace = topLeft;
}
}
topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestScreenSpace).Y;
bottomRightElements.Y = -Progress.Height;
if (lowestTopScreenSpace.HasValue)
topRightElements.Y = TopScoringElementsHeight = ToLocalSpace(lowestTopScreenSpace.Value).Y;
else
topRightElements.Y = 0;
if (highestBottomScreenSpace.HasValue)
bottomRightElements.Y = BottomScoringElementsHeight = -(DrawHeight - ToLocalSpace(highestBottomScreenSpace.Value).Y);
else
bottomRightElements.Y = 0;
}
private void updateVisibility()
@ -281,8 +273,6 @@ namespace osu.Game.Screens.Play
(drawableRuleset as ICanAttachKeyCounter)?.Attach(KeyCounter);
replayLoaded.BindTo(drawableRuleset.HasReplayLoaded);
Progress.BindDrawableRuleset(drawableRuleset);
}
protected FailingLayer CreateFailingLayer() => new FailingLayer
@ -296,13 +286,6 @@ namespace osu.Game.Screens.Play
Origin = Anchor.BottomRight,
};
protected SongProgress CreateProgress() => new SongProgress
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
};
protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton
{
Anchor = Anchor.BottomRight,

View File

@ -201,6 +201,7 @@ namespace osu.Game.Screens.Play
LocalUserPlaying.BindTo(osuGame.LocalUserPlaying);
DrawableRuleset = ruleset.CreateDrawableRulesetWith(playableBeatmap, Mods.Value);
dependencies.CacheAs(DrawableRuleset);
ScoreProcessor = ruleset.CreateScoreProcessor();
ScoreProcessor.ApplyBeatmap(playableBeatmap);
@ -360,11 +361,6 @@ namespace osu.Game.Screens.Play
AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded },
IsCounting = false
},
RequestSeek = time =>
{
GameplayClockContainer.Seek(time);
GameplayClockContainer.Start();
},
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
@ -569,6 +565,12 @@ namespace osu.Game.Screens.Play
updateSampleDisabledState();
}
/// <summary>
/// Seek to a specific time in gameplay.
/// </summary>
/// <param name="time">The destination time to seek to.</param>
public void Seek(double time) => GameplayClockContainer.Seek(time);
/// <summary>
/// Restart gameplay via a parent <see cref="PlayerLoader"/>.
/// <remarks>This can be called from a child screen in order to trigger the restart process.</remarks>

View File

@ -14,10 +14,11 @@ using osu.Framework.Timing;
using osu.Game.Configuration;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play
{
public class SongProgress : OverlayContainer
public class SongProgress : OverlayContainer, ISkinnableDrawable
{
private const int info_height = 20;
private const int bottom_bar_height = 5;
@ -39,9 +40,6 @@ namespace osu.Game.Screens.Play
public readonly Bindable<bool> ShowGraph = new Bindable<bool>();
//TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list).
private double lastHitTime => objects.Last().GetEndTime() + 1;
public override bool HandleNonPositionalInput => AllowSeeking.Value;
public override bool HandlePositionalInput => AllowSeeking.Value;
@ -49,6 +47,9 @@ namespace osu.Game.Screens.Play
private double firstHitTime => objects.First().StartTime;
//TODO: this isn't always correct (consider mania where a non-last object may last for longer than the last in the list).
private double lastHitTime => objects.Last().GetEndTime() + 1;
private IEnumerable<HitObject> objects;
public IEnumerable<HitObject> Objects
@ -65,51 +66,58 @@ namespace osu.Game.Screens.Play
}
}
public IClock ReferenceClock;
[Resolved(canBeNull: true)]
private Player player { get; set; }
private IClock gameplayClock;
[Resolved(canBeNull: true)]
private GameplayClock gameplayClock { get; set; }
private IClock referenceClock;
public SongProgress()
{
RelativeSizeAxes = Axes.X;
Anchor = Anchor.BottomRight;
Origin = Anchor.BottomRight;
Children = new Drawable[]
{
new SongProgressDisplay
info = new SongProgressInfo
{
Children = new Drawable[]
{
info = new SongProgressInfo
{
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = info_height,
},
graph = new SongProgressGraph
{
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Height = graph_height,
Margin = new MarginPadding { Bottom = bottom_bar_height },
},
bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
OnSeek = time => RequestSeek?.Invoke(time),
},
}
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = info_height,
},
graph = new SongProgressGraph
{
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Height = graph_height,
Margin = new MarginPadding { Bottom = bottom_bar_height },
},
bar = new SongProgressBar(bottom_bar_height, graph_height, handle_size)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
OnSeek = time => player?.Seek(time),
},
};
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, GameplayClock clock, OsuConfigManager config)
private void load(OsuColour colours, OsuConfigManager config, DrawableRuleset drawableRuleset)
{
base.LoadComplete();
if (clock != null)
gameplayClock = clock;
if (drawableRuleset != null)
{
AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded);
referenceClock = drawableRuleset.FrameStableClock;
Objects = drawableRuleset.Objects;
}
config.BindWith(OsuSetting.ShowProgressGraph, ShowGraph);
@ -124,11 +132,6 @@ namespace osu.Game.Screens.Play
ShowGraph.BindValueChanged(_ => updateGraphVisibility(), true);
}
public void BindDrawableRuleset(DrawableRuleset drawableRuleset)
{
AllowSeeking.BindTo(drawableRuleset.HasReplayLoaded);
}
protected override void PopIn()
{
this.FadeIn(500, Easing.OutQuint);
@ -147,7 +150,7 @@ namespace osu.Game.Screens.Play
return;
double gameplayTime = gameplayClock?.CurrentTime ?? Time.Current;
double frameStableTime = ReferenceClock?.CurrentTime ?? gameplayTime;
double frameStableTime = referenceClock?.CurrentTime ?? gameplayTime;
double progress = Math.Min(1, (frameStableTime - firstHitTime) / (lastHitTime - firstHitTime));
@ -179,19 +182,5 @@ namespace osu.Game.Screens.Play
float finalMargin = bottom_bar_height + (AllowSeeking.Value ? handle_size.Y : 0) + (ShowGraph.Value ? graph_height : 0);
info.TransformTo(nameof(info.Margin), new MarginPadding { Bottom = finalMargin }, transition_duration, Easing.In);
}
public class SongProgressDisplay : Container
{
public SongProgressDisplay()
{
// TODO: move actual implementation into this.
// exists for skin customisation purposes (interface should be added to this container).
Masking = true;
RelativeSizeAxes = Axes.Both;
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
}
}
}
}

View File

@ -12,6 +12,7 @@ using osu.Framework.Graphics.Textures;
using osu.Game.Audio;
using osu.Game.Extensions;
using osu.Game.IO;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osuTK;
using osuTK.Graphics;
@ -86,6 +87,7 @@ namespace osu.Game.Skinning
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)),
}
};
@ -109,6 +111,9 @@ namespace osu.Game.Skinning
case HUDSkinComponents.HealthDisplay:
return new DefaultHealthDisplay();
case HUDSkinComponents.SongProgress:
return new SongProgress();
}
break;

View File

@ -95,6 +95,10 @@ namespace osu.Game.Skinning.Editor
private class ToolboxComponentButton : OsuButton
{
protected override bool ShouldBeConsideredForInput(Drawable child) => false;
public override bool PropagateNonPositionalInputSubTree => false;
private readonly Drawable component;
public Action<Type> RequestPlacement;

View File

@ -9,5 +9,6 @@ namespace osu.Game.Skinning
ScoreCounter,
AccuracyCounter,
HealthDisplay,
SongProgress,
}
}

View File

@ -17,6 +17,7 @@ using osu.Game.Audio;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osuTK.Graphics;
@ -350,6 +351,7 @@ namespace osu.Game.Skinning
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.ScoreCounter)) ?? new DefaultScoreCounter(),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)) ?? new DefaultAccuracyCounter(),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)) ?? new DefaultHealthDisplay(),
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)) ?? new SongProgress(),
}
};