1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-27 14:12:56 +08:00

Merge remote-tracking branch 'upstream/master' into taiko-don

This commit is contained in:
Craftplacer 2020-04-27 02:13:09 +02:00
commit e830c7d833
51 changed files with 762 additions and 317 deletions

View File

@ -41,8 +41,6 @@ namespace osu.Game.Rulesets.Mania.Tests
AccentColour = Color4.OrangeRed, AccentColour = Color4.OrangeRed,
Clock = new FramedClock(new StopwatchClock()), // No scroll Clock = new FramedClock(new StopwatchClock()), // No scroll
}); });
AddStep("change direction", () => ((ScrollingTestContainer)HitObjectContainer).Flip());
} }
protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both }; protected override Container CreateHitObjectContainer() => new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both };

View File

@ -1,17 +1,59 @@
// 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.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Testing;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints; using osu.Game.Rulesets.Mania.Edit.Blueprints;
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;
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.Scrolling;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Input;
namespace osu.Game.Rulesets.Mania.Tests namespace osu.Game.Rulesets.Mania.Tests
{ {
public class TestSceneNotePlacementBlueprint : ManiaPlacementBlueprintTestScene public class TestSceneNotePlacementBlueprint : ManiaPlacementBlueprintTestScene
{ {
[SetUp]
public void Setup() => Schedule(() =>
{
this.ChildrenOfType<HitObjectContainer>().ForEach(c => c.Clear());
ResetPlacement();
((ScrollingTestContainer)HitObjectContainer).Direction = ScrollingDirection.Down;
});
[Test]
public void TestPlaceBeforeCurrentTimeDownwards()
{
AddStep("move mouse before current time", () => InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Single().ScreenSpaceDrawQuad.BottomLeft - new Vector2(0, 10)));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddAssert("note start time < 0", () => getNote().StartTime < 0);
}
[Test]
public void TestPlaceAfterCurrentTimeDownwards()
{
AddStep("move mouse after current time", () => InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Single()));
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddAssert("note start time > 0", () => getNote().StartTime > 0);
}
private Note getNote() => this.ChildrenOfType<DrawableNote>().FirstOrDefault()?.HitObject;
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject); protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableNote((Note)hitObject);
protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint(); protected override PlacementBlueprint CreateBlueprint() => new NotePlacementBlueprint();
} }

View File

@ -13,7 +13,6 @@ using osu.Game.Rulesets.Mania.Beatmaps.Patterns;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy; using osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy;
using osuTK; using osuTK;
using osu.Game.Audio;
namespace osu.Game.Rulesets.Mania.Beatmaps namespace osu.Game.Rulesets.Mania.Beatmaps
{ {
@ -67,7 +66,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
} }
} }
public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition || h is ManiaHitObject); public override bool CanConvert() => Beatmap.HitObjects.All(h => h is IHasXPosition);
protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original) protected override Beatmap<ManiaHitObject> ConvertBeatmap(IBeatmap original)
{ {
@ -239,8 +238,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
StartTime = HitObject.StartTime, StartTime = HitObject.StartTime,
Duration = endTimeData.Duration, Duration = endTimeData.Duration,
Column = column, Column = column,
Head = { Samples = sampleInfoListAt(HitObject.StartTime) }, Samples = HitObject.Samples,
Tail = { Samples = sampleInfoListAt(endTimeData.EndTime) }, NodeSamples = (HitObject as IHasRepeats)?.NodeSamples
}); });
} }
else if (HitObject is IHasXPosition) else if (HitObject is IHasXPosition)
@ -255,22 +254,6 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
return pattern; return pattern;
} }
/// <summary>
/// Retrieves the sample info list at a point in time.
/// </summary>
/// <param name="time">The time to retrieve the sample info list from.</param>
/// <returns></returns>
private IList<HitSampleInfo> sampleInfoListAt(double time)
{
if (!(HitObject is IHasCurve curveData))
return HitObject.Samples;
double segmentTime = (curveData.EndTime - HitObject.StartTime) / curveData.SpanCount();
int index = (int)(segmentTime == 0 ? 0 : (time - HitObject.StartTime) / segmentTime);
return curveData.NodeSamples[index];
}
} }
} }
} }

View File

@ -505,16 +505,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
} }
else else
{ {
var holdNote = new HoldNote newObject = new HoldNote
{ {
StartTime = startTime, StartTime = startTime,
Column = column,
Duration = endTime - startTime, Duration = endTime - startTime,
Head = { Samples = sampleInfoListAt(startTime) }, Column = column,
Tail = { Samples = sampleInfoListAt(endTime) } Samples = HitObject.Samples,
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples
}; };
newObject = holdNote;
} }
pattern.Add(newObject); pattern.Add(newObject);

View File

@ -64,21 +64,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (holdNote) if (holdNote)
{ {
var hold = new HoldNote newObject = new HoldNote
{ {
StartTime = HitObject.StartTime, StartTime = HitObject.StartTime,
Duration = endTime - HitObject.StartTime,
Column = column, Column = column,
Duration = endTime - HitObject.StartTime Samples = HitObject.Samples,
NodeSamples = (HitObject as IHasRepeats)?.NodeSamples
}; };
if (hold.Head.Samples == null)
hold.Head.Samples = new List<HitSampleInfo>();
hold.Head.Samples.Add(new HitSampleInfo { Name = HitSampleInfo.HIT_NORMAL });
hold.Tail.Samples = HitObject.Samples;
newObject = hold;
} }
else else
{ {

View File

@ -3,6 +3,7 @@
using System; using System;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osuTK; using osuTK;
@ -46,6 +47,12 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
bodyPiece.Height = (bottomPosition - topPosition).Y; bodyPiece.Height = (bottomPosition - topPosition).Y;
} }
protected override void OnMouseUp(MouseUpEvent e)
{
base.OnMouseUp(e);
EndPlacement(true);
}
private double originalStartTime; private double originalStartTime;
public override void UpdatePosition(Vector2 screenSpacePosition) public override void UpdatePosition(Vector2 screenSpacePosition)

View File

@ -50,16 +50,10 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
return base.OnMouseDown(e); return base.OnMouseDown(e);
HitObject.Column = Column.Index; HitObject.Column = Column.Index;
BeginPlacement(TimeAt(e.ScreenSpaceMousePosition)); BeginPlacement(TimeAt(e.ScreenSpaceMousePosition), true);
return true; return true;
} }
protected override void OnMouseUp(MouseUpEvent e)
{
EndPlacement(true);
base.OnMouseUp(e);
}
public override void UpdatePosition(Vector2 screenSpacePosition) public override void UpdatePosition(Vector2 screenSpacePosition)
{ {
if (!PlacementActive) if (!PlacementActive)

View File

@ -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 osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components; using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
@ -26,5 +27,15 @@ namespace osu.Game.Rulesets.Mania.Edit.Blueprints
Width = SnappedWidth; Width = SnappedWidth;
Position = SnappedMousePosition; Position = SnappedMousePosition;
} }
protected override bool OnMouseDown(MouseDownEvent e)
{
base.OnMouseDown(e);
// Place the note immediately.
EndPlacement(true);
return true;
}
} }
} }

View File

@ -51,7 +51,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
AddRangeInternal(new[] AddRangeInternal(new[]
{ {
bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece()) bodyPiece = new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.HoldNoteBody, hitObject.Column), _ => new DefaultBodyPiece
{
RelativeSizeAxes = Axes.Both
})
{ {
RelativeSizeAxes = Axes.X RelativeSizeAxes = Axes.X
}, },
@ -127,6 +130,11 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft; bodyPiece.Anchor = bodyPiece.Origin = e.NewValue == ScrollingDirection.Up ? Anchor.TopLeft : Anchor.BottomLeft;
} }
public override void PlaySamples()
{
// Samples are played by the head/tail notes.
}
protected override void Update() protected override void Update()
{ {
base.Update(); base.Update();

View File

@ -34,7 +34,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces
public DefaultBodyPiece() public DefaultBodyPiece()
{ {
RelativeSizeAxes = Axes.Both;
Blending = BlendingParameters.Additive; Blending = BlendingParameters.Additive;
AddLayout(subtractionCache); AddLayout(subtractionCache);

View File

@ -1,6 +1,8 @@
// 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.Collections.Generic;
using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
@ -28,7 +30,9 @@ namespace osu.Game.Rulesets.Mania.Objects
set set
{ {
duration = value; duration = value;
Tail.StartTime = EndTime;
if (Tail != null)
Tail.StartTime = EndTime;
} }
} }
@ -38,8 +42,12 @@ namespace osu.Game.Rulesets.Mania.Objects
set set
{ {
base.StartTime = value; base.StartTime = value;
Head.StartTime = value;
Tail.StartTime = EndTime; if (Head != null)
Head.StartTime = value;
if (Tail != null)
Tail.StartTime = EndTime;
} }
} }
@ -49,20 +57,26 @@ namespace osu.Game.Rulesets.Mania.Objects
set set
{ {
base.Column = value; base.Column = value;
Head.Column = value;
Tail.Column = value; if (Head != null)
Head.Column = value;
if (Tail != null)
Tail.Column = value;
} }
} }
public List<IList<HitSampleInfo>> NodeSamples { get; set; }
/// <summary> /// <summary>
/// The head note of the hold. /// The head note of the hold.
/// </summary> /// </summary>
public readonly Note Head = new Note(); public Note Head { get; private set; }
/// <summary> /// <summary>
/// The tail note of the hold. /// The tail note of the hold.
/// </summary> /// </summary>
public readonly TailNote Tail = new TailNote(); public TailNote Tail { get; private set; }
/// <summary> /// <summary>
/// The time between ticks of this hold. /// The time between ticks of this hold.
@ -83,8 +97,19 @@ namespace osu.Game.Rulesets.Mania.Objects
createTicks(); createTicks();
AddNested(Head); AddNested(Head = new Note
AddNested(Tail); {
StartTime = StartTime,
Column = Column,
Samples = getNodeSamples(0),
});
AddNested(Tail = new TailNote
{
StartTime = EndTime,
Column = Column,
Samples = getNodeSamples((NodeSamples?.Count - 1) ?? 1),
});
} }
private void createTicks() private void createTicks()
@ -105,5 +130,8 @@ namespace osu.Game.Rulesets.Mania.Objects
public override Judgement CreateJudgement() => new IgnoreJudgement(); public override Judgement CreateJudgement() => new IgnoreJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
private IList<HitSampleInfo> getNodeSamples(int nodeIndex) =>
nodeIndex < NodeSamples?.Count ? NodeSamples[nodeIndex] : Samples;
} }
} }

View File

@ -5,11 +5,12 @@ using osu.Framework.Bindables;
using osu.Game.Rulesets.Mania.Objects.Types; using osu.Game.Rulesets.Mania.Objects.Types;
using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Mania.Objects namespace osu.Game.Rulesets.Mania.Objects
{ {
public abstract class ManiaHitObject : HitObject, IHasColumn public abstract class ManiaHitObject : HitObject, IHasColumn, IHasXPosition
{ {
public readonly Bindable<int> ColumnBindable = new Bindable<int>(); public readonly Bindable<int> ColumnBindable = new Bindable<int>();
@ -20,5 +21,11 @@ namespace osu.Game.Rulesets.Mania.Objects
} }
protected override HitWindows CreateHitWindows() => new ManiaHitWindows(); protected override HitWindows CreateHitWindows() => new ManiaHitWindows();
#region LegacyBeatmapEncoder
float IHasXPosition.X => Column;
#endregion
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

View File

@ -5,7 +5,9 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Taiko.Skinning; using osu.Game.Rulesets.Taiko.Skinning;
using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Taiko.UI;
@ -18,8 +20,9 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{ {
public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[] public override IReadOnlyList<Type> RequiredTypes => base.RequiredTypes.Concat(new[]
{ {
typeof(HitTarget), typeof(TaikoHitTarget),
typeof(LegacyHitTarget), typeof(TaikoLegacyHitTarget),
typeof(PlayfieldBackgroundRight),
}).ToList(); }).ToList();
[Cached(typeof(IScrollingInfo))] [Cached(typeof(IScrollingInfo))]
@ -33,10 +36,11 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
{ {
AddStep("Load playfield", () => SetContents(() => new TaikoPlayfield(new ControlPointInfo()) AddStep("Load playfield", () => SetContents(() => new TaikoPlayfield(new ControlPointInfo())
{ {
Height = 0.4f,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
})); }));
AddRepeatStep("change height", () => this.ChildrenOfType<TaikoPlayfield>().ForEach(p => p.Height = Math.Max(0.2f, (p.Height + 0.2f) % 1f)), 50);
} }
} }
} }

View File

@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
/// osu! is generally slower than taiko, so a factor is added to increase /// osu! is generally slower than taiko, so a factor is added to increase
/// speed. This must be used everywhere slider length or beat length is used. /// speed. This must be used everywhere slider length or beat length is used.
/// </summary> /// </summary>
private const float legacy_velocity_multiplier = 1.4f; public const float LEGACY_VELOCITY_MULTIPLIER = 1.4f;
/// <summary> /// <summary>
/// Because swells are easier in taiko than spinners are in osu!, /// Because swells are easier in taiko than spinners are in osu!,
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
// Rewrite the beatmap info to add the slider velocity multiplier // Rewrite the beatmap info to add the slider velocity multiplier
original.BeatmapInfo = original.BeatmapInfo.Clone(); original.BeatmapInfo = original.BeatmapInfo.Clone();
original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone(); original.BeatmapInfo.BaseDifficulty = original.BeatmapInfo.BaseDifficulty.Clone();
original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= legacy_velocity_multiplier; original.BeatmapInfo.BaseDifficulty.SliderMultiplier *= LEGACY_VELOCITY_MULTIPLIER;
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original); Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original);
@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment; double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment;
// The true distance, accounting for any repeats. This ends up being the drum roll distance later // The true distance, accounting for any repeats. This ends up being the drum roll distance later
double distance = distanceData.Distance * spans * legacy_velocity_multiplier; double distance = distanceData.Distance * spans * LEGACY_VELOCITY_MULTIPLIER;
// The velocity of the taiko hit object - calculated as the velocity of a drum roll // The velocity of the taiko hit object - calculated as the velocity of a drum roll
double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength; double taikoVelocity = taiko_base_distance * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / speedAdjustedBeatLength;

View File

@ -92,8 +92,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
// The input manager processes all input prior to us updating, so this is the perfect time // The input manager processes all input prior to us updating, so this is the perfect time
// for us to remove the extra press blocking, before input is handled in the next frame // for us to remove the extra press blocking, before input is handled in the next frame
pressHandledThisFrame = false; pressHandledThisFrame = false;
Size = BaseSize * Parent.RelativeChildSize;
} }
protected override void UpdateStateTransforms(ArmedState state) protected override void UpdateStateTransforms(ArmedState state)

View File

@ -3,15 +3,20 @@
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using System; using System;
using System.Collections.Generic;
using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Beatmaps;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
using osuTK;
namespace osu.Game.Rulesets.Taiko.Objects namespace osu.Game.Rulesets.Taiko.Objects
{ {
public class DrumRoll : TaikoHitObject, IHasEndTime public class DrumRoll : TaikoHitObject, IHasCurve
{ {
/// <summary> /// <summary>
/// Drum roll distance that results in a duration of 1 speed-adjusted beat length. /// Drum roll distance that results in a duration of 1 speed-adjusted beat length.
@ -26,6 +31,11 @@ namespace osu.Game.Rulesets.Taiko.Objects
public double Duration { get; set; } public double Duration { get; set; }
/// <summary>
/// Velocity of this <see cref="DrumRoll"/>.
/// </summary>
public double Velocity { get; private set; }
/// <summary> /// <summary>
/// Numer of ticks per beat length. /// Numer of ticks per beat length.
/// </summary> /// </summary>
@ -54,6 +64,10 @@ namespace osu.Game.Rulesets.Taiko.Objects
base.ApplyDefaultsToSelf(controlPointInfo, difficulty); base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime); TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength;
tickSpacing = timingPoint.BeatLength / TickRate; tickSpacing = timingPoint.BeatLength / TickRate;
overallDifficulty = difficulty.OverallDifficulty; overallDifficulty = difficulty.OverallDifficulty;
@ -93,5 +107,18 @@ namespace osu.Game.Rulesets.Taiko.Objects
public override Judgement CreateJudgement() => new TaikoDrumRollJudgement(); public override Judgement CreateJudgement() => new TaikoDrumRollJudgement();
protected override HitWindows CreateHitWindows() => HitWindows.Empty; protected override HitWindows CreateHitWindows() => HitWindows.Empty;
#region LegacyBeatmapEncoder
double IHasDistance.Distance => Duration * Velocity;
int IHasRepeats.RepeatCount { get => 0; set { } }
List<IList<HitSampleInfo>> IHasRepeats.NodeSamples => new List<IList<HitSampleInfo>>();
SliderPath IHasCurve.Path
=> new SliderPath(PathType.Linear, new[] { Vector2.Zero, new Vector2(1) }, ((IHasDistance)this).Distance / TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER);
#endregion
} }
} }

View File

@ -1,41 +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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Taiko.Skinning
{
public class LegacyHitTarget : CompositeDrawable
{
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new Sprite
{
Texture = skin.GetTexture("approachcircle"),
Scale = new Vector2(0.73f),
Alpha = 0.7f,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new Sprite
{
Texture = skin.GetTexture("taikobigcircle"),
Scale = new Vector2(0.7f),
Alpha = 0.5f,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
}
}
}

View File

@ -20,36 +20,41 @@ namespace osu.Game.Rulesets.Taiko.Skinning
{ {
private LegacyHalfDrum left; private LegacyHalfDrum left;
private LegacyHalfDrum right; private LegacyHalfDrum right;
private Container content;
public LegacyInputDrum() public LegacyInputDrum()
{ {
Size = new Vector2(180, 200); RelativeSizeAxes = Axes.Both;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(ISkinSource skin) private void load(ISkinSource skin)
{ {
Children = new Drawable[] Child = content = new Container
{ {
new Sprite Size = new Vector2(180, 200),
Children = new Drawable[]
{ {
Texture = skin.GetTexture("taiko-bar-left") new Sprite
}, {
left = new LegacyHalfDrum(false) Texture = skin.GetTexture("taiko-bar-left")
{ },
Name = "Left Half", left = new LegacyHalfDrum(false)
RelativeSizeAxes = Axes.Both, {
RimAction = TaikoAction.LeftRim, Name = "Left Half",
CentreAction = TaikoAction.LeftCentre RelativeSizeAxes = Axes.Both,
}, RimAction = TaikoAction.LeftRim,
right = new LegacyHalfDrum(true) CentreAction = TaikoAction.LeftCentre
{ },
Name = "Right Half", right = new LegacyHalfDrum(true)
RelativeSizeAxes = Axes.Both, {
Origin = Anchor.TopRight, Name = "Right Half",
Scale = new Vector2(-1, 1), RelativeSizeAxes = Axes.Both,
RimAction = TaikoAction.RightRim, Origin = Anchor.TopRight,
CentreAction = TaikoAction.RightCentre Scale = new Vector2(-1, 1),
RimAction = TaikoAction.RightRim,
CentreAction = TaikoAction.RightCentre
}
} }
}; };
@ -60,7 +65,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning
const float ratio = 1.6f; const float ratio = 1.6f;
// because the right half is flipped, we need to position using width - position to get the true "topleft" origin position // because the right half is flipped, we need to position using width - position to get the true "topleft" origin position
float negativeScaleAdjust = Width / ratio; float negativeScaleAdjust = content.Width / ratio;
if (skin.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.1m) if (skin.GetConfig<LegacySkinConfiguration.LegacySetting, decimal>(LegacySkinConfiguration.LegacySetting.Version)?.Value >= 2.1m)
{ {
@ -78,6 +83,15 @@ namespace osu.Game.Rulesets.Taiko.Skinning
} }
} }
protected override void Update()
{
base.Update();
// Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements.
// This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement.
content.Scale = new Vector2(DrawHeight / content.Size.Y);
}
/// <summary> /// <summary>
/// A half-drum. Contains one centre and one rim hit. /// A half-drum. Contains one centre and one rim hit.
/// </summary> /// </summary>

View File

@ -0,0 +1,59 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Taiko.Skinning
{
public class TaikoLegacyHitTarget : CompositeDrawable
{
private Container content;
[BackgroundDependencyLoader]
private void load(ISkinSource skin)
{
RelativeSizeAxes = Axes.Both;
InternalChild = content = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Sprite
{
Texture = skin.GetTexture("approachcircle"),
Scale = new Vector2(0.73f),
Alpha = 0.7f,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new Sprite
{
Texture = skin.GetTexture("taikobigcircle"),
Scale = new Vector2(0.7f),
Alpha = 0.5f,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
}
};
}
protected override void Update()
{
base.Update();
// Relying on RelativeSizeAxes.Both + FillMode.Fit doesn't work due to the precise pixel layout requirements.
// This is a bit ugly but makes the non-legacy implementations a lot cleaner to implement.
content.Scale = new Vector2(DrawHeight / TaikoPlayfield.DEFAULT_HEIGHT);
}
}
}

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics.Textures;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Rulesets.Taiko.UI; using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK;
namespace osu.Game.Rulesets.Taiko.Skinning namespace osu.Game.Rulesets.Taiko.Skinning
{ {
@ -53,7 +54,26 @@ namespace osu.Game.Rulesets.Taiko.Skinning
case TaikoSkinComponents.HitTarget: case TaikoSkinComponents.HitTarget:
if (GetTexture("taikobigcircle") != null) if (GetTexture("taikobigcircle") != null)
return new LegacyHitTarget(); return new TaikoLegacyHitTarget();
return null;
case TaikoSkinComponents.PlayfieldBackgroundRight:
if (GetTexture("taiko-bar-right") != null)
{
return this.GetAnimation("taiko-bar-right", false, false).With(d =>
{
d.RelativeSizeAxes = Axes.Both;
d.Size = Vector2.One;
});
}
return null;
case TaikoSkinComponents.PlayfieldBackgroundLeft:
// This is displayed inside LegacyInputDrum. It is required to be there for layout purposes (can be seen on legacy skins).
if (GetTexture("taiko-bar-right") != null)
return Drawable.Empty();
return null; return null;

View File

@ -12,6 +12,8 @@ namespace osu.Game.Rulesets.Taiko
DrumRollTick, DrumRollTick,
Swell, Swell,
HitTarget, HitTarget,
PlayfieldBackgroundLeft,
PlayfieldBackgroundRight,
TaikoDon TaikoDon
} }
} }

View File

@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.UI
JudgedObject = judgedObject; JudgedObject = judgedObject;
Anchor = Anchor.CentreLeft; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;

View File

@ -31,7 +31,6 @@ namespace osu.Game.Rulesets.Taiko.UI
sampleMapping = new DrumSampleMapping(controlPoints); sampleMapping = new DrumSampleMapping(controlPoints);
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fit;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -40,6 +39,8 @@ namespace osu.Game.Rulesets.Taiko.UI
Child = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container Child = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.InputDrum), _ => new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Scale = new Vector2(0.9f),
Children = new Drawable[] Children = new Drawable[]
{ {
new TaikoHalfDrum(false) new TaikoHalfDrum(false)

View File

@ -0,0 +1,37 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Taiko.UI
{
internal class PlayfieldBackgroundLeft : CompositeDrawable
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
RelativeSizeAxes = Axes.Both;
InternalChildren = new Drawable[]
{
new Box
{
Colour = colours.Gray1,
RelativeSizeAxes = Axes.Both,
},
new Box
{
Anchor = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = 10,
Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)),
},
};
}
}
}

View File

@ -0,0 +1,61 @@
// 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.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Rulesets.Taiko.UI
{
public class PlayfieldBackgroundRight : CompositeDrawable
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Name = "Transparent playfield background";
RelativeSizeAxes = Axes.Both;
Masking = true;
BorderColour = colours.Gray1;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.2f),
Radius = 5,
};
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Gray0,
Alpha = 0.6f
},
new Container
{
Name = "Border",
RelativeSizeAxes = Axes.Both,
Masking = true,
MaskingSmoothness = 0,
BorderThickness = 2,
AlwaysPresent = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
}
}
};
}
}
}

View File

@ -13,14 +13,14 @@ namespace osu.Game.Rulesets.Taiko.UI
/// <summary> /// <summary>
/// A component that is displayed at the hit position in the taiko playfield. /// A component that is displayed at the hit position in the taiko playfield.
/// </summary> /// </summary>
internal class HitTarget : Container internal class TaikoHitTarget : Container
{ {
/// <summary> /// <summary>
/// Thickness of all drawn line pieces. /// Thickness of all drawn line pieces.
/// </summary> /// </summary>
private const float border_thickness = 2.5f; private const float border_thickness = 2.5f;
public HitTarget() public TaikoHitTarget()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -41,7 +41,6 @@ namespace osu.Game.Rulesets.Taiko.UI
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Scale = new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE), Scale = new Vector2(TaikoHitObject.DEFAULT_STRONG_SIZE),
Masking = true, Masking = true,
BorderColour = Color4.White, BorderColour = Color4.White,
@ -63,7 +62,6 @@ namespace osu.Game.Rulesets.Taiko.UI
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Scale = new Vector2(TaikoHitObject.DEFAULT_SIZE), Scale = new Vector2(TaikoHitObject.DEFAULT_SIZE),
Masking = true, Masking = true,
BorderColour = Color4.White, BorderColour = Color4.White,

View File

@ -3,11 +3,9 @@
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@ -18,175 +16,117 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements; using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK;
using osuTK.Graphics;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Framework.Bindables;
namespace osu.Game.Rulesets.Taiko.UI namespace osu.Game.Rulesets.Taiko.UI
{ {
public class TaikoPlayfield : ScrollingPlayfield public class TaikoPlayfield : ScrollingPlayfield
{ {
private readonly ControlPointInfo controlPoints;
/// <summary> /// <summary>
/// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="DrawableTaikoRuleset"/>. /// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="DrawableTaikoRuleset"/>.
/// </summary> /// </summary>
public const float DEFAULT_HEIGHT = 178; public const float DEFAULT_HEIGHT = 178;
/// <summary> private Container<HitExplosion> hitExplosionContainer;
/// The offset from <see cref="left_area_size"/> which the center of the hit target lies at. private Container<KiaiHitExplosion> kiaiExplosionContainer;
/// </summary> private JudgementContainer<DrawableTaikoJudgement> judgementContainer;
public const float HIT_TARGET_OFFSET = 100; internal Drawable HitTarget;
/// <summary> private ProxyContainer topLevelHitContainer;
/// The size of the left area of the playfield. This area contains the input drum. private ProxyContainer barlineContainer;
/// </summary> private Container rightArea;
private const float left_area_size = 240; private Container leftArea;
private readonly Container<HitExplosion> hitExplosionContainer; private Container hitTargetOffsetContent;
private readonly Container<KiaiHitExplosion> kiaiExplosionContainer;
private readonly JudgementContainer<DrawableTaikoJudgement> judgementContainer;
internal readonly Drawable HitTarget;
private readonly ProxyContainer topLevelHitContainer; private SkinnableDrawable mascotDrawable;
private readonly ProxyContainer barlineContainer;
private readonly Container overlayBackgroundContainer;
private readonly Container backgroundContainer;
private readonly Box overlayBackground;
private readonly Box background;
private readonly SkinnableDrawable characterDrawable;
private Bindable<double> frameTime = new Bindable<double>(100); private Bindable<double> frameTime = new Bindable<double>(100);
public TaikoPlayfield(ControlPointInfo controlPoints) public TaikoPlayfield(ControlPointInfo controlPoints)
{
this.controlPoints = controlPoints;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{ {
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
backgroundContainer = new Container new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundRight), _ => new PlayfieldBackgroundRight()),
{ rightArea = new Container
Name = "Transparent playfield background",
RelativeSizeAxes = Axes.Both,
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.2f),
Radius = 5,
},
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.6f
},
}
},
new Container
{ {
Name = "Right area", Name = "Right area",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = left_area_size }, RelativePositionAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
new Container new Container
{ {
Name = "Masked elements before hit objects", Name = "Masked elements before hit objects",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, FillMode = FillMode.Fit,
Masking = true,
Children = new[] Children = new[]
{ {
hitExplosionContainer = new Container<HitExplosion> hitExplosionContainer = new Container<HitExplosion>
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit,
Blending = BlendingParameters.Additive, Blending = BlendingParameters.Additive,
}, },
HitTarget = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.HitTarget), _ => new HitTarget()) HitTarget = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.HitTarget), _ => new TaikoHitTarget())
{ {
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit
} }
} }
}, },
barlineContainer = new ProxyContainer hitTargetOffsetContent = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET } Children = new Drawable[]
}, {
new Container barlineContainer = new ProxyContainer
{ {
Name = "Hit objects", RelativeSizeAxes = Axes.Both,
RelativeSizeAxes = Axes.Both, },
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }, new Container
Masking = true, {
Child = HitObjectContainer Name = "Hit objects",
}, RelativeSizeAxes = Axes.Both,
kiaiExplosionContainer = new Container<KiaiHitExplosion> Child = HitObjectContainer
{ },
Name = "Kiai hit explosions", kiaiExplosionContainer = new Container<KiaiHitExplosion>
RelativeSizeAxes = Axes.Both, {
FillMode = FillMode.Fit, Name = "Kiai hit explosions",
Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, RelativeSizeAxes = Axes.Both,
Blending = BlendingParameters.Additive FillMode = FillMode.Fit,
}, Blending = BlendingParameters.Additive
judgementContainer = new JudgementContainer<DrawableTaikoJudgement> },
{ judgementContainer = new JudgementContainer<DrawableTaikoJudgement>
Name = "Judgements", {
RelativeSizeAxes = Axes.Y, Name = "Judgements",
Margin = new MarginPadding { Left = HIT_TARGET_OFFSET }, RelativeSizeAxes = Axes.Y,
Blending = BlendingParameters.Additive Blending = BlendingParameters.Additive
},
}
}, },
} }
}, },
overlayBackgroundContainer = new Container leftArea = new Container
{ {
Name = "Left overlay", Name = "Left overlay",
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Both,
Size = new Vector2(left_area_size, 1), FillMode = FillMode.Fit,
BorderColour = colours.Gray0,
Children = new Drawable[] Children = new Drawable[]
{ {
overlayBackground = new Box new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.PlayfieldBackgroundLeft), _ => new PlayfieldBackgroundLeft()),
{
RelativeSizeAxes = Axes.Both,
},
new InputDrum(controlPoints) new InputDrum(controlPoints)
{ {
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreRight, Origin = Anchor.CentreLeft,
Scale = new Vector2(0.9f),
Margin = new MarginPadding { Right = 20 }
}, },
new Box
{
Anchor = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = 10,
Colour = Framework.Graphics.Colour.ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.6f), Color4.Black.Opacity(0)),
},
}
},
new Container
{
Name = "Border",
RelativeSizeAxes = Axes.Both,
Masking = true,
MaskingSmoothness = 0,
BorderThickness = 2,
AlwaysPresent = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true
}
} }
}, },
topLevelHitContainer = new ProxyContainer topLevelHitContainer = new ProxyContainer
@ -194,7 +134,7 @@ namespace osu.Game.Rulesets.Taiko.UI
Name = "Top level hit objects", Name = "Top level hit objects",
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}, },
characterDrawable = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.TaikoDon), _ => new Container(), confineMode: ConfineMode.ScaleToFit) mascotDrawable = new SkinnableDrawable(new TaikoSkinComponent(TaikoSkinComponents.TaikoDon), _ => new Container(), confineMode: ConfineMode.ScaleToFit)
{ {
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Anchor = Anchor.TopLeft, Anchor = Anchor.TopLeft,
@ -203,14 +143,14 @@ namespace osu.Game.Rulesets.Taiko.UI
}; };
} }
[BackgroundDependencyLoader] protected override void Update()
private void load(OsuColour colours)
{ {
overlayBackgroundContainer.BorderColour = colours.Gray0; base.Update();
overlayBackground.Colour = colours.Gray1;
backgroundContainer.BorderColour = colours.Gray1; // Padding is required to be updated for elements which are based on "absolute" X sized elements.
background.Colour = colours.Gray0; // This is basically allowing for correct alignment as relative pieces move around them.
rightArea.Padding = new MarginPadding { Left = leftArea.DrawWidth };
hitTargetOffsetContent.Padding = new MarginPadding { Left = HitTarget.DrawWidth / 2 };
} }
public override void Add(DrawableHitObject h) public override void Add(DrawableHitObject h)
@ -268,7 +208,7 @@ namespace osu.Game.Rulesets.Taiko.UI
break; break;
} }
if (characterDrawable.Drawable is DrawableTaikoMascot mascot) if (mascotDrawable.Drawable is DrawableTaikoMascot mascot)
{ {
var miss = result.Type == HitResult.Miss; var miss = result.Type == HitResult.Miss;

View File

@ -0,0 +1,30 @@
osu file format v14
[General]
SampleSet: Normal
StackLeniency: 0.7
Mode: 2
[Difficulty]
HPDrainRate:3
CircleSize:5
OverallDifficulty:8
ApproachRate:8
SliderMultiplier:3.59999990463257
SliderTickRate:2
[TimingPoints]
24,352.941176470588,4,1,1,100,1,0
6376,-50,4,1,1,100,0,0
[HitObjects]
32,183,24,5,0,0:0:0:0:
106,123,200,1,10,0:0:0:0:
199,108,376,1,2,0:0:0:0:
305,105,553,5,4,0:0:0:0:
386,112,729,1,14,0:0:0:0:
486,197,906,5,12,0:0:0:0:
14,199,1082,2,0,L|473:198,1,449.999988079071
14,199,1700,6,6,P|248:33|490:222,1,629.9999833107,0|8,0:0|0:0,0:0:0:0:
10,190,2494,2,8,B|252:29|254:335|468:167,1,449.999988079071,10|12,0:0|0:0,0:0:0:0:
256,192,3112,12,0,3906,0:0:0:0:

View File

@ -0,0 +1,39 @@
osu file format v14
[General]
SampleSet: Normal
StackLeniency: 0.7
Mode: 3
[Difficulty]
HPDrainRate:3
CircleSize:5
OverallDifficulty:8
ApproachRate:8
SliderMultiplier:3.59999990463257
SliderTickRate:2
[TimingPoints]
24,352.941176470588,4,1,1,100,1,0
6376,-50,4,1,1,100,0,0
[HitObjects]
51,192,24,1,0,0:0:0:0:
153,192,200,1,0,0:0:0:0:
358,192,376,1,0,0:0:0:0:
460,192,553,1,0,0:0:0:0:
460,192,729,128,0,1435:0:0:0:0:
358,192,906,128,0,1612:0:0:0:0:
256,192,1082,128,0,1788:0:0:0:0:
153,192,1259,128,0,1965:0:0:0:0:
51,192,1435,128,0,2141:0:0:0:0:
51,192,2318,1,12,0:0:0:0:
153,192,2318,1,4,0:0:0:0:
256,192,2318,1,6,0:0:0:0:
358,192,2318,1,14,0:0:0:0:
460,192,2318,1,0,0:0:0:0:
51,192,2494,128,0,2582:0:0:0:0:
153,192,2494,128,14,2582:0:0:0:0:
256,192,2494,128,6,2582:0:0:0:0:
358,192,2494,128,4,2582:0:0:0:0:
460,192,2494,128,12,2582:0:0:0:0:

View File

@ -0,0 +1,42 @@
osu file format v14
[General]
SampleSet: Normal
StackLeniency: 0.7
Mode: 1
[Difficulty]
HPDrainRate:3
CircleSize:5
OverallDifficulty:8
ApproachRate:8
SliderMultiplier:3.59999990463257
SliderTickRate:2
[TimingPoints]
24,352.941176470588,4,1,1,100,1,0
6376,-50,4,1,1,100,0,0
[HitObjects]
231,129,24,1,0,0:0:0:0:
231,129,200,1,0,0:0:0:0:
231,129,376,1,0,0:0:0:0:
231,129,553,1,0,0:0:0:0:
231,129,729,1,0,0:0:0:0:
373,132,906,1,4,0:0:0:0:
373,132,1082,1,4,0:0:0:0:
373,132,1259,1,4,0:0:0:0:
373,132,1435,1,4,0:0:0:0:
231,129,1788,1,8,0:0:0:0:
231,129,1964,1,8,0:0:0:0:
231,129,2140,1,8,0:0:0:0:
231,129,2317,1,8,0:0:0:0:
231,129,2493,1,8,0:0:0:0:
373,132,2670,1,12,0:0:0:0:
373,132,2846,1,12,0:0:0:0:
373,132,3023,1,12,0:0:0:0:
373,132,3199,1,12,0:0:0:0:
51,189,3553,2,0,L|150:188,1,89.9999976158143
52,191,3906,2,0,L|512:189,1,449.999988079071
26,196,4612,2,4,L|501:195,1,449.999988079071
17,242,5318,2,10,P|250:69|495:243,1,629.9999833107,0|8,0:0|0:0,0:0:0:0:

View File

@ -5,13 +5,17 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Overlays.Toolbar; using osu.Game.Overlays.Toolbar;
using osu.Game.Rulesets;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Menus namespace osu.Game.Tests.Visual.Menus
{ {
[TestFixture] [TestFixture]
public class TestSceneToolbar : OsuTestScene public class TestSceneToolbar : OsuManualInputManagerTestScene
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
@ -21,24 +25,62 @@ namespace osu.Game.Tests.Visual.Menus
typeof(ToolbarNotificationButton), typeof(ToolbarNotificationButton),
}; };
public TestSceneToolbar() private Toolbar toolbar;
[Resolved]
private RulesetStore rulesets { get; set; }
[SetUp]
public void SetUp() => Schedule(() =>
{
Child = toolbar = new Toolbar { State = { Value = Visibility.Visible } };
});
[Test]
public void TestNotificationCounter()
{ {
var toolbar = new Toolbar { State = { Value = Visibility.Visible } };
ToolbarNotificationButton notificationButton = null; ToolbarNotificationButton notificationButton = null;
AddStep("create toolbar", () => AddStep("retrieve notification button", () => notificationButton = toolbar.ChildrenOfType<ToolbarNotificationButton>().Single());
{
Add(toolbar);
notificationButton = toolbar.Children.OfType<FillFlowContainer>().Last().Children.OfType<ToolbarNotificationButton>().First();
});
void setNotifications(int count) => AddStep($"set notification count to {count}", () => notificationButton.NotificationCount.Value = count);
setNotifications(1); setNotifications(1);
setNotifications(2); setNotifications(2);
setNotifications(3); setNotifications(3);
setNotifications(0); setNotifications(0);
setNotifications(144); setNotifications(144);
void setNotifications(int count)
=> AddStep($"set notification count to {count}",
() => notificationButton.NotificationCount.Value = count);
}
[TestCase(false)]
[TestCase(true)]
public void TestRulesetSwitchingShortcut(bool toolbarHidden)
{
ToolbarRulesetSelector rulesetSelector = null;
if (toolbarHidden)
AddStep("hide toolbar", () => toolbar.Hide());
AddStep("retrieve ruleset selector", () => rulesetSelector = toolbar.ChildrenOfType<ToolbarRulesetSelector>().Single());
for (int i = 0; i < 4; i++)
{
var expected = rulesets.AvailableRulesets.ElementAt(i);
var numberKey = Key.Number1 + i;
AddStep($"switch to ruleset {i} via shortcut", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.PressKey(numberKey);
InputManager.ReleaseKey(Key.ControlLeft);
InputManager.ReleaseKey(numberKey);
});
AddUntilStep("ruleset switched", () => rulesetSelector.Current.Value.Equals(expected));
}
} }
} }
} }

View File

@ -13,6 +13,7 @@ using osu.Game.Beatmaps.Legacy;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Legacy; using osu.Game.Rulesets.Objects.Legacy;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using osuTK;
namespace osu.Game.Beatmaps.Formats namespace osu.Game.Beatmaps.Formats
{ {
@ -124,7 +125,12 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine(FormattableString.Invariant($"CircleSize: {beatmap.BeatmapInfo.BaseDifficulty.CircleSize}")); writer.WriteLine(FormattableString.Invariant($"CircleSize: {beatmap.BeatmapInfo.BaseDifficulty.CircleSize}"));
writer.WriteLine(FormattableString.Invariant($"OverallDifficulty: {beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty}")); writer.WriteLine(FormattableString.Invariant($"OverallDifficulty: {beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty}"));
writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.BeatmapInfo.BaseDifficulty.ApproachRate}")); writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.BeatmapInfo.BaseDifficulty.ApproachRate}"));
writer.WriteLine(FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier}"));
// Taiko adjusts the slider multiplier (see: TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER)
writer.WriteLine(beatmap.BeatmapInfo.RulesetID == 1
? FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / 1.4f}")
: FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier}"));
writer.WriteLine(FormattableString.Invariant($"SliderTickRate: {beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate}")); writer.WriteLine(FormattableString.Invariant($"SliderTickRate: {beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate}"));
} }
@ -197,51 +203,63 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine("[HitObjects]"); writer.WriteLine("[HitObjects]");
// TODO: implement other legacy rulesets foreach (var h in beatmap.HitObjects)
handleHitObject(writer, h);
}
private void handleHitObject(TextWriter writer, HitObject hitObject)
{
Vector2 position = new Vector2(256, 192);
switch (beatmap.BeatmapInfo.RulesetID) switch (beatmap.BeatmapInfo.RulesetID)
{ {
case 0: case 0:
foreach (var h in beatmap.HitObjects) position = ((IHasPosition)hitObject).Position;
handleOsuHitObject(writer, h); break;
case 2:
position.X = ((IHasXPosition)hitObject).X * 512;
break;
case 3:
int totalColumns = (int)Math.Max(1, beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
position.X = (int)Math.Ceiling(((IHasXPosition)hitObject).X * (512f / totalColumns));
break; break;
} }
}
private void handleOsuHitObject(TextWriter writer, HitObject hitObject) writer.Write(FormattableString.Invariant($"{position.X},"));
{ writer.Write(FormattableString.Invariant($"{position.Y},"));
var positionData = (IHasPosition)hitObject;
writer.Write(FormattableString.Invariant($"{positionData.X},"));
writer.Write(FormattableString.Invariant($"{positionData.Y},"));
writer.Write(FormattableString.Invariant($"{hitObject.StartTime},")); writer.Write(FormattableString.Invariant($"{hitObject.StartTime},"));
writer.Write(FormattableString.Invariant($"{(int)getObjectType(hitObject)},")); writer.Write(FormattableString.Invariant($"{(int)getObjectType(hitObject)},"));
writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},"));
writer.Write(hitObject is IHasCurve
? FormattableString.Invariant($"0,")
: FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},"));
if (hitObject is IHasCurve curveData) if (hitObject is IHasCurve curveData)
{ {
addCurveData(writer, curveData, positionData); addCurveData(writer, curveData, position);
writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true)); writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true));
} }
else else
{ {
if (hitObject is IHasEndTime endTimeData) if (hitObject is IHasEndTime _)
writer.Write(FormattableString.Invariant($"{endTimeData.EndTime},")); addEndTimeData(writer, hitObject);
writer.Write(getSampleBank(hitObject.Samples)); writer.Write(getSampleBank(hitObject.Samples));
} }
writer.WriteLine(); writer.WriteLine();
} }
private static LegacyHitObjectType getObjectType(HitObject hitObject) private LegacyHitObjectType getObjectType(HitObject hitObject)
{ {
var comboData = (IHasCombo)hitObject; LegacyHitObjectType type = 0;
var type = (LegacyHitObjectType)(comboData.ComboOffset << 4); if (hitObject is IHasCombo combo)
{
type = (LegacyHitObjectType)(combo.ComboOffset << 4);
if (comboData.NewCombo) type |= LegacyHitObjectType.NewCombo; if (combo.NewCombo)
type |= LegacyHitObjectType.NewCombo;
}
switch (hitObject) switch (hitObject)
{ {
@ -250,7 +268,10 @@ namespace osu.Game.Beatmaps.Formats
break; break;
case IHasEndTime _: case IHasEndTime _:
type |= LegacyHitObjectType.Spinner; if (beatmap.BeatmapInfo.RulesetID == 3)
type |= LegacyHitObjectType.Hold;
else
type |= LegacyHitObjectType.Spinner;
break; break;
default: default:
@ -261,7 +282,7 @@ namespace osu.Game.Beatmaps.Formats
return type; return type;
} }
private void addCurveData(TextWriter writer, IHasCurve curveData, IHasPosition positionData) private void addCurveData(TextWriter writer, IHasCurve curveData, Vector2 position)
{ {
PathType? lastType = null; PathType? lastType = null;
@ -297,13 +318,13 @@ namespace osu.Game.Beatmaps.Formats
else else
{ {
// New segment with the same type - duplicate the control point // New segment with the same type - duplicate the control point
writer.Write(FormattableString.Invariant($"{positionData.X + point.Position.Value.X}:{positionData.Y + point.Position.Value.Y}|")); writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}|"));
} }
} }
if (i != 0) if (i != 0)
{ {
writer.Write(FormattableString.Invariant($"{positionData.X + point.Position.Value.X}:{positionData.Y + point.Position.Value.Y}")); writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}"));
writer.Write(i != curveData.Path.ControlPoints.Count - 1 ? "|" : ","); writer.Write(i != curveData.Path.ControlPoints.Count - 1 ? "|" : ",");
} }
} }
@ -324,6 +345,20 @@ namespace osu.Game.Beatmaps.Formats
} }
} }
private void addEndTimeData(TextWriter writer, HitObject hitObject)
{
var endTimeData = (IHasEndTime)hitObject;
var type = getObjectType(hitObject);
char suffix = ',';
// Holds write the end time as if it's part of sample data.
if (type == LegacyHitObjectType.Hold)
suffix = ':';
writer.Write(FormattableString.Invariant($"{endTimeData.EndTime}{suffix}"));
}
private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false, bool zeroBanks = false) private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false, bool zeroBanks = false)
{ {
LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank); LegacySampleBank normalBank = toLegacySampleBank(samples.SingleOrDefault(s => s.Name == HitSampleInfo.HIT_NORMAL)?.Bank);

View File

@ -153,7 +153,7 @@ namespace osu.Game.Overlays.Comments
request?.Cancel(); request?.Cancel();
loadCancellation?.Cancel(); loadCancellation?.Cancel();
request = new GetCommentsRequest(id.Value, type.Value, Sort.Value, currentPage++, 0); request = new GetCommentsRequest(id.Value, type.Value, Sort.Value, currentPage++, 0);
request.Success += onSuccess; request.Success += res => Schedule(() => onSuccess(res));
api.PerformAsync(request); api.PerformAsync(request);
} }

View File

@ -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.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
@ -9,7 +10,11 @@ namespace osu.Game.Overlays.KeyBinding
{ {
public class GlobalKeyBindingsSection : SettingsSection public class GlobalKeyBindingsSection : SettingsSection
{ {
public override IconUsage Icon => FontAwesome.Solid.Globe; public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Globe
};
public override string Header => "Global"; public override string Header => "Global";
public GlobalKeyBindingsSection(GlobalActionContainer manager) public GlobalKeyBindingsSection(GlobalActionContainer manager)

View File

@ -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.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
@ -10,7 +11,11 @@ namespace osu.Game.Overlays.KeyBinding
{ {
public class RulesetBindingsSection : SettingsSection public class RulesetBindingsSection : SettingsSection
{ {
public override IconUsage Icon => (ruleset.CreateInstance().CreateIcon() as SpriteIcon)?.Icon ?? OsuIcon.Hot; public override Drawable CreateIcon() => ruleset?.CreateInstance()?.CreateIcon() ?? new SpriteIcon
{
Icon = OsuIcon.Hot
};
public override string Header => ruleset.Name; public override string Header => ruleset.Name;
private readonly RulesetInfo ruleset; private readonly RulesetInfo ruleset;

View File

@ -13,9 +13,12 @@ namespace osu.Game.Overlays.Settings.Sections
{ {
public override string Header => "Audio"; public override string Header => "Audio";
public override IEnumerable<string> FilterTerms => base.FilterTerms.Concat(new[] { "sound" }); public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.VolumeUp
};
public override IconUsage Icon => FontAwesome.Solid.VolumeUp; public override IEnumerable<string> FilterTerms => base.FilterTerms.Concat(new[] { "sound" });
public AudioSection() public AudioSection()
{ {

View File

@ -10,7 +10,11 @@ namespace osu.Game.Overlays.Settings.Sections
public class DebugSection : SettingsSection public class DebugSection : SettingsSection
{ {
public override string Header => "Debug"; public override string Header => "Debug";
public override IconUsage Icon => FontAwesome.Solid.Bug;
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Bug
};
public DebugSection() public DebugSection()
{ {

View File

@ -13,7 +13,11 @@ namespace osu.Game.Overlays.Settings.Sections
public class GameplaySection : SettingsSection public class GameplaySection : SettingsSection
{ {
public override string Header => "Gameplay"; public override string Header => "Gameplay";
public override IconUsage Icon => FontAwesome.Regular.Circle;
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Regular.Circle
};
public GameplaySection() public GameplaySection()
{ {

View File

@ -10,7 +10,11 @@ namespace osu.Game.Overlays.Settings.Sections
public class GeneralSection : SettingsSection public class GeneralSection : SettingsSection
{ {
public override string Header => "General"; public override string Header => "General";
public override IconUsage Icon => FontAwesome.Solid.Cog;
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Cog
};
public GeneralSection() public GeneralSection()
{ {

View File

@ -10,7 +10,11 @@ namespace osu.Game.Overlays.Settings.Sections
public class GraphicsSection : SettingsSection public class GraphicsSection : SettingsSection
{ {
public override string Header => "Graphics"; public override string Header => "Graphics";
public override IconUsage Icon => FontAwesome.Solid.Laptop;
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Laptop
};
public GraphicsSection() public GraphicsSection()
{ {

View File

@ -10,7 +10,11 @@ namespace osu.Game.Overlays.Settings.Sections
public class InputSection : SettingsSection public class InputSection : SettingsSection
{ {
public override string Header => "Input"; public override string Header => "Input";
public override IconUsage Icon => FontAwesome.Regular.Keyboard;
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Keyboard
};
public InputSection(KeyBindingPanel keyConfig) public InputSection(KeyBindingPanel keyConfig)
{ {

View File

@ -11,7 +11,11 @@ namespace osu.Game.Overlays.Settings.Sections
public class MaintenanceSection : SettingsSection public class MaintenanceSection : SettingsSection
{ {
public override string Header => "Maintenance"; public override string Header => "Maintenance";
public override IconUsage Icon => FontAwesome.Solid.Wrench;
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.Wrench
};
public MaintenanceSection() public MaintenanceSection()
{ {

View File

@ -10,7 +10,11 @@ namespace osu.Game.Overlays.Settings.Sections
public class OnlineSection : SettingsSection public class OnlineSection : SettingsSection
{ {
public override string Header => "Online"; public override string Header => "Online";
public override IconUsage Icon => FontAwesome.Solid.GlobeAsia;
public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.GlobeAsia
};
public OnlineSection() public OnlineSection()
{ {

View File

@ -19,7 +19,10 @@ namespace osu.Game.Overlays.Settings.Sections
public override string Header => "Skin"; public override string Header => "Skin";
public override IconUsage Icon => FontAwesome.Solid.PaintBrush; public override Drawable CreateIcon() => new SpriteIcon
{
Icon = FontAwesome.Solid.PaintBrush
};
private readonly Bindable<SkinInfo> dropdownBindable = new Bindable<SkinInfo> { Default = SkinInfo.Default }; private readonly Bindable<SkinInfo> dropdownBindable = new Bindable<SkinInfo> { Default = SkinInfo.Default };
private readonly Bindable<int> configBindable = new Bindable<int>(); private readonly Bindable<int> configBindable = new Bindable<int>();

View File

@ -11,7 +11,6 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Graphics.Sprites;
namespace osu.Game.Overlays.Settings namespace osu.Game.Overlays.Settings
{ {
@ -20,7 +19,7 @@ namespace osu.Game.Overlays.Settings
protected FillFlowContainer FlowContent; protected FillFlowContainer FlowContent;
protected override Container<Drawable> Content => FlowContent; protected override Container<Drawable> Content => FlowContent;
public abstract IconUsage Icon { get; } public abstract Drawable CreateIcon();
public abstract string Header { get; } public abstract string Header { get; }
public IEnumerable<IFilterable> FilterableChildren => Children.OfType<IFilterable>(); public IEnumerable<IFilterable> FilterableChildren => Children.OfType<IFilterable>();

View File

@ -11,12 +11,13 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Settings namespace osu.Game.Overlays.Settings
{ {
public class SidebarButton : OsuButton public class SidebarButton : OsuButton
{ {
private readonly SpriteIcon drawableIcon; private readonly ConstrainedIconContainer iconContainer;
private readonly SpriteText headerText; private readonly SpriteText headerText;
private readonly Box selectionIndicator; private readonly Box selectionIndicator;
private readonly Container text; private readonly Container text;
@ -30,7 +31,7 @@ namespace osu.Game.Overlays.Settings
{ {
section = value; section = value;
headerText.Text = value.Header; headerText.Text = value.Header;
drawableIcon.Icon = value.Icon; iconContainer.Icon = value.CreateIcon();
} }
} }
@ -78,7 +79,7 @@ namespace osu.Game.Overlays.Settings
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
}, },
drawableIcon = new SpriteIcon iconContainer = new ConstrainedIconContainer
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,

View File

@ -33,6 +33,9 @@ namespace osu.Game.Overlays.Toolbar
private readonly Bindable<OverlayActivation> overlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All); private readonly Bindable<OverlayActivation> overlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
// Toolbar components like RulesetSelector should receive keyboard input events even when the toolbar is hidden.
public override bool PropagateNonPositionalInputSubTree => true;
public Toolbar() public Toolbar()
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
@ -148,7 +151,7 @@ namespace osu.Game.Overlays.Toolbar
protected override void PopOut() protected override void PopOut()
{ {
userButton?.StateContainer.Hide(); userButton.StateContainer?.Hide();
this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint); this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint);
this.FadeOut(transition_time); this.FadeOut(transition_time);

View File

@ -43,6 +43,15 @@ namespace osu.Game.Rulesets.UI
return true; return true;
} }
public virtual void Clear(bool disposeChildren = true)
{
ClearInternal(disposeChildren);
foreach (var kvp in startTimeMap)
kvp.Value.bindable.UnbindAll();
startTimeMap.Clear();
}
public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject); public int IndexOf(DrawableHitObject hitObject) => IndexOfInternal(hitObject);
private void onStartTimeChanged(DrawableHitObject hitObject) private void onStartTimeChanged(DrawableHitObject hitObject)

View File

@ -58,6 +58,14 @@ namespace osu.Game.Rulesets.UI.Scrolling
return result; return result;
} }
public override void Clear(bool disposeChildren = true)
{
base.Clear(disposeChildren);
initialStateCache.Invalidate();
hitObjectInitialStateCache.Clear();
}
private float scrollLength; private float scrollLength;
protected override void Update() protected override void Update()

View File

@ -30,6 +30,11 @@ namespace osu.Game.Tests.Visual
set => scrollingInfo.TimeRange.Value = value; set => scrollingInfo.TimeRange.Value = value;
} }
public ScrollingDirection Direction
{
set => scrollingInfo.Direction.Value = value;
}
public IScrollingInfo ScrollingInfo => scrollingInfo; public IScrollingInfo ScrollingInfo => scrollingInfo;
[Cached(Type = typeof(IScrollingInfo))] [Cached(Type = typeof(IScrollingInfo))]