mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 03:22:54 +08:00
Merge branch 'legacy-beatmap-skin-hud-fallback' into catch-hide-combo-workaround
This commit is contained in:
commit
43094425e2
@ -15,7 +15,6 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Tests.Visual;
|
using osu.Game.Tests.Visual;
|
||||||
using osuTK;
|
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
@ -35,7 +34,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPlaceBeforeCurrentTimeDownwards()
|
public void TestPlaceBeforeCurrentTimeDownwards()
|
||||||
{
|
{
|
||||||
AddStep("move mouse before current time", () => InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Single().ScreenSpaceDrawQuad.BottomLeft - new Vector2(0, 10)));
|
AddStep("move mouse before current time", () =>
|
||||||
|
{
|
||||||
|
var column = this.ChildrenOfType<Column>().Single();
|
||||||
|
InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(-100));
|
||||||
|
});
|
||||||
|
|
||||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
@ -45,7 +48,11 @@ namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestPlaceAfterCurrentTimeDownwards()
|
public void TestPlaceAfterCurrentTimeDownwards()
|
||||||
{
|
{
|
||||||
AddStep("move mouse after current time", () => InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Single()));
|
AddStep("move mouse after current time", () =>
|
||||||
|
{
|
||||||
|
var column = this.ChildrenOfType<Column>().Single();
|
||||||
|
InputManager.MoveMouseTo(column.ScreenSpacePositionAtTime(100));
|
||||||
|
});
|
||||||
|
|
||||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 0), _ => new DefaultColumnBackground())
|
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, 1), _ => new DefaultColumnBackground())
|
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, 0), _ => new DefaultKeyArea())
|
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Mania.Tests.Skinning
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Width = 0.5f,
|
||||||
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, 1), _ => new DefaultKeyArea())
|
Child = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
public class TestSceneDrawableManiaHitObject : OsuTestScene
|
||||||
|
{
|
||||||
|
private readonly ManualClock clock = new ManualClock();
|
||||||
|
|
||||||
|
private Column column;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = new ScrollingTestContainer(ScrollingDirection.Down)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
AutoSizeAxes = Axes.X,
|
||||||
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
TimeRange = 2000,
|
||||||
|
Clock = new FramedClock(clock),
|
||||||
|
Child = column = new Column(0)
|
||||||
|
{
|
||||||
|
Action = { Value = ManiaAction.Key1 },
|
||||||
|
Height = 0.85f,
|
||||||
|
AccentColour = Color4.Gray
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHoldNoteHeadVisibility()
|
||||||
|
{
|
||||||
|
DrawableHoldNote note = null;
|
||||||
|
AddStep("Add hold note", () =>
|
||||||
|
{
|
||||||
|
var h = new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = 0,
|
||||||
|
Duration = 1000
|
||||||
|
};
|
||||||
|
h.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
column.Add(note = new DrawableHoldNote(h));
|
||||||
|
});
|
||||||
|
AddStep("Hold key", () =>
|
||||||
|
{
|
||||||
|
clock.CurrentTime = 0;
|
||||||
|
note.OnPressed(ManiaAction.Key1);
|
||||||
|
});
|
||||||
|
AddStep("progress time", () => clock.CurrentTime = 500);
|
||||||
|
AddAssert("head is visible", () => note.Head.Alpha == 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -5,13 +5,11 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Replays;
|
using osu.Game.Replays;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -414,14 +412,7 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
AddUntilStep("Beatmap at 0", () => Beatmap.Value.Track.CurrentTime == 0);
|
||||||
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
AddUntilStep("Wait until player is loaded", () => currentPlayer.IsCurrentScreen());
|
||||||
|
|
||||||
AddUntilStep("wait for head", () => currentPlayer.GameplayClockContainer.GameplayClock.CurrentTime >= time_head);
|
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor?.HasCompleted.Value == true);
|
||||||
AddAssert("head is visible",
|
|
||||||
() => currentPlayer.ChildrenOfType<DrawableHoldNote>()
|
|
||||||
.Single(note => note.HitObject == beatmap.HitObjects[0])
|
|
||||||
.Head
|
|
||||||
.Alpha == 1);
|
|
||||||
|
|
||||||
AddUntilStep("Wait for completion", () => currentPlayer.ScoreProcessor.HasCompleted.Value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
private class ScoreAccessibleReplayPlayer : ReplayPlayer
|
||||||
|
@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
foreach (var line in grid.Objects.OfType<DrawableGridLine>())
|
foreach (var line in grid.Objects.OfType<DrawableGridLine>())
|
||||||
availableLines.Push(line);
|
availableLines.Push(line);
|
||||||
|
|
||||||
grid.Clear(false);
|
grid.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (selectionTimeRange == null)
|
if (selectionTimeRange == null)
|
||||||
|
@ -58,8 +58,9 @@ namespace osu.Game.Rulesets.Mania.Edit
|
|||||||
|
|
||||||
EditorBeatmap.PerformOnSelection(h =>
|
EditorBeatmap.PerformOnSelection(h =>
|
||||||
{
|
{
|
||||||
if (h is ManiaHitObject maniaObj)
|
maniaPlayfield.Remove(h);
|
||||||
maniaObj.Column += columnDelta;
|
((ManiaHitObject)h).Column += columnDelta;
|
||||||
|
maniaPlayfield.Add(h);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,12 +9,6 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
{
|
{
|
||||||
public class ManiaSkinComponent : GameplaySkinComponent<ManiaSkinComponents>
|
public class ManiaSkinComponent : GameplaySkinComponent<ManiaSkinComponents>
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// The intended <see cref="Column"/> index for this component.
|
|
||||||
/// May be null if the component does not exist in a <see cref="Column"/>.
|
|
||||||
/// </summary>
|
|
||||||
public readonly int? TargetColumn;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The intended <see cref="StageDefinition"/> for this component.
|
/// The intended <see cref="StageDefinition"/> for this component.
|
||||||
/// May be null if the component is not a direct member of a <see cref="Stage"/>.
|
/// May be null if the component is not a direct member of a <see cref="Stage"/>.
|
||||||
@ -25,12 +19,10 @@ namespace osu.Game.Rulesets.Mania
|
|||||||
/// Creates a new <see cref="ManiaSkinComponent"/>.
|
/// Creates a new <see cref="ManiaSkinComponent"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="component">The component.</param>
|
/// <param name="component">The component.</param>
|
||||||
/// <param name="targetColumn">The intended <see cref="Column"/> index for this component. May be null if the component does not exist in a <see cref="Column"/>.</param>
|
|
||||||
/// <param name="stageDefinition">The intended <see cref="StageDefinition"/> for this component. May be null if the component is not a direct member of a <see cref="Stage"/>.</param>
|
/// <param name="stageDefinition">The intended <see cref="StageDefinition"/> for this component. May be null if the component is not a direct member of a <see cref="Stage"/>.</param>
|
||||||
public ManiaSkinComponent(ManiaSkinComponents component, int? targetColumn = null, StageDefinition? stageDefinition = null)
|
public ManiaSkinComponent(ManiaSkinComponents component, StageDefinition? stageDefinition = null)
|
||||||
: base(component)
|
: base(component)
|
||||||
{
|
{
|
||||||
TargetColumn = targetColumn;
|
|
||||||
StageDefinition = stageDefinition;
|
StageDefinition = stageDefinition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -12,6 +13,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
{
|
{
|
||||||
@ -29,21 +31,21 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
public DrawableHoldNoteHead Head => headContainer.Child;
|
public DrawableHoldNoteHead Head => headContainer.Child;
|
||||||
public DrawableHoldNoteTail Tail => tailContainer.Child;
|
public DrawableHoldNoteTail Tail => tailContainer.Child;
|
||||||
|
|
||||||
private readonly Container<DrawableHoldNoteHead> headContainer;
|
private Container<DrawableHoldNoteHead> headContainer;
|
||||||
private readonly Container<DrawableHoldNoteTail> tailContainer;
|
private Container<DrawableHoldNoteTail> tailContainer;
|
||||||
private readonly Container<DrawableHoldNoteTick> tickContainer;
|
private Container<DrawableHoldNoteTick> tickContainer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed.
|
/// Contains the size of the hold note covering the whole head/tail bounds. The size of this container changes as the hold note is being pressed.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Container sizingContainer;
|
private Container sizingContainer;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of <see cref="sizingContainer"/>.
|
/// Contains the contents of the hold note that should be masked as the hold note is being pressed. Follows changes in the size of <see cref="sizingContainer"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly Container maskingContainer;
|
private Container maskingContainer;
|
||||||
|
|
||||||
private readonly SkinnableDrawable bodyPiece;
|
private SkinnableDrawable bodyPiece;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
|
/// Time at which the user started holding this hold note. Null if the user is not holding this hold note.
|
||||||
@ -60,11 +62,19 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private double? releaseTime;
|
private double? releaseTime;
|
||||||
|
|
||||||
|
public DrawableHoldNote()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public DrawableHoldNote(HoldNote hitObject)
|
public DrawableHoldNote(HoldNote hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
Container maskedContents;
|
Container maskedContents;
|
||||||
|
|
||||||
AddRangeInternal(new Drawable[]
|
AddRangeInternal(new Drawable[]
|
||||||
@ -86,7 +96,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both }
|
headContainer = new Container<DrawableHoldNoteHead> { RelativeSizeAxes = Axes.Both }
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece
|
bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody), _ => new DefaultBodyPiece
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
})
|
})
|
||||||
@ -105,6 +115,16 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
sizingContainer.Size = Vector2.One;
|
||||||
|
HoldStartTime = null;
|
||||||
|
HoldBrokenTime = null;
|
||||||
|
releaseTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
protected override void AddNestedHitObject(DrawableHitObject hitObject)
|
||||||
{
|
{
|
||||||
base.AddNestedHitObject(hitObject);
|
base.AddNestedHitObject(hitObject);
|
||||||
@ -128,37 +148,23 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
protected override void ClearNestedHitObjects()
|
protected override void ClearNestedHitObjects()
|
||||||
{
|
{
|
||||||
base.ClearNestedHitObjects();
|
base.ClearNestedHitObjects();
|
||||||
headContainer.Clear();
|
headContainer.Clear(false);
|
||||||
tailContainer.Clear();
|
tailContainer.Clear(false);
|
||||||
tickContainer.Clear();
|
tickContainer.Clear(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
protected override DrawableHitObject CreateNestedHitObject(HitObject hitObject)
|
||||||
{
|
{
|
||||||
switch (hitObject)
|
switch (hitObject)
|
||||||
{
|
{
|
||||||
case TailNote _:
|
case TailNote tail:
|
||||||
return new DrawableHoldNoteTail(this)
|
return new DrawableHoldNoteTail(tail);
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
AccentColour = { BindTarget = AccentColour }
|
|
||||||
};
|
|
||||||
|
|
||||||
case Note _:
|
case HeadNote head:
|
||||||
return new DrawableHoldNoteHead(this)
|
return new DrawableHoldNoteHead(head);
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
AccentColour = { BindTarget = AccentColour }
|
|
||||||
};
|
|
||||||
|
|
||||||
case HoldNoteTick tick:
|
case HoldNoteTick tick:
|
||||||
return new DrawableHoldNoteTick(tick)
|
return new DrawableHoldNoteTick(tick);
|
||||||
{
|
|
||||||
HoldStartTime = () => HoldStartTime,
|
|
||||||
AccentColour = { BindTarget = AccentColour }
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.CreateNestedHitObject(hitObject);
|
return base.CreateNestedHitObject(hitObject);
|
||||||
|
@ -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 osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
@ -12,11 +13,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
{
|
{
|
||||||
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteHead;
|
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteHead;
|
||||||
|
|
||||||
public DrawableHoldNoteHead(DrawableHoldNote holdNote)
|
public DrawableHoldNoteHead()
|
||||||
: base(holdNote.HitObject.Head)
|
: this(null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DrawableHoldNoteHead(HeadNote headNote)
|
||||||
|
: base(headNote)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
|
}
|
||||||
|
|
||||||
public void UpdateResult() => base.UpdateResult(true);
|
public void UpdateResult() => base.UpdateResult(true);
|
||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// 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.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
||||||
@ -20,12 +21,18 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
protected override ManiaSkinComponents Component => ManiaSkinComponents.HoldNoteTail;
|
||||||
|
|
||||||
private readonly DrawableHoldNote holdNote;
|
protected DrawableHoldNote HoldNote => (DrawableHoldNote)ParentHitObject;
|
||||||
|
|
||||||
public DrawableHoldNoteTail(DrawableHoldNote holdNote)
|
public DrawableHoldNoteTail()
|
||||||
: base(holdNote.HitObject.Tail)
|
: this(null)
|
||||||
{
|
{
|
||||||
this.holdNote = holdNote;
|
}
|
||||||
|
|
||||||
|
public DrawableHoldNoteTail(TailNote tailNote)
|
||||||
|
: base(tailNote)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre;
|
||||||
|
Origin = Anchor.TopCentre;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void UpdateResult() => base.UpdateResult(true);
|
public void UpdateResult() => base.UpdateResult(true);
|
||||||
@ -54,7 +61,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
ApplyResult(r =>
|
ApplyResult(r =>
|
||||||
{
|
{
|
||||||
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
|
// If the head wasn't hit or the hold note was broken, cap the max score to Meh.
|
||||||
if (result > HitResult.Meh && (!holdNote.Head.IsHit || holdNote.HoldBrokenTime != null))
|
if (result > HitResult.Meh && (!HoldNote.Head.IsHit || HoldNote.HoldBrokenTime != null))
|
||||||
result = HitResult.Meh;
|
result = HitResult.Meh;
|
||||||
|
|
||||||
r.Type = result;
|
r.Type = result;
|
||||||
|
@ -2,7 +2,8 @@
|
|||||||
// 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;
|
||||||
using osuTK;
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -19,22 +20,28 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// References the time at which the user started holding the hold note.
|
/// References the time at which the user started holding the hold note.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Func<double?> HoldStartTime;
|
private Func<double?> holdStartTime;
|
||||||
|
|
||||||
|
private Container glowContainer;
|
||||||
|
|
||||||
|
public DrawableHoldNoteTick()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public DrawableHoldNoteTick(HoldNoteTick hitObject)
|
public DrawableHoldNoteTick(HoldNoteTick hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
Container glowContainer;
|
|
||||||
|
|
||||||
Anchor = Anchor.TopCentre;
|
Anchor = Anchor.TopCentre;
|
||||||
Origin = Anchor.TopCentre;
|
Origin = Anchor.TopCentre;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Size = new Vector2(1);
|
}
|
||||||
|
|
||||||
AddRangeInternal(new[]
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
{
|
{
|
||||||
glowContainer = new CircularContainer
|
AddInternal(glowContainer = new CircularContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
@ -49,8 +56,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
AlwaysPresent = true
|
AlwaysPresent = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
AccentColour.BindValueChanged(colour =>
|
AccentColour.BindValueChanged(colour =>
|
||||||
{
|
{
|
||||||
@ -64,12 +75,29 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
Debug.Assert(ParentHitObject != null);
|
||||||
|
|
||||||
|
var holdNote = (DrawableHoldNote)ParentHitObject;
|
||||||
|
holdStartTime = () => holdNote.HoldStartTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFree()
|
||||||
|
{
|
||||||
|
base.OnFree();
|
||||||
|
|
||||||
|
holdStartTime = null;
|
||||||
|
}
|
||||||
|
|
||||||
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
protected override void CheckForResult(bool userTriggered, double timeOffset)
|
||||||
{
|
{
|
||||||
if (Time.Current < HitObject.StartTime)
|
if (Time.Current < HitObject.StartTime)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var startTime = HoldStartTime?.Invoke();
|
var startTime = holdStartTime?.Invoke();
|
||||||
|
|
||||||
if (startTime == null || startTime > HitObject.StartTime)
|
if (startTime == null || startTime > HitObject.StartTime)
|
||||||
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||||
|
@ -50,6 +50,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
protected DrawableManiaHitObject(ManiaHitObject hitObject)
|
protected DrawableManiaHitObject(ManiaHitObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
@ -59,9 +60,31 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
Action.BindTo(action);
|
Action.BindTo(action);
|
||||||
|
|
||||||
Direction.BindTo(scrollingInfo.Direction);
|
Direction.BindTo(scrollingInfo.Direction);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
Direction.BindValueChanged(OnDirectionChanged, true);
|
Direction.BindValueChanged(OnDirectionChanged, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnApply()
|
||||||
|
{
|
||||||
|
base.OnApply();
|
||||||
|
|
||||||
|
if (ParentHitObject != null)
|
||||||
|
AccentColour.BindTo(ParentHitObject.AccentColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFree()
|
||||||
|
{
|
||||||
|
base.OnFree();
|
||||||
|
|
||||||
|
if (ParentHitObject != null)
|
||||||
|
AccentColour.UnbindFrom(ParentHitObject.AccentColour);
|
||||||
|
}
|
||||||
|
|
||||||
private double computedLifetimeStart;
|
private double computedLifetimeStart;
|
||||||
|
|
||||||
public override double LifetimeStart
|
public override double LifetimeStart
|
||||||
@ -147,12 +170,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
|
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
|
||||||
where TObject : ManiaHitObject
|
where TObject : ManiaHitObject
|
||||||
{
|
{
|
||||||
public new readonly TObject HitObject;
|
public new TObject HitObject => (TObject)base.HitObject;
|
||||||
|
|
||||||
protected DrawableManiaHitObject(TObject hitObject)
|
protected DrawableManiaHitObject(TObject hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
HitObject = hitObject;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,31 +33,37 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note;
|
protected virtual ManiaSkinComponents Component => ManiaSkinComponents.Note;
|
||||||
|
|
||||||
private readonly Drawable headPiece;
|
private Drawable headPiece;
|
||||||
|
|
||||||
|
public DrawableNote()
|
||||||
|
: this(null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
public DrawableNote(Note hitObject)
|
public DrawableNote(Note hitObject)
|
||||||
: base(hitObject)
|
: base(hitObject)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
|
|
||||||
AddInternal(headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component, hitObject.Column), _ => new DefaultNotePiece())
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(ManiaRulesetConfigManager rulesetConfig)
|
private void load(ManiaRulesetConfigManager rulesetConfig)
|
||||||
{
|
{
|
||||||
rulesetConfig?.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
|
rulesetConfig?.BindWith(ManiaRulesetSetting.TimingBasedNoteColouring, configTimingBasedNoteColouring);
|
||||||
|
|
||||||
|
AddInternal(headPiece = new SkinnableDrawable(new ManiaSkinComponent(Component), _ => new DefaultNotePiece())
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
HitObject.StartTimeBindable.BindValueChanged(_ => updateSnapColour());
|
base.LoadComplete();
|
||||||
configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour(), true);
|
|
||||||
|
configTimingBasedNoteColouring.BindValueChanged(_ => updateSnapColour());
|
||||||
|
StartTimeBindable.BindValueChanged(_ => updateSnapColour(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
protected override void OnDirectionChanged(ValueChangedEvent<ScrollingDirection> e)
|
||||||
@ -102,7 +108,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
|
|
||||||
private void updateSnapColour()
|
private void updateSnapColour()
|
||||||
{
|
{
|
||||||
if (beatmap == null) return;
|
if (beatmap == null || HitObject == null) return;
|
||||||
|
|
||||||
int snapDivisor = beatmap.ControlPointInfo.GetClosestBeatDivisor(HitObject.StartTime);
|
int snapDivisor = beatmap.ControlPointInfo.GetClosestBeatDivisor(HitObject.StartTime);
|
||||||
|
|
||||||
|
9
osu.Game.Rulesets.Mania/Objects/HeadNote.cs
Normal file
9
osu.Game.Rulesets.Mania/Objects/HeadNote.cs
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Objects
|
||||||
|
{
|
||||||
|
public class HeadNote : Note
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
@ -72,7 +72,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The head note of the hold.
|
/// The head note of the hold.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public Note Head { get; private set; }
|
public HeadNote Head { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The tail note of the hold.
|
/// The tail note of the hold.
|
||||||
@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Mania.Objects
|
|||||||
|
|
||||||
createTicks(cancellationToken);
|
createTicks(cancellationToken);
|
||||||
|
|
||||||
AddNested(Head = new Note
|
AddNested(Head = new HeadNote
|
||||||
{
|
{
|
||||||
StartTime = StartTime,
|
StartTime = StartTime,
|
||||||
Column = Column,
|
Column = Column,
|
||||||
|
@ -17,6 +17,7 @@ using osu.Game.Rulesets.UI.Scrolling;
|
|||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.UI
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
@ -55,7 +56,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
RelativeSizeAxes = Axes.Y;
|
RelativeSizeAxes = Axes.Y;
|
||||||
Width = COLUMN_WIDTH;
|
Width = COLUMN_WIDTH;
|
||||||
|
|
||||||
Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground, Index), _ => new DefaultColumnBackground())
|
Drawable background = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.ColumnBackground), _ => new DefaultColumnBackground())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
};
|
};
|
||||||
@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
||||||
background.CreateProxy(),
|
background.CreateProxy(),
|
||||||
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
||||||
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, Index), _ => new DefaultKeyArea())
|
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea), _ => new DefaultKeyArea())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
@ -83,6 +84,19 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
hitPolicy = new OrderedHitPolicy(HitObjectContainer);
|
||||||
|
|
||||||
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
||||||
|
|
||||||
|
RegisterPool<Note, DrawableNote>(10, 50);
|
||||||
|
RegisterPool<HoldNote, DrawableHoldNote>(10, 50);
|
||||||
|
RegisterPool<HeadNote, DrawableHoldNoteHead>(10, 50);
|
||||||
|
RegisterPool<TailNote, DrawableHoldNoteTail>(10, 50);
|
||||||
|
RegisterPool<HoldNoteTick, DrawableHoldNoteTick>(50, 250);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
NewResult += OnNewResult;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ColumnType ColumnType { get; set; }
|
public ColumnType ColumnType { get; set; }
|
||||||
@ -98,28 +112,14 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
return dependencies;
|
return dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
protected override void OnNewDrawableHitObject(DrawableHitObject drawableHitObject)
|
||||||
/// Adds a DrawableHitObject to this Playfield.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="hitObject">The DrawableHitObject to add.</param>
|
|
||||||
public override void Add(DrawableHitObject hitObject)
|
|
||||||
{
|
{
|
||||||
hitObject.AccentColour.Value = AccentColour;
|
base.OnNewDrawableHitObject(drawableHitObject);
|
||||||
hitObject.OnNewResult += OnNewResult;
|
|
||||||
|
|
||||||
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)hitObject;
|
DrawableManiaHitObject maniaObject = (DrawableManiaHitObject)drawableHitObject;
|
||||||
|
|
||||||
|
maniaObject.AccentColour.Value = AccentColour;
|
||||||
maniaObject.CheckHittable = hitPolicy.IsHittable;
|
maniaObject.CheckHittable = hitPolicy.IsHittable;
|
||||||
|
|
||||||
base.Add(hitObject);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool Remove(DrawableHitObject h)
|
|
||||||
{
|
|
||||||
if (!base.Remove(h))
|
|
||||||
return false;
|
|
||||||
|
|
||||||
h.OnNewResult -= OnNewResult;
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Depth = 2,
|
Depth = 2,
|
||||||
},
|
},
|
||||||
hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget, columnIndex), _ => new DefaultHitTarget())
|
hitTarget = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitTarget), _ => new DefaultHitTarget())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Depth = 1
|
Depth = 1
|
||||||
|
@ -18,7 +18,6 @@ using osu.Game.Replays;
|
|||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Configuration;
|
using osu.Game.Rulesets.Mania.Configuration;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -134,20 +133,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
|
protected override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
|
||||||
|
|
||||||
public override DrawableHitObject<ManiaHitObject> CreateDrawableRepresentation(ManiaHitObject h)
|
public override DrawableHitObject<ManiaHitObject> CreateDrawableRepresentation(ManiaHitObject h) => null;
|
||||||
{
|
|
||||||
switch (h)
|
|
||||||
{
|
|
||||||
case HoldNote holdNote:
|
|
||||||
return new DrawableHoldNote(holdNote);
|
|
||||||
|
|
||||||
case Note note:
|
|
||||||
return new DrawableNote(note);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
protected override ReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay);
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Game.Rulesets.Mania.Beatmaps;
|
using osu.Game.Rulesets.Mania.Beatmaps;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -56,6 +57,10 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void Add(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Add(hitObject);
|
||||||
|
|
||||||
|
public override bool Remove(HitObject hitObject) => getStageByColumn(((ManiaHitObject)hitObject).Column).Remove(hitObject);
|
||||||
|
|
||||||
public override void Add(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Add(h);
|
public override void Add(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Add(h);
|
||||||
|
|
||||||
public override bool Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h);
|
public override bool Remove(DrawableHitObject h) => getStageByColumn(((ManiaHitObject)h.HitObject).Column).Remove(h);
|
||||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion, column.Index), _ => new DefaultHitExplosion())
|
InternalChild = skinnableExplosion = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HitExplosion), _ => new DefaultHitExplosion())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
};
|
};
|
||||||
|
@ -11,6 +11,7 @@ using osu.Game.Rulesets.Mania.Beatmaps;
|
|||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Mania.UI.Components;
|
using osu.Game.Rulesets.Mania.UI.Components;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -132,33 +133,19 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Add(DrawableHitObject h)
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
var maniaObject = (ManiaHitObject)h.HitObject;
|
base.LoadComplete();
|
||||||
|
NewResult += OnNewResult;
|
||||||
int columnIndex = -1;
|
|
||||||
|
|
||||||
maniaObject.ColumnBindable.BindValueChanged(_ =>
|
|
||||||
{
|
|
||||||
if (columnIndex != -1)
|
|
||||||
Columns.ElementAt(columnIndex).Remove(h);
|
|
||||||
|
|
||||||
columnIndex = maniaObject.Column - firstColumnIndex;
|
|
||||||
Columns.ElementAt(columnIndex).Add(h);
|
|
||||||
}, true);
|
|
||||||
|
|
||||||
h.OnNewResult += OnNewResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool Remove(DrawableHitObject h)
|
public override void Add(HitObject hitObject) => Columns.ElementAt(((ManiaHitObject)hitObject).Column - firstColumnIndex).Add(hitObject);
|
||||||
{
|
|
||||||
var maniaObject = (ManiaHitObject)h.HitObject;
|
|
||||||
int columnIndex = maniaObject.Column - firstColumnIndex;
|
|
||||||
Columns.ElementAt(columnIndex).Remove(h);
|
|
||||||
|
|
||||||
h.OnNewResult -= OnNewResult;
|
public override bool Remove(HitObject hitObject) => Columns.ElementAt(((ManiaHitObject)hitObject).Column - firstColumnIndex).Remove(hitObject);
|
||||||
return true;
|
|
||||||
}
|
public override void Add(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Add(h);
|
||||||
|
|
||||||
|
public override bool Remove(DrawableHitObject h) => Columns.ElementAt(((ManiaHitObject)h.HitObject).Column - firstColumnIndex).Remove(h);
|
||||||
|
|
||||||
public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline));
|
public void Add(BarLine barline) => base.Add(new DrawableBarLine(barline));
|
||||||
|
|
||||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Taiko.Tests
|
|||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
=> AddStep("clear SHOC", () => hitObjectContainer.Clear(false));
|
=> AddStep("clear SHOC", () => hitObjectContainer.Clear());
|
||||||
|
|
||||||
protected void AddHitObject(DrawableHitObject hitObject)
|
protected void AddHitObject(DrawableHitObject hitObject)
|
||||||
=> AddStep("add to SHOC", () => hitObjectContainer.Add(hitObject));
|
=> AddStep("add to SHOC", () => hitObjectContainer.Add(hitObject));
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
@ -12,6 +13,7 @@ using osu.Game.Tests.Visual;
|
|||||||
|
|
||||||
namespace osu.Game.Tests.Editing
|
namespace osu.Game.Tests.Editing
|
||||||
{
|
{
|
||||||
|
[HeadlessTest]
|
||||||
public class TestSceneHitObjectContainerEventBuffer : OsuTestScene
|
public class TestSceneHitObjectContainerEventBuffer : OsuTestScene
|
||||||
{
|
{
|
||||||
private readonly TestHitObject testObj = new TestHitObject();
|
private readonly TestHitObject testObj = new TestHitObject();
|
||||||
|
@ -45,15 +45,16 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
AddStep("Create DHO", () =>
|
AddStep("Create DHO", () =>
|
||||||
{
|
{
|
||||||
dho = new TestDrawableHitObject(null);
|
dho = new TestDrawableHitObject(null);
|
||||||
dho.Apply(entry = new TestLifetimeEntry(new HitObject())
|
dho.Apply(entry = new TestLifetimeEntry(new HitObject()));
|
||||||
{
|
|
||||||
LifetimeStart = 0,
|
|
||||||
LifetimeEnd = 1000,
|
|
||||||
});
|
|
||||||
Child = dho;
|
Child = dho;
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("KeepAlive = true", () => entry.KeepAlive = true);
|
AddStep("KeepAlive = true", () =>
|
||||||
|
{
|
||||||
|
entry.LifetimeStart = 0;
|
||||||
|
entry.LifetimeEnd = 1000;
|
||||||
|
entry.KeepAlive = true;
|
||||||
|
});
|
||||||
AddAssert("Lifetime is overriden", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == double.MaxValue);
|
AddAssert("Lifetime is overriden", () => entry.LifetimeStart == double.MinValue && entry.LifetimeEnd == double.MaxValue);
|
||||||
|
|
||||||
AddStep("Set LifetimeStart", () => dho.LifetimeStart = 500);
|
AddStep("Set LifetimeStart", () => dho.LifetimeStart = 500);
|
||||||
|
133
osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
Normal file
133
osu.Game.Tests/Visual/Gameplay/TestSceneBeatmapSkinFallbacks.cs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// 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 System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Lists;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Skinning;
|
||||||
|
using osu.Game.Storyboards;
|
||||||
|
using osu.Game.Tests.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
|
{
|
||||||
|
public class TestSceneBeatmapSkinFallbacks : OsuPlayerTestScene
|
||||||
|
{
|
||||||
|
private ISkin currentBeatmapSkin;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private SkinManager skinManager { get; set; }
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||||
|
|
||||||
|
[Cached(typeof(HealthProcessor))]
|
||||||
|
private HealthProcessor healthProcessor = new DrainingHealthProcessor(0);
|
||||||
|
|
||||||
|
protected override bool HasCustomSteps => true;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEmptyDefaultBeatmapSkinFallsBack()
|
||||||
|
{
|
||||||
|
CreateSkinTest(DefaultLegacySkin.Info, () => new TestWorkingBeatmap(CreateBeatmap(new OsuRuleset().RulesetInfo)).Skin);
|
||||||
|
AddAssert("hud from default legacy skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEmptyLegacyBeatmapSkinFallsBack()
|
||||||
|
{
|
||||||
|
CreateSkinTest(SkinInfo.Default, () => new LegacyBeatmapSkin(new BeatmapInfo(), null, null));
|
||||||
|
AddAssert("hud from default skin", () => AssertComponentsFromExpectedSource(SkinnableTarget.MainHUDComponents, skinManager.CurrentSkin.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void CreateSkinTest(SkinInfo gameCurrentSkin, Func<ISkin> getBeatmapSkin)
|
||||||
|
{
|
||||||
|
CreateTest(() =>
|
||||||
|
{
|
||||||
|
AddStep("setup skins", () =>
|
||||||
|
{
|
||||||
|
skinManager.CurrentSkinInfo.Value = gameCurrentSkin;
|
||||||
|
currentBeatmapSkin = getBeatmapSkin();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected bool AssertComponentsFromExpectedSource(SkinnableTarget target, ISkin expectedSource)
|
||||||
|
{
|
||||||
|
var expectedComponentsContainer = (SkinnableTargetComponentsContainer)expectedSource.GetDrawableComponent(new SkinnableTargetComponent(target));
|
||||||
|
|
||||||
|
Add(expectedComponentsContainer);
|
||||||
|
expectedComponentsContainer?.UpdateSubTree();
|
||||||
|
var expectedInfo = expectedComponentsContainer?.CreateSkinnableInfo();
|
||||||
|
Remove(expectedComponentsContainer);
|
||||||
|
|
||||||
|
var actualInfo = Player.ChildrenOfType<SkinnableTargetContainer>().First(s => s.Target == target)
|
||||||
|
.ChildrenOfType<SkinnableTargetComponentsContainer>().Single().CreateSkinnableInfo();
|
||||||
|
|
||||||
|
return almostEqual(actualInfo, expectedInfo, 2f);
|
||||||
|
|
||||||
|
static bool almostEqual(SkinnableInfo info, SkinnableInfo other, float positionTolerance) =>
|
||||||
|
other != null
|
||||||
|
&& info.Anchor == other.Anchor
|
||||||
|
&& info.Origin == other.Origin
|
||||||
|
&& Precision.AlmostEquals(info.Position, other.Position, positionTolerance)
|
||||||
|
&& info.Children.SequenceEqual(other.Children, new FuncEqualityComparer<SkinnableInfo>((s1, s2) => almostEqual(s1, s2, positionTolerance)));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard = null)
|
||||||
|
=> new CustomSkinWorkingBeatmap(beatmap, storyboard, Clock, Audio, currentBeatmapSkin);
|
||||||
|
|
||||||
|
protected override Ruleset CreatePlayerRuleset() => new TestOsuRuleset();
|
||||||
|
|
||||||
|
private class CustomSkinWorkingBeatmap : ClockBackedTestWorkingBeatmap
|
||||||
|
{
|
||||||
|
private readonly ISkin beatmapSkin;
|
||||||
|
|
||||||
|
public CustomSkinWorkingBeatmap(IBeatmap beatmap, Storyboard storyboard, IFrameBasedClock referenceClock, AudioManager audio, ISkin beatmapSkin)
|
||||||
|
: base(beatmap, storyboard, referenceClock, audio)
|
||||||
|
{
|
||||||
|
this.beatmapSkin = beatmapSkin;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ISkin GetSkin() => beatmapSkin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestOsuRuleset : OsuRuleset
|
||||||
|
{
|
||||||
|
public override ISkin CreateLegacySkinProvider(ISkinSource source, IBeatmap beatmap) => new TestOsuLegacySkinTransformer(source);
|
||||||
|
|
||||||
|
private class TestOsuLegacySkinTransformer : OsuLegacySkinTransformer
|
||||||
|
{
|
||||||
|
public TestOsuLegacySkinTransformer(ISkinSource source)
|
||||||
|
: base(source)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||||
|
{
|
||||||
|
var drawable = base.GetDrawableComponent(component);
|
||||||
|
if (drawable != null)
|
||||||
|
return drawable;
|
||||||
|
|
||||||
|
// this isn't really supposed to make a difference from returning null,
|
||||||
|
// but it appears it does, returning null will skip over falling back to beatmap skin,
|
||||||
|
// while calling Source.GetDrawableComponent() doesn't.
|
||||||
|
return Source.GetDrawableComponent(component);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,9 @@
|
|||||||
// 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.Diagnostics.CodeAnalysis;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -11,29 +14,35 @@ using osu.Game.Graphics.Sprites;
|
|||||||
using osu.Game.Rulesets.Catch.Scoring;
|
using osu.Game.Rulesets.Catch.Scoring;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Scoring;
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Rulesets.Osu.Scoring;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.Taiko.Scoring;
|
using osu.Game.Rulesets.Taiko.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Gameplay
|
namespace osu.Game.Tests.Visual.Gameplay
|
||||||
{
|
{
|
||||||
public class TestSceneHitErrorMeter : OsuTestScene
|
public class TestSceneHitErrorMeter : OsuTestScene
|
||||||
{
|
{
|
||||||
private HitWindows hitWindows;
|
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
private ScoreProcessor scoreProcessor = new ScoreProcessor();
|
||||||
|
|
||||||
|
[Cached(typeof(DrawableRuleset))]
|
||||||
|
private TestDrawableRuleset drawableRuleset = new TestDrawableRuleset();
|
||||||
|
|
||||||
public TestSceneHitErrorMeter()
|
public TestSceneHitErrorMeter()
|
||||||
{
|
{
|
||||||
recreateDisplay(new OsuHitWindows(), 5);
|
recreateDisplay(new OsuHitWindows(), 5);
|
||||||
|
|
||||||
AddRepeatStep("New random judgement", () => newJudgement(), 40);
|
AddRepeatStep("New random judgement", () => newJudgement(), 40);
|
||||||
|
|
||||||
AddRepeatStep("New max negative", () => newJudgement(-hitWindows.WindowFor(HitResult.Meh)), 20);
|
AddRepeatStep("New max negative", () => newJudgement(-drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20);
|
||||||
AddRepeatStep("New max positive", () => newJudgement(hitWindows.WindowFor(HitResult.Meh)), 20);
|
AddRepeatStep("New max positive", () => newJudgement(drawableRuleset.HitWindows.WindowFor(HitResult.Meh)), 20);
|
||||||
AddStep("New fixed judgement (50ms)", () => newJudgement(50));
|
AddStep("New fixed judgement (50ms)", () => newJudgement(50));
|
||||||
|
|
||||||
AddStep("Judgement barrage", () =>
|
AddStep("Judgement barrage", () =>
|
||||||
@ -83,10 +92,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)
|
private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)
|
||||||
{
|
{
|
||||||
this.hitWindows = hitWindows;
|
|
||||||
|
|
||||||
hitWindows?.SetDifficulty(overallDifficulty);
|
hitWindows?.SetDifficulty(overallDifficulty);
|
||||||
|
|
||||||
|
drawableRuleset.HitWindows = hitWindows;
|
||||||
|
|
||||||
Clear();
|
Clear();
|
||||||
|
|
||||||
Add(new FillFlowContainer
|
Add(new FillFlowContainer
|
||||||
@ -103,40 +112,40 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(new BarHitErrorMeter(hitWindows, true)
|
Add(new BarHitErrorMeter
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(new BarHitErrorMeter(hitWindows, false)
|
Add(new BarHitErrorMeter
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(new BarHitErrorMeter(hitWindows, true)
|
Add(new BarHitErrorMeter
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Rotation = 270,
|
Rotation = 270,
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(new ColourHitErrorMeter(hitWindows)
|
Add(new ColourHitErrorMeter
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Margin = new MarginPadding { Right = 50 }
|
Margin = new MarginPadding { Right = 50 }
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(new ColourHitErrorMeter(hitWindows)
|
Add(new ColourHitErrorMeter
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Margin = new MarginPadding { Left = 50 }
|
Margin = new MarginPadding { Left = 50 }
|
||||||
});
|
});
|
||||||
|
|
||||||
Add(new ColourHitErrorMeter(hitWindows)
|
Add(new ColourHitErrorMeter
|
||||||
{
|
{
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
@ -147,11 +156,47 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private void newJudgement(double offset = 0)
|
private void newJudgement(double offset = 0)
|
||||||
{
|
{
|
||||||
scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = hitWindows }, new Judgement())
|
scoreProcessor.ApplyResult(new JudgementResult(new HitCircle { HitWindows = drawableRuleset.HitWindows }, new Judgement())
|
||||||
{
|
{
|
||||||
TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset,
|
TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset,
|
||||||
Type = HitResult.Perfect,
|
Type = HitResult.Perfect,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "UnassignedGetOnlyAutoProperty")]
|
||||||
|
private class TestDrawableRuleset : DrawableRuleset
|
||||||
|
{
|
||||||
|
public HitWindows HitWindows;
|
||||||
|
|
||||||
|
public override IEnumerable<HitObject> Objects => new[] { new HitCircle { HitWindows = HitWindows } };
|
||||||
|
|
||||||
|
public override event Action<JudgementResult> NewResult;
|
||||||
|
public override event Action<JudgementResult> RevertResult;
|
||||||
|
|
||||||
|
public override Playfield Playfield { get; }
|
||||||
|
public override Container Overlays { get; }
|
||||||
|
public override Container FrameStableComponents { get; }
|
||||||
|
public override IFrameStableClock FrameStableClock { get; }
|
||||||
|
public override IReadOnlyList<Mod> Mods { get; }
|
||||||
|
|
||||||
|
public override double GameplayStartTime { get; }
|
||||||
|
public override GameplayCursorContainer Cursor { get; }
|
||||||
|
|
||||||
|
public TestDrawableRuleset()
|
||||||
|
: base(new OsuRuleset())
|
||||||
|
{
|
||||||
|
// won't compile without this.
|
||||||
|
NewResult?.Invoke(null);
|
||||||
|
RevertResult?.Invoke(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void SetReplayScore(Score replayScore) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public override void SetRecordTarget(Score score) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public override void RequestResume(Action continueResume) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
public override void CancelResume() => throw new NotImplementedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
152
osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs
Normal file
152
osu.Game.Tests/Visual/Online/TestSceneNewsSidebar.cs
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
// 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 System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.News.Sidebar;
|
||||||
|
using static osu.Game.Overlays.News.Sidebar.YearsPanel;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
public class TestSceneNewsSidebar : OsuTestScene
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||||
|
|
||||||
|
private TestNewsSidebar sidebar;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() => Child = sidebar = new TestNewsSidebar { YearChanged = onYearChanged });
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasic()
|
||||||
|
{
|
||||||
|
AddStep("Add metadata", () => sidebar.Metadata.Value = getMetadata(2021));
|
||||||
|
AddUntilStep("Month sections exist", () => sidebar.ChildrenOfType<MonthSection>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMetadataWithNoPosts()
|
||||||
|
{
|
||||||
|
AddStep("Add data with no posts", () => sidebar.Metadata.Value = metadata_with_no_posts);
|
||||||
|
AddUntilStep("No month sections were created", () => !sidebar.ChildrenOfType<MonthSection>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestYearsPanelVisibility()
|
||||||
|
{
|
||||||
|
AddUntilStep("Years panel is hidden", () => yearsPanel?.Alpha == 0);
|
||||||
|
AddStep("Add data", () => sidebar.Metadata.Value = getMetadata(2021));
|
||||||
|
AddUntilStep("Years panel is visible", () => yearsPanel?.Alpha == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onYearChanged(int year) => sidebar.Metadata.Value = getMetadata(year);
|
||||||
|
|
||||||
|
private YearsPanel yearsPanel => sidebar.ChildrenOfType<YearsPanel>().FirstOrDefault();
|
||||||
|
|
||||||
|
private APINewsSidebar getMetadata(int year) => new APINewsSidebar
|
||||||
|
{
|
||||||
|
CurrentYear = year,
|
||||||
|
Years = new[]
|
||||||
|
{
|
||||||
|
2021,
|
||||||
|
2020,
|
||||||
|
2019,
|
||||||
|
2018,
|
||||||
|
2017,
|
||||||
|
2016,
|
||||||
|
2015,
|
||||||
|
2014,
|
||||||
|
2013
|
||||||
|
},
|
||||||
|
NewsPosts = new List<APINewsPost>
|
||||||
|
{
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Mar) Short title",
|
||||||
|
PublishedAt = new DateTime(year, 3, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Mar) Oh boy that's a long post title I wonder if it will break anything",
|
||||||
|
PublishedAt = new DateTime(year, 3, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Mar) Medium title, nothing to see here",
|
||||||
|
PublishedAt = new DateTime(year, 3, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Feb) Short title",
|
||||||
|
PublishedAt = new DateTime(year, 2, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Feb) Oh boy that's a long post title I wonder if it will break anything",
|
||||||
|
PublishedAt = new DateTime(year, 2, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "(Feb) Medium title, nothing to see here",
|
||||||
|
PublishedAt = new DateTime(year, 2, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "Short title",
|
||||||
|
PublishedAt = new DateTime(year, 1, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "Oh boy that's a long post title I wonder if it will break anything",
|
||||||
|
PublishedAt = new DateTime(year, 1, 1)
|
||||||
|
},
|
||||||
|
new APINewsPost
|
||||||
|
{
|
||||||
|
Title = "Medium title, nothing to see here",
|
||||||
|
PublishedAt = new DateTime(year, 1, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private static readonly APINewsSidebar metadata_with_no_posts = new APINewsSidebar
|
||||||
|
{
|
||||||
|
CurrentYear = 2021,
|
||||||
|
Years = new[]
|
||||||
|
{
|
||||||
|
2021,
|
||||||
|
2020,
|
||||||
|
2019,
|
||||||
|
2018,
|
||||||
|
2017,
|
||||||
|
2016,
|
||||||
|
2015,
|
||||||
|
2014,
|
||||||
|
2013
|
||||||
|
},
|
||||||
|
NewsPosts = Array.Empty<APINewsPost>()
|
||||||
|
};
|
||||||
|
|
||||||
|
private class TestNewsSidebar : NewsSidebar
|
||||||
|
{
|
||||||
|
public Action<int> YearChanged;
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Metadata.BindValueChanged(metadata =>
|
||||||
|
{
|
||||||
|
foreach (var b in this.ChildrenOfType<YearButton>())
|
||||||
|
b.Action = () => YearChanged?.Invoke(b.Year);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -324,7 +324,7 @@ namespace osu.Game.Beatmaps
|
|||||||
public bool SkinLoaded => skin.IsResultAvailable;
|
public bool SkinLoaded => skin.IsResultAvailable;
|
||||||
public ISkin Skin => skin.Value;
|
public ISkin Skin => skin.Value;
|
||||||
|
|
||||||
protected virtual ISkin GetSkin() => new DefaultSkin(null);
|
protected virtual ISkin GetSkin() => new BeatmapSkin(BeatmapInfo);
|
||||||
private readonly RecyclableLazy<ISkin> skin;
|
private readonly RecyclableLazy<ISkin> skin;
|
||||||
|
|
||||||
public abstract Stream GetStream(string storagePath);
|
public abstract Stream GetStream(string storagePath);
|
||||||
|
@ -104,7 +104,6 @@ namespace osu.Game.Configuration
|
|||||||
SetDefault(OsuSetting.KeyOverlay, false);
|
SetDefault(OsuSetting.KeyOverlay, false);
|
||||||
SetDefault(OsuSetting.PositionalHitSounds, true);
|
SetDefault(OsuSetting.PositionalHitSounds, true);
|
||||||
SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
|
SetDefault(OsuSetting.AlwaysPlayFirstComboBreak, true);
|
||||||
SetDefault(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth);
|
|
||||||
|
|
||||||
SetDefault(OsuSetting.FloatingComments, false);
|
SetDefault(OsuSetting.FloatingComments, false);
|
||||||
|
|
||||||
@ -213,7 +212,6 @@ namespace osu.Game.Configuration
|
|||||||
KeyOverlay,
|
KeyOverlay,
|
||||||
PositionalHitSounds,
|
PositionalHitSounds,
|
||||||
AlwaysPlayFirstComboBreak,
|
AlwaysPlayFirstComboBreak,
|
||||||
ScoreMeter,
|
|
||||||
FloatingComments,
|
FloatingComments,
|
||||||
HUDVisibilityMode,
|
HUDVisibilityMode,
|
||||||
ShowProgressGraph,
|
ShowProgressGraph,
|
||||||
|
@ -1,37 +0,0 @@
|
|||||||
// 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.ComponentModel;
|
|
||||||
|
|
||||||
namespace osu.Game.Configuration
|
|
||||||
{
|
|
||||||
public enum ScoreMeterType
|
|
||||||
{
|
|
||||||
[Description("None")]
|
|
||||||
None,
|
|
||||||
|
|
||||||
[Description("Hit Error (left)")]
|
|
||||||
HitErrorLeft,
|
|
||||||
|
|
||||||
[Description("Hit Error (right)")]
|
|
||||||
HitErrorRight,
|
|
||||||
|
|
||||||
[Description("Hit Error (left+right)")]
|
|
||||||
HitErrorBoth,
|
|
||||||
|
|
||||||
[Description("Hit Error (bottom)")]
|
|
||||||
HitErrorBottom,
|
|
||||||
|
|
||||||
[Description("Colour (left)")]
|
|
||||||
ColourLeft,
|
|
||||||
|
|
||||||
[Description("Colour (right)")]
|
|
||||||
ColourRight,
|
|
||||||
|
|
||||||
[Description("Colour (left+right)")]
|
|
||||||
ColourBoth,
|
|
||||||
|
|
||||||
[Description("Colour (bottom)")]
|
|
||||||
ColourBottom,
|
|
||||||
}
|
|
||||||
}
|
|
@ -11,5 +11,8 @@ namespace osu.Game.Online.API.Requests
|
|||||||
{
|
{
|
||||||
[JsonProperty("news_posts")]
|
[JsonProperty("news_posts")]
|
||||||
public IEnumerable<APINewsPost> NewsPosts;
|
public IEnumerable<APINewsPost> NewsPosts;
|
||||||
|
|
||||||
|
[JsonProperty("news_sidebar")]
|
||||||
|
public APINewsSidebar SidebarMetadata;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
20
osu.Game/Online/API/Requests/Responses/APINewsSidebar.cs
Normal file
20
osu.Game/Online/API/Requests/Responses/APINewsSidebar.cs
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 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 Newtonsoft.Json;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests.Responses
|
||||||
|
{
|
||||||
|
public class APINewsSidebar
|
||||||
|
{
|
||||||
|
[JsonProperty("current_year")]
|
||||||
|
public int CurrentYear { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("news_posts")]
|
||||||
|
public IEnumerable<APINewsPost> NewsPosts { get; set; }
|
||||||
|
|
||||||
|
[JsonProperty("years")]
|
||||||
|
public int[] Years { get; set; }
|
||||||
|
}
|
||||||
|
}
|
179
osu.Game/Overlays/News/Sidebar/MonthSection.cs
Normal file
179
osu.Game/Overlays/News/Sidebar/MonthSection.cs
Normal file
@ -0,0 +1,179 @@
|
|||||||
|
// 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.Bindables;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osuTK;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.News.Sidebar
|
||||||
|
{
|
||||||
|
public class MonthSection : CompositeDrawable
|
||||||
|
{
|
||||||
|
private const int animation_duration = 250;
|
||||||
|
|
||||||
|
public readonly BindableBool Expanded = new BindableBool();
|
||||||
|
|
||||||
|
public MonthSection(int month, int year, IEnumerable<APINewsPost> posts)
|
||||||
|
{
|
||||||
|
Debug.Assert(posts.All(p => p.PublishedAt.Month == month && p.PublishedAt.Year == year));
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Masking = true;
|
||||||
|
|
||||||
|
InternalChild = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new DropdownHeader(month, year)
|
||||||
|
{
|
||||||
|
Expanded = { BindTarget = Expanded }
|
||||||
|
},
|
||||||
|
new PostsContainer
|
||||||
|
{
|
||||||
|
Expanded = { BindTarget = Expanded },
|
||||||
|
Children = posts.Select(p => new PostButton(p)).ToArray()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DropdownHeader : OsuClickableContainer
|
||||||
|
{
|
||||||
|
public readonly BindableBool Expanded = new BindableBool();
|
||||||
|
|
||||||
|
private readonly SpriteIcon icon;
|
||||||
|
|
||||||
|
public DropdownHeader(int month, int year)
|
||||||
|
{
|
||||||
|
var date = new DateTime(year, month, 1);
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Height = 15;
|
||||||
|
Action = Expanded.Toggle;
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
|
||||||
|
Text = date.ToString("MMM yyyy")
|
||||||
|
},
|
||||||
|
icon = new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Size = new Vector2(10),
|
||||||
|
Icon = FontAwesome.Solid.ChevronDown
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Expanded.BindValueChanged(open =>
|
||||||
|
{
|
||||||
|
icon.Scale = new Vector2(1, open.NewValue ? -1 : 1);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PostButton : OsuHoverContainer
|
||||||
|
{
|
||||||
|
protected override IEnumerable<Drawable> EffectTargets => new[] { text };
|
||||||
|
|
||||||
|
private readonly TextFlowContainer text;
|
||||||
|
private readonly APINewsPost post;
|
||||||
|
|
||||||
|
public PostButton(APINewsPost post)
|
||||||
|
{
|
||||||
|
this.post = post;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Child = text = new TextFlowContainer(t => t.Font = OsuFont.GetFont(size: 12))
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Text = post.Title
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider overlayColours, GameHost host)
|
||||||
|
{
|
||||||
|
IdleColour = overlayColours.Light2;
|
||||||
|
HoverColour = overlayColours.Light1;
|
||||||
|
|
||||||
|
TooltipText = "view in browser";
|
||||||
|
Action = () => host.OpenUrlExternally("https://osu.ppy.sh/home/news/" + post.Slug);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PostsContainer : Container
|
||||||
|
{
|
||||||
|
public readonly BindableBool Expanded = new BindableBool();
|
||||||
|
|
||||||
|
protected override Container<Drawable> Content { get; }
|
||||||
|
|
||||||
|
public PostsContainer()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
AutoSizeDuration = animation_duration;
|
||||||
|
AutoSizeEasing = Easing.Out;
|
||||||
|
InternalChild = Content = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Margin = new MarginPadding { Top = 5 },
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(0, 5),
|
||||||
|
Alpha = 0
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
Expanded.BindValueChanged(updateState, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState(ValueChangedEvent<bool> expanded)
|
||||||
|
{
|
||||||
|
ClearTransforms(true);
|
||||||
|
|
||||||
|
if (expanded.NewValue)
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Content.FadeIn(animation_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.None;
|
||||||
|
this.ResizeHeightTo(0, animation_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
Content.FadeOut(animation_duration, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
103
osu.Game/Overlays/News/Sidebar/NewsSidebar.cs
Normal file
103
osu.Game/Overlays/News/Sidebar/NewsSidebar.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
// 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.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osuTK;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.News.Sidebar
|
||||||
|
{
|
||||||
|
public class NewsSidebar : CompositeDrawable
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
public readonly Bindable<APINewsSidebar> Metadata = new Bindable<APINewsSidebar>();
|
||||||
|
|
||||||
|
private FillFlowContainer<MonthSection> monthsFlow;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Y;
|
||||||
|
Width = 250;
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = colourProvider.Background4
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding
|
||||||
|
{
|
||||||
|
Vertical = 20,
|
||||||
|
Left = 50,
|
||||||
|
Right = 30
|
||||||
|
},
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(0, 20),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new YearsPanel(),
|
||||||
|
monthsFlow = new FillFlowContainer<MonthSection>
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Spacing = new Vector2(0, 10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Metadata.BindValueChanged(onMetadataChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onMetadataChanged(ValueChangedEvent<APINewsSidebar> metadata)
|
||||||
|
{
|
||||||
|
monthsFlow.Clear();
|
||||||
|
|
||||||
|
if (metadata.NewValue == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var allPosts = metadata.NewValue.NewsPosts;
|
||||||
|
|
||||||
|
if (allPosts?.Any() != true)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var lookup = metadata.NewValue.NewsPosts.ToLookup(post => post.PublishedAt.Month);
|
||||||
|
|
||||||
|
var keys = lookup.Select(kvp => kvp.Key);
|
||||||
|
var sortedKeys = keys.OrderByDescending(k => k).ToList();
|
||||||
|
|
||||||
|
var year = metadata.NewValue.CurrentYear;
|
||||||
|
|
||||||
|
for (int i = 0; i < sortedKeys.Count; i++)
|
||||||
|
{
|
||||||
|
var month = sortedKeys[i];
|
||||||
|
var posts = lookup[month];
|
||||||
|
|
||||||
|
monthsFlow.Add(new MonthSection(month, year, posts)
|
||||||
|
{
|
||||||
|
Expanded = { Value = i == 0 }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
113
osu.Game/Overlays/News/Sidebar/YearsPanel.cs
Normal file
113
osu.Game/Overlays/News/Sidebar/YearsPanel.cs
Normal file
@ -0,0 +1,113 @@
|
|||||||
|
// 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.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.News.Sidebar
|
||||||
|
{
|
||||||
|
public class YearsPanel : CompositeDrawable
|
||||||
|
{
|
||||||
|
private readonly Bindable<APINewsSidebar> metadata = new Bindable<APINewsSidebar>();
|
||||||
|
|
||||||
|
private FillFlowContainer yearsFlow;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider overlayColours, Bindable<APINewsSidebar> metadata)
|
||||||
|
{
|
||||||
|
this.metadata.BindTo(metadata);
|
||||||
|
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = 6;
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = overlayColours.Background3
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding(5),
|
||||||
|
Child = yearsFlow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(0, 5)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
metadata.BindValueChanged(_ => recreateDrawables(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recreateDrawables()
|
||||||
|
{
|
||||||
|
yearsFlow.Clear();
|
||||||
|
|
||||||
|
if (metadata.Value == null)
|
||||||
|
{
|
||||||
|
Hide();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var currentYear = metadata.Value.CurrentYear;
|
||||||
|
|
||||||
|
foreach (var y in metadata.Value.Years)
|
||||||
|
yearsFlow.Add(new YearButton(y, y == currentYear));
|
||||||
|
|
||||||
|
Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class YearButton : OsuHoverContainer
|
||||||
|
{
|
||||||
|
public int Year { get; }
|
||||||
|
|
||||||
|
private readonly bool isCurrent;
|
||||||
|
|
||||||
|
public YearButton(int year, bool isCurrent)
|
||||||
|
{
|
||||||
|
Year = year;
|
||||||
|
this.isCurrent = isCurrent;
|
||||||
|
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Width = 0.25f;
|
||||||
|
Height = 15;
|
||||||
|
|
||||||
|
Child = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Font = OsuFont.GetFont(size: 12, weight: isCurrent ? FontWeight.SemiBold : FontWeight.Medium),
|
||||||
|
Text = year.ToString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider)
|
||||||
|
{
|
||||||
|
IdleColour = isCurrent ? Color4.White : colourProvider.Light2;
|
||||||
|
HoverColour = isCurrent ? Color4.White : colourProvider.Light1;
|
||||||
|
Action = () => { }; // Avoid button being disabled since there's no proper action assigned.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -73,11 +73,6 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
|||||||
LabelText = "Always play first combo break sound",
|
LabelText = "Always play first combo break sound",
|
||||||
Current = config.GetBindable<bool>(OsuSetting.AlwaysPlayFirstComboBreak)
|
Current = config.GetBindable<bool>(OsuSetting.AlwaysPlayFirstComboBreak)
|
||||||
},
|
},
|
||||||
new SettingsEnumDropdown<ScoreMeterType>
|
|
||||||
{
|
|
||||||
LabelText = "Score meter type",
|
|
||||||
Current = config.GetBindable<ScoreMeterType>(OsuSetting.ScoreMeter)
|
|
||||||
},
|
|
||||||
new SettingsEnumDropdown<ScoringMode>
|
new SettingsEnumDropdown<ScoringMode>
|
||||||
{
|
{
|
||||||
LabelText = "Score display mode",
|
LabelText = "Score display mode",
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using osu.Framework.Graphics.Performance;
|
using osu.Framework.Graphics.Performance;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
@ -18,7 +19,7 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The entry holding essential state of this <see cref="PoolableDrawableWithLifetime{TEntry}"/>.
|
/// The entry holding essential state of this <see cref="PoolableDrawableWithLifetime{TEntry}"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected TEntry? Entry { get; private set; }
|
public TEntry? Entry { get; private set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether <see cref="Entry"/> is applied to this <see cref="PoolableDrawableWithLifetime{TEntry}"/>.
|
/// Whether <see cref="Entry"/> is applied to this <see cref="PoolableDrawableWithLifetime{TEntry}"/>.
|
||||||
@ -28,14 +29,28 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
|
|
||||||
public override double LifetimeStart
|
public override double LifetimeStart
|
||||||
{
|
{
|
||||||
get => base.LifetimeStart;
|
get => Entry?.LifetimeStart ?? double.MinValue;
|
||||||
set => setLifetime(value, LifetimeEnd);
|
set
|
||||||
|
{
|
||||||
|
if (Entry == null && LifetimeStart != value)
|
||||||
|
throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime<TEntry>)} when entry is not set");
|
||||||
|
|
||||||
|
if (Entry != null)
|
||||||
|
Entry.LifetimeStart = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override double LifetimeEnd
|
public override double LifetimeEnd
|
||||||
{
|
{
|
||||||
get => base.LifetimeEnd;
|
get => Entry?.LifetimeEnd ?? double.MaxValue;
|
||||||
set => setLifetime(LifetimeStart, value);
|
set
|
||||||
|
{
|
||||||
|
if (Entry == null && LifetimeEnd != value)
|
||||||
|
throw new InvalidOperationException($"Cannot modify lifetime of {nameof(PoolableDrawableWithLifetime<TEntry>)} when entry is not set");
|
||||||
|
|
||||||
|
if (Entry != null)
|
||||||
|
Entry.LifetimeEnd = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool RemoveWhenNotAlive => false;
|
public override bool RemoveWhenNotAlive => false;
|
||||||
@ -64,11 +79,8 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
if (HasEntryApplied)
|
if (HasEntryApplied)
|
||||||
free();
|
free();
|
||||||
|
|
||||||
setLifetime(entry.LifetimeStart, entry.LifetimeEnd);
|
|
||||||
Entry = entry;
|
Entry = entry;
|
||||||
|
|
||||||
OnApply(entry);
|
OnApply(entry);
|
||||||
|
|
||||||
HasEntryApplied = true;
|
HasEntryApplied = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,27 +107,12 @@ namespace osu.Game.Rulesets.Objects.Pooling
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setLifetime(double start, double end)
|
|
||||||
{
|
|
||||||
base.LifetimeStart = start;
|
|
||||||
base.LifetimeEnd = end;
|
|
||||||
|
|
||||||
if (Entry != null)
|
|
||||||
{
|
|
||||||
Entry.LifetimeStart = start;
|
|
||||||
Entry.LifetimeEnd = end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void free()
|
private void free()
|
||||||
{
|
{
|
||||||
Debug.Assert(Entry != null && HasEntryApplied);
|
Debug.Assert(Entry != null && HasEntryApplied);
|
||||||
|
|
||||||
OnFree(Entry);
|
OnFree(Entry);
|
||||||
|
|
||||||
Entry = null;
|
Entry = null;
|
||||||
setLifetime(double.MaxValue, double.MaxValue);
|
|
||||||
|
|
||||||
HasEntryApplied = false;
|
HasEntryApplied = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,18 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
public class HitObjectContainer : LifetimeManagementContainer, IHitObjectContainer
|
public class HitObjectContainer : CompositeDrawable, IHitObjectContainer
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// All entries in this <see cref="HitObjectContainer"/> including dead entries.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<HitObjectLifetimeEntry> Entries => allEntries;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All alive entries and <see cref="DrawableHitObject"/>s used by the entries.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<(HitObjectLifetimeEntry Entry, DrawableHitObject Drawable)> AliveEntries => aliveDrawableMap.Select(x => (x.Key, x.Value));
|
||||||
|
|
||||||
public IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
public IEnumerable<DrawableHitObject> Objects => InternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
||||||
|
|
||||||
public IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
public IEnumerable<DrawableHitObject> AliveObjects => AliveInternalChildren.Cast<DrawableHitObject>().OrderBy(h => h.HitObject.StartTime);
|
||||||
@ -60,8 +70,12 @@ namespace osu.Game.Rulesets.UI
|
|||||||
internal double FutureLifetimeExtension { get; set; }
|
internal double FutureLifetimeExtension { get; set; }
|
||||||
|
|
||||||
private readonly Dictionary<DrawableHitObject, IBindable> startTimeMap = new Dictionary<DrawableHitObject, IBindable>();
|
private readonly Dictionary<DrawableHitObject, IBindable> startTimeMap = new Dictionary<DrawableHitObject, IBindable>();
|
||||||
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> drawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
|
|
||||||
|
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> aliveDrawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
|
||||||
|
private readonly Dictionary<HitObjectLifetimeEntry, DrawableHitObject> nonPooledDrawableMap = new Dictionary<HitObjectLifetimeEntry, DrawableHitObject>();
|
||||||
|
|
||||||
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
private readonly LifetimeEntryManager lifetimeManager = new LifetimeEntryManager();
|
||||||
|
private readonly HashSet<HitObjectLifetimeEntry> allEntries = new HashSet<HitObjectLifetimeEntry>();
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
|
private IPooledHitObjectProvider pooledObjectProvider { get; set; }
|
||||||
@ -72,6 +86,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
lifetimeManager.EntryBecameAlive += entryBecameAlive;
|
lifetimeManager.EntryBecameAlive += entryBecameAlive;
|
||||||
lifetimeManager.EntryBecameDead += entryBecameDead;
|
lifetimeManager.EntryBecameDead += entryBecameDead;
|
||||||
|
lifetimeManager.EntryCrossedBoundary += entryCrossedBoundary;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadAsyncComplete()
|
protected override void LoadAsyncComplete()
|
||||||
@ -84,93 +99,113 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
#region Pooling support
|
#region Pooling support
|
||||||
|
|
||||||
public void Add(HitObjectLifetimeEntry entry) => lifetimeManager.AddEntry(entry);
|
public void Add(HitObjectLifetimeEntry entry)
|
||||||
|
|
||||||
public bool Remove(HitObjectLifetimeEntry entry) => lifetimeManager.RemoveEntry(entry);
|
|
||||||
|
|
||||||
private void entryBecameAlive(LifetimeEntry entry) => addDrawable((HitObjectLifetimeEntry)entry);
|
|
||||||
|
|
||||||
private void entryBecameDead(LifetimeEntry entry) => removeDrawable((HitObjectLifetimeEntry)entry);
|
|
||||||
|
|
||||||
private void addDrawable(HitObjectLifetimeEntry entry)
|
|
||||||
{
|
{
|
||||||
Debug.Assert(!drawableMap.ContainsKey(entry));
|
allEntries.Add(entry);
|
||||||
|
lifetimeManager.AddEntry(entry);
|
||||||
|
}
|
||||||
|
|
||||||
var drawable = pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null);
|
public bool Remove(HitObjectLifetimeEntry entry)
|
||||||
|
{
|
||||||
|
if (!lifetimeManager.RemoveEntry(entry)) return false;
|
||||||
|
|
||||||
|
// This logic is not in `Remove(DrawableHitObject)` because a non-pooled drawable may be removed by specifying its entry.
|
||||||
|
if (nonPooledDrawableMap.Remove(entry, out var drawable))
|
||||||
|
removeDrawable(drawable);
|
||||||
|
|
||||||
|
allEntries.Remove(entry);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void entryBecameAlive(LifetimeEntry lifetimeEntry)
|
||||||
|
{
|
||||||
|
var entry = (HitObjectLifetimeEntry)lifetimeEntry;
|
||||||
|
Debug.Assert(!aliveDrawableMap.ContainsKey(entry));
|
||||||
|
|
||||||
|
bool isNonPooled = nonPooledDrawableMap.TryGetValue(entry, out var drawable);
|
||||||
|
drawable ??= pooledObjectProvider?.GetPooledDrawableRepresentation(entry.HitObject, null);
|
||||||
if (drawable == null)
|
if (drawable == null)
|
||||||
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
throw new InvalidOperationException($"A drawable representation could not be retrieved for hitobject type: {entry.HitObject.GetType().ReadableName()}.");
|
||||||
|
|
||||||
|
aliveDrawableMap[entry] = drawable;
|
||||||
|
OnAdd(drawable);
|
||||||
|
|
||||||
|
if (isNonPooled) return;
|
||||||
|
|
||||||
|
addDrawable(drawable);
|
||||||
|
HitObjectUsageBegan?.Invoke(entry.HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void entryBecameDead(LifetimeEntry lifetimeEntry)
|
||||||
|
{
|
||||||
|
var entry = (HitObjectLifetimeEntry)lifetimeEntry;
|
||||||
|
Debug.Assert(aliveDrawableMap.ContainsKey(entry));
|
||||||
|
|
||||||
|
var drawable = aliveDrawableMap[entry];
|
||||||
|
bool isNonPooled = nonPooledDrawableMap.ContainsKey(entry);
|
||||||
|
|
||||||
|
drawable.OnKilled();
|
||||||
|
aliveDrawableMap.Remove(entry);
|
||||||
|
OnRemove(drawable);
|
||||||
|
|
||||||
|
if (isNonPooled) return;
|
||||||
|
|
||||||
|
removeDrawable(drawable);
|
||||||
|
// The hit object is not freed when the DHO was not pooled.
|
||||||
|
HitObjectUsageFinished?.Invoke(entry.HitObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addDrawable(DrawableHitObject drawable)
|
||||||
|
{
|
||||||
drawable.OnNewResult += onNewResult;
|
drawable.OnNewResult += onNewResult;
|
||||||
drawable.OnRevertResult += onRevertResult;
|
drawable.OnRevertResult += onRevertResult;
|
||||||
|
|
||||||
bindStartTime(drawable);
|
bindStartTime(drawable);
|
||||||
AddInternal(drawableMap[entry] = drawable, false);
|
AddInternal(drawable);
|
||||||
OnAdd(drawable);
|
|
||||||
|
|
||||||
HitObjectUsageBegan?.Invoke(entry.HitObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void removeDrawable(HitObjectLifetimeEntry entry)
|
private void removeDrawable(DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
Debug.Assert(drawableMap.ContainsKey(entry));
|
|
||||||
|
|
||||||
var drawable = drawableMap[entry];
|
|
||||||
|
|
||||||
// OnKilled can potentially change the hitobject's result, so it needs to run first before unbinding.
|
|
||||||
drawable.OnKilled();
|
|
||||||
drawable.OnNewResult -= onNewResult;
|
drawable.OnNewResult -= onNewResult;
|
||||||
drawable.OnRevertResult -= onRevertResult;
|
drawable.OnRevertResult -= onRevertResult;
|
||||||
|
|
||||||
drawableMap.Remove(entry);
|
|
||||||
|
|
||||||
OnRemove(drawable);
|
|
||||||
unbindStartTime(drawable);
|
unbindStartTime(drawable);
|
||||||
RemoveInternal(drawable);
|
|
||||||
|
|
||||||
HitObjectUsageFinished?.Invoke(entry.HitObject);
|
RemoveInternal(drawable);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
#region Non-pooling support
|
#region Non-pooling support
|
||||||
|
|
||||||
public virtual void Add(DrawableHitObject hitObject)
|
public virtual void Add(DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
bindStartTime(hitObject);
|
if (drawable.Entry == null)
|
||||||
|
throw new InvalidOperationException($"May not add a {nameof(DrawableHitObject)} without {nameof(HitObject)} associated");
|
||||||
|
|
||||||
hitObject.OnNewResult += onNewResult;
|
nonPooledDrawableMap.Add(drawable.Entry, drawable);
|
||||||
hitObject.OnRevertResult += onRevertResult;
|
addDrawable(drawable);
|
||||||
|
Add(drawable.Entry);
|
||||||
AddInternal(hitObject);
|
|
||||||
OnAdd(hitObject);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual bool Remove(DrawableHitObject hitObject)
|
public virtual bool Remove(DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
OnRemove(hitObject);
|
if (drawable.Entry == null)
|
||||||
if (!RemoveInternal(hitObject))
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
hitObject.OnNewResult -= onNewResult;
|
return Remove(drawable.Entry);
|
||||||
hitObject.OnRevertResult -= onRevertResult;
|
|
||||||
|
|
||||||
unbindStartTime(hitObject);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject);
|
public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject);
|
||||||
|
|
||||||
protected override void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e)
|
private void entryCrossedBoundary(LifetimeEntry entry, LifetimeBoundaryKind kind, LifetimeBoundaryCrossingDirection direction)
|
||||||
{
|
{
|
||||||
if (!(e.Child is DrawableHitObject hitObject))
|
if (nonPooledDrawableMap.TryGetValue((HitObjectLifetimeEntry)entry, out var drawable))
|
||||||
return;
|
OnChildLifetimeBoundaryCrossed(new LifetimeBoundaryCrossedEvent(drawable, kind, direction));
|
||||||
|
|
||||||
if ((e.Kind == LifetimeBoundaryKind.End && e.Direction == LifetimeBoundaryCrossingDirection.Forward)
|
|
||||||
|| (e.Kind == LifetimeBoundaryKind.Start && e.Direction == LifetimeBoundaryCrossingDirection.Backward))
|
|
||||||
{
|
|
||||||
hitObject.OnKilled();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void OnChildLifetimeBoundaryCrossed(LifetimeBoundaryCrossedEvent e)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -195,12 +230,13 @@ namespace osu.Game.Rulesets.UI
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual void Clear(bool disposeChildren = true)
|
public virtual void Clear()
|
||||||
{
|
{
|
||||||
lifetimeManager.ClearEntries();
|
lifetimeManager.ClearEntries();
|
||||||
|
foreach (var drawable in nonPooledDrawableMap.Values)
|
||||||
ClearInternal(disposeChildren);
|
removeDrawable(drawable);
|
||||||
unbindAllStartTimes();
|
nonPooledDrawableMap.Clear();
|
||||||
|
Debug.Assert(InternalChildren.Count == 0 && startTimeMap.Count == 0 && aliveDrawableMap.Count == 0, "All hit objects should have been removed");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool CheckChildrenLife()
|
protected override bool CheckChildrenLife()
|
||||||
|
@ -50,9 +50,9 @@ namespace osu.Game.Rulesets.UI.Scrolling
|
|||||||
timeRange.ValueChanged += _ => layoutCache.Invalidate();
|
timeRange.ValueChanged += _ => layoutCache.Invalidate();
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Clear(bool disposeChildren = true)
|
public override void Clear()
|
||||||
{
|
{
|
||||||
base.Clear(disposeChildren);
|
base.Clear();
|
||||||
|
|
||||||
toComputeLifetime.Clear();
|
toComputeLifetime.Clear();
|
||||||
layoutComputed.Clear();
|
layoutComputed.Clear();
|
||||||
|
@ -1,127 +0,0 @@
|
|||||||
// 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.IEnumerableExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Configuration;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play.HUD
|
|
||||||
{
|
|
||||||
public class HitErrorDisplay : Container<HitErrorMeter>
|
|
||||||
{
|
|
||||||
private const int fade_duration = 200;
|
|
||||||
private const int margin = 10;
|
|
||||||
|
|
||||||
private readonly Bindable<ScoreMeterType> type = new Bindable<ScoreMeterType>();
|
|
||||||
|
|
||||||
private readonly HitWindows hitWindows;
|
|
||||||
|
|
||||||
public HitErrorDisplay(HitWindows hitWindows)
|
|
||||||
{
|
|
||||||
this.hitWindows = hitWindows;
|
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuConfigManager config)
|
|
||||||
{
|
|
||||||
config.BindWith(OsuSetting.ScoreMeter, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
type.BindValueChanged(typeChanged, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void typeChanged(ValueChangedEvent<ScoreMeterType> type)
|
|
||||||
{
|
|
||||||
Children.ForEach(c => c.FadeOut(fade_duration, Easing.OutQuint));
|
|
||||||
|
|
||||||
if (hitWindows == null)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (type.NewValue)
|
|
||||||
{
|
|
||||||
case ScoreMeterType.HitErrorBoth:
|
|
||||||
createBar(Anchor.CentreLeft);
|
|
||||||
createBar(Anchor.CentreRight);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScoreMeterType.HitErrorLeft:
|
|
||||||
createBar(Anchor.CentreLeft);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScoreMeterType.HitErrorRight:
|
|
||||||
createBar(Anchor.CentreRight);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScoreMeterType.HitErrorBottom:
|
|
||||||
createBar(Anchor.BottomCentre);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScoreMeterType.ColourBoth:
|
|
||||||
createColour(Anchor.CentreLeft);
|
|
||||||
createColour(Anchor.CentreRight);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScoreMeterType.ColourLeft:
|
|
||||||
createColour(Anchor.CentreLeft);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScoreMeterType.ColourRight:
|
|
||||||
createColour(Anchor.CentreRight);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case ScoreMeterType.ColourBottom:
|
|
||||||
createColour(Anchor.BottomCentre);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createBar(Anchor anchor)
|
|
||||||
{
|
|
||||||
bool rightAligned = (anchor & Anchor.x2) > 0;
|
|
||||||
bool bottomAligned = (anchor & Anchor.y2) > 0;
|
|
||||||
|
|
||||||
var display = new BarHitErrorMeter(hitWindows, rightAligned)
|
|
||||||
{
|
|
||||||
Margin = new MarginPadding(margin),
|
|
||||||
Anchor = anchor,
|
|
||||||
Origin = bottomAligned ? Anchor.CentreLeft : anchor,
|
|
||||||
Alpha = 0,
|
|
||||||
Rotation = bottomAligned ? 270 : 0
|
|
||||||
};
|
|
||||||
|
|
||||||
completeDisplayLoading(display);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void createColour(Anchor anchor)
|
|
||||||
{
|
|
||||||
bool bottomAligned = (anchor & Anchor.y2) > 0;
|
|
||||||
|
|
||||||
var display = new ColourHitErrorMeter(hitWindows)
|
|
||||||
{
|
|
||||||
Margin = new MarginPadding(margin),
|
|
||||||
Anchor = anchor,
|
|
||||||
Origin = bottomAligned ? Anchor.CentreLeft : anchor,
|
|
||||||
Alpha = 0,
|
|
||||||
Rotation = bottomAligned ? 270 : 0
|
|
||||||
};
|
|
||||||
|
|
||||||
completeDisplayLoading(display);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void completeDisplayLoading(HitErrorMeter display)
|
|
||||||
{
|
|
||||||
Add(display);
|
|
||||||
display.FadeInFromZero(fade_duration, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -20,8 +20,6 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
|||||||
{
|
{
|
||||||
public class BarHitErrorMeter : HitErrorMeter
|
public class BarHitErrorMeter : HitErrorMeter
|
||||||
{
|
{
|
||||||
private readonly Anchor alignment;
|
|
||||||
|
|
||||||
private const int arrow_move_duration = 400;
|
private const int arrow_move_duration = 400;
|
||||||
|
|
||||||
private const int judgement_line_width = 6;
|
private const int judgement_line_width = 6;
|
||||||
@ -43,11 +41,8 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
|||||||
|
|
||||||
private double maxHitWindow;
|
private double maxHitWindow;
|
||||||
|
|
||||||
public BarHitErrorMeter(HitWindows hitWindows, bool rightAligned = false)
|
public BarHitErrorMeter()
|
||||||
: base(hitWindows)
|
|
||||||
{
|
{
|
||||||
alignment = rightAligned ? Anchor.x0 : Anchor.x2;
|
|
||||||
|
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,33 +58,42 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
|||||||
Margin = new MarginPadding(2),
|
Margin = new MarginPadding(2),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
judgementsContainer = new Container
|
new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.y1 | alignment,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.y1 | alignment,
|
Origin = Anchor.CentreLeft,
|
||||||
Width = judgement_line_width,
|
Width = chevron_size,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
|
Child = arrow = new SpriteIcon
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativePositionAxes = Axes.Y,
|
||||||
|
Y = 0.5f,
|
||||||
|
Icon = FontAwesome.Solid.ChevronRight,
|
||||||
|
Size = new Vector2(chevron_size),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
colourBars = new Container
|
colourBars = new Container
|
||||||
{
|
{
|
||||||
Width = bar_width,
|
Width = bar_width,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Anchor = Anchor.y1 | alignment,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.y1 | alignment,
|
Origin = Anchor.CentreLeft,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
colourBarsEarly = new Container
|
colourBarsEarly = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.y1 | alignment,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = alignment,
|
Origin = Anchor.TopRight,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Height = 0.5f,
|
Height = 0.5f,
|
||||||
Scale = new Vector2(1, -1),
|
Scale = new Vector2(1, -1),
|
||||||
},
|
},
|
||||||
colourBarsLate = new Container
|
colourBarsLate = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.y1 | alignment,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = alignment,
|
Origin = Anchor.TopRight,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Height = 0.5f,
|
Height = 0.5f,
|
||||||
},
|
},
|
||||||
@ -115,21 +119,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Container
|
judgementsContainer = new Container
|
||||||
{
|
{
|
||||||
Anchor = Anchor.y1 | alignment,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.y1 | alignment,
|
Origin = Anchor.CentreLeft,
|
||||||
Width = chevron_size,
|
Width = judgement_line_width,
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
Child = arrow = new SpriteIcon
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativePositionAxes = Axes.Y,
|
|
||||||
Y = 0.5f,
|
|
||||||
Icon = alignment == Anchor.x2 ? FontAwesome.Solid.ChevronRight : FontAwesome.Solid.ChevronLeft,
|
|
||||||
Size = new Vector2(chevron_size),
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -152,19 +147,22 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
|||||||
{
|
{
|
||||||
var windows = HitWindows.GetAllAvailableWindows().ToArray();
|
var windows = HitWindows.GetAllAvailableWindows().ToArray();
|
||||||
|
|
||||||
maxHitWindow = windows.First().length;
|
// max to avoid div-by-zero.
|
||||||
|
maxHitWindow = Math.Max(1, windows.First().length);
|
||||||
|
|
||||||
for (var i = 0; i < windows.Length; i++)
|
for (var i = 0; i < windows.Length; i++)
|
||||||
{
|
{
|
||||||
var (result, length) = windows[i];
|
var (result, length) = windows[i];
|
||||||
|
|
||||||
colourBarsEarly.Add(createColourBar(result, (float)(length / maxHitWindow), i == 0));
|
var hitWindow = (float)(length / maxHitWindow);
|
||||||
colourBarsLate.Add(createColourBar(result, (float)(length / maxHitWindow), i == 0));
|
|
||||||
|
colourBarsEarly.Add(createColourBar(result, hitWindow, i == 0));
|
||||||
|
colourBarsLate.Add(createColourBar(result, hitWindow, i == 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
// a little nub to mark the centre point.
|
// a little nub to mark the centre point.
|
||||||
var centre = createColourBar(windows.Last().result, 0.01f);
|
var centre = createColourBar(windows.Last().result, 0.01f);
|
||||||
centre.Anchor = centre.Origin = Anchor.y1 | (alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2);
|
centre.Anchor = centre.Origin = Anchor.CentreLeft;
|
||||||
centre.Width = 2.5f;
|
centre.Width = 2.5f;
|
||||||
colourBars.Add(centre);
|
colourBars.Add(centre);
|
||||||
|
|
||||||
@ -236,8 +234,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
|||||||
judgementsContainer.Add(new JudgementLine
|
judgementsContainer.Add(new JudgementLine
|
||||||
{
|
{
|
||||||
Y = getRelativeJudgementPosition(judgement.TimeOffset),
|
Y = getRelativeJudgementPosition(judgement.TimeOffset),
|
||||||
Anchor = alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2,
|
Origin = Anchor.CentreLeft,
|
||||||
Origin = Anchor.y1 | (alignment == Anchor.x2 ? Anchor.x0 : Anchor.x2),
|
|
||||||
});
|
});
|
||||||
|
|
||||||
arrow.MoveToY(
|
arrow.MoveToY(
|
||||||
|
@ -7,7 +7,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -19,8 +18,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
|||||||
|
|
||||||
private readonly JudgementFlow judgementsFlow;
|
private readonly JudgementFlow judgementsFlow;
|
||||||
|
|
||||||
public ColourHitErrorMeter(HitWindows hitWindows)
|
public ColourHitErrorMeter()
|
||||||
: base(hitWindows)
|
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
InternalChild = judgementsFlow = new JudgementFlow();
|
InternalChild = judgementsFlow = new JudgementFlow();
|
||||||
|
@ -6,13 +6,15 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Skinning;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
||||||
{
|
{
|
||||||
public abstract class HitErrorMeter : CompositeDrawable
|
public abstract class HitErrorMeter : CompositeDrawable, ISkinnableDrawable
|
||||||
{
|
{
|
||||||
protected readonly HitWindows HitWindows;
|
protected HitWindows HitWindows { get; private set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private ScoreProcessor processor { get; set; }
|
private ScoreProcessor processor { get; set; }
|
||||||
@ -20,9 +22,10 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private OsuColour colours { get; set; }
|
||||||
|
|
||||||
protected HitErrorMeter(HitWindows hitWindows)
|
[BackgroundDependencyLoader(true)]
|
||||||
|
private void load(DrawableRuleset drawableRuleset)
|
||||||
{
|
{
|
||||||
HitWindows = hitWindows;
|
HitWindows = drawableRuleset?.FirstAvailableHitWindows ?? HitWindows.Empty;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -87,22 +87,10 @@ namespace osu.Game.Screens.Play
|
|||||||
visibilityContainer = new Container
|
visibilityContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = new Drawable[]
|
Child = mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents)
|
||||||
{
|
|
||||||
mainComponents = new SkinnableTargetContainer(SkinnableTarget.MainHUDComponents)
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
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),
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
topRightElements = new FillFlowContainer
|
topRightElements = new FillFlowContainer
|
||||||
{
|
{
|
||||||
|
@ -20,10 +20,14 @@ namespace osu.Game.Screens.Play
|
|||||||
{
|
{
|
||||||
public class SongProgress : OverlayContainer, ISkinnableDrawable
|
public class SongProgress : OverlayContainer, ISkinnableDrawable
|
||||||
{
|
{
|
||||||
private const int info_height = 20;
|
public const float MAX_HEIGHT = info_height + bottom_bar_height + graph_height + handle_height;
|
||||||
private const int bottom_bar_height = 5;
|
|
||||||
|
private const float info_height = 20;
|
||||||
|
private const float bottom_bar_height = 5;
|
||||||
private const float graph_height = SquareGraph.Column.WIDTH * 6;
|
private const float graph_height = SquareGraph.Column.WIDTH * 6;
|
||||||
private static readonly Vector2 handle_size = new Vector2(10, 18);
|
private const float handle_height = 18;
|
||||||
|
|
||||||
|
private static readonly Vector2 handle_size = new Vector2(10, handle_height);
|
||||||
|
|
||||||
private const float transition_duration = 200;
|
private const float transition_duration = 200;
|
||||||
|
|
||||||
|
32
osu.Game/Skinning/BeatmapSkin.cs
Normal file
32
osu.Game/Skinning/BeatmapSkin.cs
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
// 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.Audio.Sample;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Game.Audio;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Skinning
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An empty implementation of a beatmap skin, serves as a temporary default for <see cref="WorkingBeatmap"/>s.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This should be removed once <see cref="Skin"/> becomes instantiable or a new skin type for osu!lazer beatmaps is defined.
|
||||||
|
/// </remarks>
|
||||||
|
public class BeatmapSkin : Skin
|
||||||
|
{
|
||||||
|
public BeatmapSkin(BeatmapInfo beatmap)
|
||||||
|
: base(BeatmapSkinExtensions.CreateSkinInfo(beatmap), null)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
|
||||||
|
|
||||||
|
public override IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => null;
|
||||||
|
|
||||||
|
public override ISample GetSample(ISampleInfo sampleInfo) => null;
|
||||||
|
}
|
||||||
|
}
|
16
osu.Game/Skinning/BeatmapSkinExtensions.cs
Normal file
16
osu.Game/Skinning/BeatmapSkinExtensions.cs
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// 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.Game.Beatmaps;
|
||||||
|
|
||||||
|
namespace osu.Game.Skinning
|
||||||
|
{
|
||||||
|
public static class BeatmapSkinExtensions
|
||||||
|
{
|
||||||
|
public static SkinInfo CreateSkinInfo(BeatmapInfo beatmap) => new SkinInfo
|
||||||
|
{
|
||||||
|
Name = beatmap.ToString(),
|
||||||
|
Creator = beatmap.Metadata?.AuthorString,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -14,6 +14,7 @@ using osu.Game.Extensions;
|
|||||||
using osu.Game.IO;
|
using osu.Game.IO;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -78,6 +79,24 @@ namespace osu.Game.Skinning
|
|||||||
combo.Position = new Vector2(accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding, vertical_offset + 5);
|
combo.Position = new Vector2(accuracy.ScreenSpaceDeltaToParentSpace(score.ScreenSpaceDrawQuad.Size).X / 2 + horizontal_padding, vertical_offset + 5);
|
||||||
combo.Anchor = Anchor.TopCentre;
|
combo.Anchor = Anchor.TopCentre;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var hitError = container.OfType<HitErrorMeter>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (hitError != null)
|
||||||
|
{
|
||||||
|
hitError.Anchor = Anchor.CentreLeft;
|
||||||
|
hitError.Origin = Anchor.CentreLeft;
|
||||||
|
}
|
||||||
|
|
||||||
|
var hitError2 = container.OfType<HitErrorMeter>().LastOrDefault();
|
||||||
|
|
||||||
|
if (hitError2 != null)
|
||||||
|
{
|
||||||
|
hitError2.Anchor = Anchor.CentreRight;
|
||||||
|
hitError2.Scale = new Vector2(-1, 1);
|
||||||
|
// origin flipped to match scale above.
|
||||||
|
hitError2.Origin = Anchor.CentreLeft;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
@ -88,6 +107,8 @@ namespace osu.Game.Skinning
|
|||||||
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)),
|
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)),
|
||||||
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)),
|
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)),
|
||||||
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)),
|
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)),
|
||||||
|
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.BarHitErrorMeter)),
|
||||||
|
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.BarHitErrorMeter)),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -114,6 +135,12 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
case HUDSkinComponents.SongProgress:
|
case HUDSkinComponents.SongProgress:
|
||||||
return new SongProgress();
|
return new SongProgress();
|
||||||
|
|
||||||
|
case HUDSkinComponents.BarHitErrorMeter:
|
||||||
|
return new BarHitErrorMeter();
|
||||||
|
|
||||||
|
case HUDSkinComponents.ColourHitErrorMeter:
|
||||||
|
return new ColourHitErrorMeter();
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -10,5 +10,7 @@ namespace osu.Game.Skinning
|
|||||||
AccuracyCounter,
|
AccuracyCounter,
|
||||||
HealthDisplay,
|
HealthDisplay,
|
||||||
SongProgress,
|
SongProgress,
|
||||||
|
BarHitErrorMeter,
|
||||||
|
ColourHitErrorMeter,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Skinning
|
|||||||
protected override bool UseCustomSampleBanks => true;
|
protected override bool UseCustomSampleBanks => true;
|
||||||
|
|
||||||
public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore<byte[]> storage, IStorageResourceProvider resources)
|
public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore<byte[]> storage, IStorageResourceProvider resources)
|
||||||
: base(createSkinInfo(beatmap), new LegacySkinResourceStore<BeatmapSetFileInfo>(beatmap.BeatmapSet, storage), resources, beatmap.Path)
|
: base(BeatmapSkinExtensions.CreateSkinInfo(beatmap), new LegacySkinResourceStore<BeatmapSetFileInfo>(beatmap.BeatmapSet, storage), resources, beatmap.Path)
|
||||||
{
|
{
|
||||||
// Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer)
|
// Disallow default colours fallback on beatmap skins to allow using parent skin combo colours. (via SkinProvidingContainer)
|
||||||
Configuration.AllowDefaultComboColoursFallback = false;
|
Configuration.AllowDefaultComboColoursFallback = false;
|
||||||
@ -26,12 +26,18 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
public override Drawable GetDrawableComponent(ISkinComponent component)
|
public override Drawable GetDrawableComponent(ISkinComponent component)
|
||||||
{
|
{
|
||||||
if (component is SkinnableTargetComponent targetComponent && targetComponent.Target == SkinnableTarget.MainHUDComponents)
|
if (component is SkinnableTargetComponent targetComponent)
|
||||||
{
|
{
|
||||||
// for now, if the beatmap skin doesn't skin the score font, fall back to current skin
|
switch (targetComponent.Target)
|
||||||
// instead of potentially returning default lazer skin HUD components from here.
|
{
|
||||||
|
case SkinnableTarget.MainHUDComponents:
|
||||||
|
// this should exist in LegacySkin instead, but there isn't a fallback skin for LegacySkins yet.
|
||||||
|
// therefore keep the check here until fallback default legacy skin is supported.
|
||||||
if (!this.HasFont(LegacyFont.Score))
|
if (!this.HasFont(LegacyFont.Score))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.GetDrawableComponent(component);
|
return base.GetDrawableComponent(component);
|
||||||
@ -63,8 +69,5 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
return base.GetSample(sampleInfo);
|
return base.GetSample(sampleInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>
|
|
||||||
new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() };
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ using osu.Game.IO;
|
|||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Screens.Play;
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
@ -341,6 +342,20 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y;
|
accuracy.Y = container.ToLocalSpace(score.ScreenSpaceDrawQuad.BottomRight).Y;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var songProgress = container.OfType<SongProgress>().FirstOrDefault();
|
||||||
|
|
||||||
|
var hitError = container.OfType<HitErrorMeter>().FirstOrDefault();
|
||||||
|
|
||||||
|
if (hitError != null)
|
||||||
|
{
|
||||||
|
hitError.Anchor = Anchor.BottomCentre;
|
||||||
|
hitError.Origin = Anchor.CentreLeft;
|
||||||
|
hitError.Rotation = -90;
|
||||||
|
|
||||||
|
if (songProgress != null)
|
||||||
|
hitError.Y -= SongProgress.MAX_HEIGHT;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
Children = new[]
|
Children = new[]
|
||||||
@ -351,6 +366,7 @@ namespace osu.Game.Skinning
|
|||||||
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)) ?? new DefaultAccuracyCounter(),
|
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.AccuracyCounter)) ?? new DefaultAccuracyCounter(),
|
||||||
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)) ?? new DefaultHealthDisplay(),
|
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.HealthDisplay)) ?? new DefaultHealthDisplay(),
|
||||||
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)) ?? new SongProgress(),
|
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.SongProgress)) ?? new SongProgress(),
|
||||||
|
GetDrawableComponent(new HUDSkinComponent(HUDSkinComponents.BarHitErrorMeter)) ?? new BarHitErrorMeter(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user