mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 08:33:21 +08:00
Merge branch 'master' into mania-playfield-inversions
This commit is contained in:
commit
1d9d240454
@ -29,9 +29,12 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
/// </summary>
|
||||
private const double decay_weight = 0.9;
|
||||
|
||||
private readonly bool isForCurrentRuleset;
|
||||
|
||||
public ManiaDifficultyCalculator(Ruleset ruleset, WorkingBeatmap beatmap)
|
||||
: base(ruleset, beatmap)
|
||||
{
|
||||
isForCurrentRuleset = beatmap.BeatmapInfo.Ruleset.Equals(ruleset.RulesetInfo);
|
||||
}
|
||||
|
||||
protected override DifficultyAttributes Calculate(IBeatmap beatmap, Mod[] mods, double timeRate)
|
||||
@ -129,22 +132,35 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
||||
return difficulty;
|
||||
}
|
||||
|
||||
protected override Mod[] DifficultyAdjustmentMods => new Mod[]
|
||||
protected override Mod[] DifficultyAdjustmentMods
|
||||
{
|
||||
new ManiaModDoubleTime(),
|
||||
new ManiaModHalfTime(),
|
||||
new ManiaModEasy(),
|
||||
new ManiaModHardRock(),
|
||||
new ManiaModKey1(),
|
||||
new ManiaModKey2(),
|
||||
new ManiaModKey3(),
|
||||
new ManiaModKey4(),
|
||||
new ManiaModKey5(),
|
||||
new ManiaModKey6(),
|
||||
new ManiaModKey7(),
|
||||
new ManiaModKey8(),
|
||||
new ManiaModKey9(),
|
||||
};
|
||||
get
|
||||
{
|
||||
var mods = new Mod[]
|
||||
{
|
||||
new ManiaModDoubleTime(),
|
||||
new ManiaModHalfTime(),
|
||||
new ManiaModEasy(),
|
||||
new ManiaModHardRock(),
|
||||
};
|
||||
|
||||
if (isForCurrentRuleset)
|
||||
return mods;
|
||||
|
||||
// if we are a convert, we can be played in any key mod.
|
||||
return mods.Concat(new Mod[]
|
||||
{
|
||||
new ManiaModKey1(),
|
||||
new ManiaModKey2(),
|
||||
new ManiaModKey3(),
|
||||
new ManiaModKey4(),
|
||||
new ManiaModKey5(),
|
||||
new ManiaModKey6(),
|
||||
new ManiaModKey7(),
|
||||
new ManiaModKey8(),
|
||||
new ManiaModKey9(),
|
||||
}).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -86,6 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
switch (State.Value)
|
||||
{
|
||||
case ArmedState.Idle:
|
||||
UnproxyContent();
|
||||
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
|
||||
break;
|
||||
case ArmedState.Miss:
|
||||
@ -93,6 +94,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
.Expire();
|
||||
break;
|
||||
case ArmedState.Hit:
|
||||
// If we're far enough away from the left stage, we should bring outselves in front of it
|
||||
if (X >= -0.05f)
|
||||
ProxyContent();
|
||||
|
||||
var flash = circlePiece?.FlashBox;
|
||||
if (flash != null)
|
||||
{
|
||||
|
@ -20,12 +20,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
public class DrawableSwell : DrawableTaikoHitObject<Swell>
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when the swell has reached the hit target, i.e. when CurrentTime >= StartTime.
|
||||
/// This is only ever invoked once.
|
||||
/// </summary>
|
||||
public event Action OnStart;
|
||||
|
||||
private const float target_ring_thick_border = 1.4f;
|
||||
private const float target_ring_thin_border = 1f;
|
||||
private const float target_ring_scale = 5f;
|
||||
@ -40,7 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
/// </summary>
|
||||
private int userHits;
|
||||
|
||||
private bool hasStarted;
|
||||
private readonly SwellSymbolPiece symbol;
|
||||
|
||||
public DrawableSwell(Swell swell)
|
||||
@ -48,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
FillMode = FillMode.Fit;
|
||||
|
||||
AddInternal(bodyContainer = new Container
|
||||
Content.Add(bodyContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = 1,
|
||||
@ -177,6 +170,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case ArmedState.Idle:
|
||||
UnproxyContent();
|
||||
break;
|
||||
case ArmedState.Hit:
|
||||
bodyContainer.Delay(untilJudgement).ScaleTo(1.4f, out_transition_time);
|
||||
break;
|
||||
@ -195,11 +191,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
X = Math.Max(0, X);
|
||||
|
||||
double t = Math.Min(HitObject.StartTime, Time.Current);
|
||||
if (t == HitObject.StartTime && !hasStarted)
|
||||
{
|
||||
OnStart?.Invoke();
|
||||
hasStarted = true;
|
||||
}
|
||||
if (t == HitObject.StartTime)
|
||||
ProxyContent();
|
||||
}
|
||||
|
||||
private bool? lastWasCentre;
|
||||
|
@ -9,10 +9,75 @@ using OpenTK;
|
||||
using System.Linq;
|
||||
using osu.Game.Audio;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Primitives;
|
||||
|
||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
{
|
||||
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableHitObject<TaikoHitObject>, IKeyBindingHandler<TaikoAction>
|
||||
public abstract class DrawableTaikoHitObject : DrawableHitObject<TaikoHitObject>, IKeyBindingHandler<TaikoAction>
|
||||
{
|
||||
protected readonly Container Content;
|
||||
private readonly Container proxiedContent;
|
||||
|
||||
private readonly Container nonProxiedContent;
|
||||
|
||||
protected DrawableTaikoHitObject(TaikoHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
InternalChildren = new[]
|
||||
{
|
||||
nonProxiedContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = Content = new Container { RelativeSizeAxes = Axes.Both }
|
||||
},
|
||||
proxiedContent = new Container { RelativeSizeAxes = Axes.Both }
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="proxiedContent"/> is proxied into an upper layer. We don't want to get masked away otherwise <see cref="proxiedContent"/> would too.
|
||||
/// </summary>
|
||||
protected override bool ComputeIsMaskedAway(RectangleF maskingBounds) => false;
|
||||
|
||||
private bool isProxied;
|
||||
|
||||
/// <summary>
|
||||
/// Moves <see cref="Content"/> to a layer proxied above the playfield.
|
||||
/// Does nothing is content is already proxied.
|
||||
/// </summary>
|
||||
protected void ProxyContent()
|
||||
{
|
||||
if (isProxied) return;
|
||||
isProxied = true;
|
||||
|
||||
nonProxiedContent.Remove(Content);
|
||||
proxiedContent.Add(Content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Moves <see cref="Content"/> to the normal hitobject layer.
|
||||
/// Does nothing is content is not currently proxied.
|
||||
/// </summary>
|
||||
protected void UnproxyContent()
|
||||
{
|
||||
if (!isProxied) return;
|
||||
isProxied = false;
|
||||
|
||||
proxiedContent.Remove(Content);
|
||||
nonProxiedContent.Add(Content);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a proxy for the content of this <see cref="DrawableTaikoHitObject"/>.
|
||||
/// </summary>
|
||||
public Drawable CreateProxiedContent() => proxiedContent.CreateProxy();
|
||||
|
||||
public abstract bool OnPressed(TaikoAction action);
|
||||
public virtual bool OnReleased(TaikoAction action) => false;
|
||||
}
|
||||
|
||||
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableTaikoHitObject
|
||||
where TaikoHitType : TaikoHitObject
|
||||
{
|
||||
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
|
||||
@ -34,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
|
||||
|
||||
InternalChild = MainPiece = CreateMainPiece();
|
||||
Content.Add(MainPiece = CreateMainPiece());
|
||||
MainPiece.KiaiMode = HitObject.Kiai;
|
||||
}
|
||||
|
||||
@ -44,9 +109,5 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
||||
protected override string SampleNamespace => "Taiko";
|
||||
|
||||
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
|
||||
|
||||
public abstract bool OnPressed(TaikoAction action);
|
||||
|
||||
public virtual bool OnReleased(TaikoAction action) => false;
|
||||
}
|
||||
}
|
||||
|
@ -216,10 +216,9 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
if (barline != null)
|
||||
barlineContainer.Add(barline.CreateProxy());
|
||||
|
||||
// Swells should be moved at the very top of the playfield when they reach the hit target
|
||||
var swell = h as DrawableSwell;
|
||||
if (swell != null)
|
||||
swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy());
|
||||
var taikoObject = h as DrawableTaikoHitObject;
|
||||
if (taikoObject != null)
|
||||
topLevelHitContainer.Add(taikoObject.CreateProxiedContent());
|
||||
}
|
||||
|
||||
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
|
||||
@ -244,19 +243,6 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == judgedObject)?.VisualiseSecondHit();
|
||||
else
|
||||
{
|
||||
if (judgedObject.X >= -0.05f && judgedObject is DrawableHit)
|
||||
{
|
||||
// If we're far enough away from the left stage, we should bring outselves in front of it
|
||||
// Todo: The following try-catch is temporary for replay rewinding support
|
||||
try
|
||||
{
|
||||
topLevelHitContainer.Add(judgedObject.CreateProxy());
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim));
|
||||
|
||||
if (judgedObject.HitObject.Kiai)
|
||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
[TestFixture]
|
||||
public class ImportBeatmapTest
|
||||
{
|
||||
private const string osz_path = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz";
|
||||
public const string TEST_OSZ_PATH = @"../../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz";
|
||||
|
||||
[Test]
|
||||
public void TestImportWhenClosed()
|
||||
@ -265,7 +265,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
||||
private string createTemporaryBeatmap()
|
||||
{
|
||||
var temp = Path.GetTempFileName() + ".osz";
|
||||
File.Copy(osz_path, temp, true);
|
||||
File.Copy(TEST_OSZ_PATH, temp, true);
|
||||
Assert.IsTrue(File.Exists(temp));
|
||||
return temp;
|
||||
}
|
||||
|
@ -4,28 +4,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using OpenTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestCaseEditorComposeTimeline : OsuTestCase
|
||||
public class TestCaseEditorComposeTimeline : EditorClockTestCase
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(TimelineArea), typeof(Timeline), typeof(TimelineButton) };
|
||||
|
||||
public TestCaseEditorComposeTimeline()
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(TimelineArea),
|
||||
typeof(Timeline),
|
||||
typeof(TimelineButton),
|
||||
typeof(CentreMarker)
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Beatmap.Value = new WaveformTestBeatmap();
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new MusicController
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
State = Visibility.Visible
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new StartStopButton(),
|
||||
new AudioVisualiser(),
|
||||
}
|
||||
},
|
||||
new TimelineArea
|
||||
{
|
||||
@ -36,5 +56,85 @@ namespace osu.Game.Tests.Visual
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class AudioVisualiser : CompositeDrawable
|
||||
{
|
||||
private readonly Drawable marker;
|
||||
|
||||
private readonly IBindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
||||
private IAdjustableClock adjustableClock;
|
||||
|
||||
public AudioVisualiser()
|
||||
{
|
||||
Size = new Vector2(250, 25);
|
||||
|
||||
InternalChildren = new[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.25f,
|
||||
},
|
||||
marker = new Box
|
||||
{
|
||||
RelativePositionAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 2,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAdjustableClock adjustableClock, IBindableBeatmap beatmap)
|
||||
{
|
||||
this.adjustableClock = adjustableClock;
|
||||
this.beatmap.BindTo(beatmap);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (beatmap.Value.Track.IsLoaded)
|
||||
marker.X = (float)(adjustableClock.CurrentTime / beatmap.Value.Track.Length);
|
||||
}
|
||||
}
|
||||
|
||||
private class StartStopButton : Button
|
||||
{
|
||||
private IAdjustableClock adjustableClock;
|
||||
private bool started;
|
||||
|
||||
public StartStopButton()
|
||||
{
|
||||
BackgroundColour = Color4.SlateGray;
|
||||
Size = new Vector2(100, 50);
|
||||
Text = "Start";
|
||||
|
||||
Action = onClick;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAdjustableClock adjustableClock)
|
||||
{
|
||||
this.adjustableClock = adjustableClock;
|
||||
}
|
||||
|
||||
private void onClick()
|
||||
{
|
||||
if (started)
|
||||
{
|
||||
adjustableClock.Stop();
|
||||
Text = "Start";
|
||||
}
|
||||
else
|
||||
{
|
||||
adjustableClock.Start();
|
||||
Text = "Stop";
|
||||
}
|
||||
|
||||
started = !started;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
@ -20,22 +19,14 @@ namespace osu.Game.Tests.Visual
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Beatmap.Value = new WaveformTestBeatmap();
|
||||
|
||||
FillFlowContainer flow;
|
||||
Child = flow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new MusicController
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Y = 100,
|
||||
State = Visibility.Visible
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 1; i <= 16; i *= 2)
|
||||
@ -44,10 +35,9 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Resolution = 1f / i,
|
||||
Waveform = Beatmap.Value.Waveform,
|
||||
};
|
||||
|
||||
Beatmap.ValueChanged += b => newDisplay.Waveform = b.Waveform;
|
||||
|
||||
flow.Add(new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
|
@ -13,7 +13,6 @@ using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using OpenTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
@ -66,7 +65,7 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
reset();
|
||||
AddStep("Set zoom = 10", () => scrollContainer.Zoom = 10);
|
||||
AddAssert("Box at 1/2", () => Precision.AlmostEquals(boxQuad.Centre, scrollQuad.Centre));
|
||||
AddAssert("Box at 1/2", () => Precision.AlmostEquals(boxQuad.Centre, scrollQuad.Centre, 1));
|
||||
AddAssert("Box width = 10x", () => Precision.AlmostEquals(boxQuad.Size.X, 10 * scrollQuad.Size.X));
|
||||
}
|
||||
|
||||
@ -77,16 +76,12 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
// Scroll in at 0.25
|
||||
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||
AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(3, 0)));
|
||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(0, 3)));
|
||||
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
|
||||
|
||||
// Scroll out at 0.25
|
||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||
AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(-3, 0)));
|
||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(0, -3)));
|
||||
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||
AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X));
|
||||
}
|
||||
@ -98,15 +93,11 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
// Scroll in at 0.25
|
||||
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
|
||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(0, 1)));
|
||||
|
||||
// Scroll in at 0.6
|
||||
AddStep("Move mouse to 0.75x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.75f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(1, 0)));
|
||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(0, 1)));
|
||||
AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||
|
||||
// Very hard to determine actual position, so approximate
|
||||
@ -115,15 +106,11 @@ namespace osu.Game.Tests.Visual
|
||||
AddAssert("Box at correct position (3)", () => Precision.DefinitelyBigger(boxQuad.TopLeft.X + 0.6f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.6f * scrollQuad.Size.X));
|
||||
|
||||
// Scroll out at 0.6
|
||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
|
||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(0, -1)));
|
||||
|
||||
// Scroll out at 0.25
|
||||
AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y)));
|
||||
AddStep("Press ctrl", () => InputManager.PressKey(Key.LControl));
|
||||
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(-1, 0)));
|
||||
AddStep("Release ctrl", () => InputManager.ReleaseKey(Key.LControl));
|
||||
AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(0, -1)));
|
||||
AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft));
|
||||
}
|
||||
|
||||
|
55
osu.Game.Tests/WaveformTestBeatmap.cs
Normal file
55
osu.Game.Tests/WaveformTestBeatmap.cs
Normal file
@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.IO.Archives;
|
||||
using osu.Game.Tests.Beatmaps.IO;
|
||||
|
||||
namespace osu.Game.Tests
|
||||
{
|
||||
/// <summary>
|
||||
/// A <see cref="WorkingBeatmap"/> that is used for testcases that include waveforms.
|
||||
/// </summary>
|
||||
public class WaveformTestBeatmap : WorkingBeatmap
|
||||
{
|
||||
private readonly ZipArchiveReader reader;
|
||||
private readonly FileStream stream;
|
||||
|
||||
public WaveformTestBeatmap()
|
||||
: base(new BeatmapInfo())
|
||||
{
|
||||
stream = File.OpenRead(ImportBeatmapTest.TEST_OSZ_PATH);
|
||||
reader = new ZipArchiveReader(stream);
|
||||
}
|
||||
|
||||
public override void Dispose()
|
||||
{
|
||||
base.Dispose();
|
||||
stream?.Dispose();
|
||||
reader?.Dispose();
|
||||
}
|
||||
|
||||
protected override IBeatmap GetBeatmap() => createTestBeatmap();
|
||||
|
||||
protected override Texture GetBackground() => null;
|
||||
|
||||
protected override Waveform GetWaveform() => new Waveform(getAudioStream());
|
||||
|
||||
protected override Track GetTrack() => new TrackBass(getAudioStream());
|
||||
|
||||
private Stream getAudioStream() => reader.GetStream(reader.Filenames.First(f => f.EndsWith(".mp3")));
|
||||
private Stream getBeatmapStream() => reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu")));
|
||||
|
||||
private Beatmap createTestBeatmap()
|
||||
{
|
||||
using (var beatmapStream = getBeatmapStream())
|
||||
using (var beatmapReader = new StreamReader(beatmapStream))
|
||||
return Decoder.GetDecoder<Beatmap>(beatmapReader).Decode(beatmapReader);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
|
||||
{
|
||||
public class CentreMarker : CompositeDrawable
|
||||
{
|
||||
private const float triangle_width = 20;
|
||||
private const float triangle_height = 10;
|
||||
private const float bar_width = 2;
|
||||
|
||||
public CentreMarker()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Size = new Vector2(20, 1);
|
||||
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = bar_width,
|
||||
},
|
||||
new Triangle
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Size = new Vector2(triangle_width, triangle_height),
|
||||
Scale = new Vector2(1, -1)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Colour = colours.Red;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,9 +2,13 @@
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
@ -15,25 +19,36 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
|
||||
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
|
||||
public readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
||||
|
||||
private IAdjustableClock adjustableClock;
|
||||
|
||||
public Timeline()
|
||||
{
|
||||
ZoomDuration = 200;
|
||||
ZoomEasing = Easing.OutQuint;
|
||||
Zoom = 10;
|
||||
ScrollbarVisible = false;
|
||||
}
|
||||
|
||||
private WaveformGraph waveform;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IBindableBeatmap beatmap)
|
||||
private void load(IBindableBeatmap beatmap, IAdjustableClock adjustableClock, OsuColour colours)
|
||||
{
|
||||
this.adjustableClock = adjustableClock;
|
||||
|
||||
Child = waveform = new WaveformGraph
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = OsuColour.FromHex("222"),
|
||||
Colour = colours.Blue.Opacity(0.2f),
|
||||
LowColour = colours.BlueLighter,
|
||||
MidColour = colours.BlueDark,
|
||||
HighColour = colours.BlueDarker,
|
||||
Depth = float.MaxValue
|
||||
};
|
||||
|
||||
// We don't want the centre marker to scroll
|
||||
AddInternal(new CentreMarker());
|
||||
|
||||
WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
|
||||
|
||||
Beatmap.BindTo(beatmap);
|
||||
@ -46,12 +61,102 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
|
||||
waveform.Waveform = Beatmap.Value.Waveform;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The track's time in the previous frame.
|
||||
/// </summary>
|
||||
private double lastTrackTime;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the user is currently dragging the timeline.
|
||||
/// </summary>
|
||||
private bool handlingDragInput;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the track was playing before a user drag event.
|
||||
/// </summary>
|
||||
private bool trackWasPlaying;
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
// We want time = 0 to be at the centre of the container when scrolled to the start
|
||||
// The extrema of track time should be positioned at the centre of the container when scrolled to the start or end
|
||||
Content.Margin = new MarginPadding { Horizontal = DrawWidth / 2 };
|
||||
|
||||
if (handlingDragInput)
|
||||
{
|
||||
// The user is dragging - the track should always follow the timeline
|
||||
seekTrackToCurrent();
|
||||
}
|
||||
else if (adjustableClock.IsRunning)
|
||||
{
|
||||
// If the user hasn't provided mouse input but the track is running, always follow the track
|
||||
scrollToTrackTime();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The track isn't playing, so we want to smooth-scroll once more, and re-enable wheel scrolling
|
||||
// There are two cases we have to be wary of:
|
||||
// 1) The user scrolls on this timeline: We want the track to follow us
|
||||
// 2) The user changes the track time through some other means (scrolling in the editor or overview timeline): We want to follow the track time
|
||||
|
||||
// The simplest way to cover both cases is by checking that inter-frame track times are identical
|
||||
if (adjustableClock.CurrentTime == lastTrackTime)
|
||||
{
|
||||
// The track hasn't been seeked externally
|
||||
seekTrackToCurrent();
|
||||
}
|
||||
else
|
||||
{
|
||||
// The track has been seeked externally
|
||||
scrollToTrackTime();
|
||||
}
|
||||
}
|
||||
|
||||
lastTrackTime = adjustableClock.CurrentTime;
|
||||
|
||||
void seekTrackToCurrent()
|
||||
{
|
||||
if (!(Beatmap.Value.Track is TrackVirtual))
|
||||
adjustableClock.Seek(Current / Content.DrawWidth * Beatmap.Value.Track.Length);
|
||||
}
|
||||
|
||||
void scrollToTrackTime()
|
||||
{
|
||||
if (!(Beatmap.Value.Track is TrackVirtual))
|
||||
ScrollTo((float)(adjustableClock.CurrentTime / Beatmap.Value.Track.Length) * Content.DrawWidth, false);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||
{
|
||||
if (base.OnMouseDown(state, args))
|
||||
{
|
||||
beginUserDrag();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||
{
|
||||
endUserDrag();
|
||||
return base.OnMouseUp(state, args);
|
||||
}
|
||||
|
||||
private void beginUserDrag()
|
||||
{
|
||||
handlingDragInput = true;
|
||||
trackWasPlaying = adjustableClock.IsRunning;
|
||||
adjustableClock.Stop();
|
||||
}
|
||||
|
||||
private void endUserDrag()
|
||||
{
|
||||
handlingDragInput = false;
|
||||
if (trackWasPlaying)
|
||||
adjustableClock.Start();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,10 +99,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
|
||||
|
||||
protected override bool OnScroll(InputState state)
|
||||
{
|
||||
if (!state.Keyboard.ControlPressed)
|
||||
if (state.Mouse.HasPreciseScroll)
|
||||
// for now, we don't support zoom when using a precision scroll device. this needs gesture support.
|
||||
return base.OnScroll(state);
|
||||
|
||||
setZoomTarget(zoomTarget + state.Mouse.ScrollDelta.X, zoomedContent.ToLocalSpace(state.Mouse.NativeState.Position).X);
|
||||
setZoomTarget(zoomTarget + state.Mouse.ScrollDelta.Y, zoomedContent.ToLocalSpace(state.Mouse.NativeState.Position).X);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -25,13 +25,20 @@ namespace osu.Game.Tests.Visual
|
||||
Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false };
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
|
||||
{
|
||||
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
|
||||
|
||||
dependencies.Cache(BeatDivisor);
|
||||
dependencies.CacheAs<IFrameBasedClock>(Clock);
|
||||
dependencies.CacheAs<IAdjustableClock>(Clock);
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Dependencies.Cache(BeatDivisor);
|
||||
Dependencies.CacheAs<IFrameBasedClock>(Clock);
|
||||
Dependencies.CacheAs<IAdjustableClock>(Clock);
|
||||
|
||||
Beatmap.BindValueChanged(beatmapChanged, true);
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user