mirror of
https://github.com/ppy/osu.git
synced 2025-02-15 04:12:57 +08:00
Merge branch 'master' into mania-keymod-optimisation
This commit is contained in:
commit
bf958d736b
@ -86,6 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
switch (State.Value)
|
switch (State.Value)
|
||||||
{
|
{
|
||||||
case ArmedState.Idle:
|
case ArmedState.Idle:
|
||||||
|
UnproxyContent();
|
||||||
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
|
this.Delay(HitObject.HitWindows.HalfWindowFor(HitResult.Miss)).Expire();
|
||||||
break;
|
break;
|
||||||
case ArmedState.Miss:
|
case ArmedState.Miss:
|
||||||
@ -93,6 +94,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
.Expire();
|
.Expire();
|
||||||
break;
|
break;
|
||||||
case ArmedState.Hit:
|
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;
|
var flash = circlePiece?.FlashBox;
|
||||||
if (flash != null)
|
if (flash != null)
|
||||||
{
|
{
|
||||||
|
@ -20,12 +20,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public class DrawableSwell : DrawableTaikoHitObject<Swell>
|
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_thick_border = 1.4f;
|
||||||
private const float target_ring_thin_border = 1f;
|
private const float target_ring_thin_border = 1f;
|
||||||
private const float target_ring_scale = 5f;
|
private const float target_ring_scale = 5f;
|
||||||
@ -40,7 +34,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private int userHits;
|
private int userHits;
|
||||||
|
|
||||||
private bool hasStarted;
|
|
||||||
private readonly SwellSymbolPiece symbol;
|
private readonly SwellSymbolPiece symbol;
|
||||||
|
|
||||||
public DrawableSwell(Swell swell)
|
public DrawableSwell(Swell swell)
|
||||||
@ -48,7 +41,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
{
|
{
|
||||||
FillMode = FillMode.Fit;
|
FillMode = FillMode.Fit;
|
||||||
|
|
||||||
AddInternal(bodyContainer = new Container
|
Content.Add(bodyContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Depth = 1,
|
Depth = 1,
|
||||||
@ -177,6 +170,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
|
case ArmedState.Idle:
|
||||||
|
UnproxyContent();
|
||||||
|
break;
|
||||||
case ArmedState.Hit:
|
case ArmedState.Hit:
|
||||||
bodyContainer.Delay(untilJudgement).ScaleTo(1.4f, out_transition_time);
|
bodyContainer.Delay(untilJudgement).ScaleTo(1.4f, out_transition_time);
|
||||||
break;
|
break;
|
||||||
@ -195,11 +191,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
X = Math.Max(0, X);
|
X = Math.Max(0, X);
|
||||||
|
|
||||||
double t = Math.Min(HitObject.StartTime, Time.Current);
|
double t = Math.Min(HitObject.StartTime, Time.Current);
|
||||||
if (t == HitObject.StartTime && !hasStarted)
|
if (t == HitObject.StartTime)
|
||||||
{
|
ProxyContent();
|
||||||
OnStart?.Invoke();
|
|
||||||
hasStarted = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool? lastWasCentre;
|
private bool? lastWasCentre;
|
||||||
|
@ -9,10 +9,75 @@ using OpenTK;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
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
|
where TaikoHitType : TaikoHitObject
|
||||||
{
|
{
|
||||||
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
|
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
|
||||||
@ -34,7 +99,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
|
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
|
||||||
|
|
||||||
InternalChild = MainPiece = CreateMainPiece();
|
Content.Add(MainPiece = CreateMainPiece());
|
||||||
MainPiece.KiaiMode = HitObject.Kiai;
|
MainPiece.KiaiMode = HitObject.Kiai;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,9 +109,5 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
|
|||||||
protected override string SampleNamespace => "Taiko";
|
protected override string SampleNamespace => "Taiko";
|
||||||
|
|
||||||
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
|
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)
|
if (barline != null)
|
||||||
barlineContainer.Add(barline.CreateProxy());
|
barlineContainer.Add(barline.CreateProxy());
|
||||||
|
|
||||||
// Swells should be moved at the very top of the playfield when they reach the hit target
|
var taikoObject = h as DrawableTaikoHitObject;
|
||||||
var swell = h as DrawableSwell;
|
if (taikoObject != null)
|
||||||
if (swell != null)
|
topLevelHitContainer.Add(taikoObject.CreateProxiedContent());
|
||||||
swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
|
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();
|
hitExplosionContainer.Children.FirstOrDefault(e => e.JudgedObject == judgedObject)?.VisualiseSecondHit();
|
||||||
else
|
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));
|
hitExplosionContainer.Add(new HitExplosion(judgedObject, isRim));
|
||||||
|
|
||||||
if (judgedObject.HitObject.Kiai)
|
if (judgedObject.HitObject.Kiai)
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class ImportBeatmapTest
|
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]
|
[Test]
|
||||||
public void TestImportWhenClosed()
|
public void TestImportWhenClosed()
|
||||||
@ -265,7 +265,7 @@ namespace osu.Game.Tests.Beatmaps.IO
|
|||||||
private string createTemporaryBeatmap()
|
private string createTemporaryBeatmap()
|
||||||
{
|
{
|
||||||
var temp = Path.GetTempFileName() + ".osz";
|
var temp = Path.GetTempFileName() + ".osz";
|
||||||
File.Copy(osz_path, temp, true);
|
File.Copy(TEST_OSZ_PATH, temp, true);
|
||||||
Assert.IsTrue(File.Exists(temp));
|
Assert.IsTrue(File.Exists(temp));
|
||||||
return temp;
|
return temp;
|
||||||
}
|
}
|
||||||
|
@ -4,28 +4,42 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Configuration;
|
||||||
using OpenTK;
|
using OpenTK;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
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 osu.Game.Screens.Edit.Screens.Compose.Timeline;
|
||||||
|
using OpenTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestCaseEditorComposeTimeline : OsuTestCase
|
public class TestCaseEditorComposeTimeline : EditorClockTestCase
|
||||||
{
|
{
|
||||||
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(TimelineArea), typeof(Timeline), typeof(TimelineButton) };
|
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(TimelineArea), typeof(Timeline), typeof(TimelineButton) };
|
||||||
|
|
||||||
public TestCaseEditorComposeTimeline()
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
{
|
{
|
||||||
|
Beatmap.Value = new WaveformTestBeatmap();
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new MusicController
|
new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
AutoSizeAxes = Axes.Both,
|
||||||
Origin = Anchor.TopCentre,
|
Direction = FillDirection.Vertical,
|
||||||
State = Visibility.Visible
|
Spacing = new Vector2(0, 5),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new StartStopButton(),
|
||||||
|
new AudioVisualiser(),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
new TimelineArea
|
new TimelineArea
|
||||||
{
|
{
|
||||||
@ -36,5 +50,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.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Overlays;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual
|
namespace osu.Game.Tests.Visual
|
||||||
{
|
{
|
||||||
@ -20,22 +19,14 @@ namespace osu.Game.Tests.Visual
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
Beatmap.Value = new WaveformTestBeatmap();
|
||||||
|
|
||||||
FillFlowContainer flow;
|
FillFlowContainer flow;
|
||||||
Child = flow = new FillFlowContainer
|
Child = flow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
Spacing = new Vector2(0, 10),
|
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)
|
for (int i = 1; i <= 16; i *= 2)
|
||||||
@ -44,10 +35,9 @@ namespace osu.Game.Tests.Visual
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Resolution = 1f / i,
|
Resolution = 1f / i,
|
||||||
|
Waveform = Beatmap.Value.Waveform,
|
||||||
};
|
};
|
||||||
|
|
||||||
Beatmap.ValueChanged += b => newDisplay.Waveform = b.Waveform;
|
|
||||||
|
|
||||||
flow.Add(new Container
|
flow.Add(new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -2,9 +2,12 @@
|
|||||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio.Track;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Audio;
|
using osu.Framework.Graphics.Audio;
|
||||||
|
using osu.Framework.Input;
|
||||||
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
@ -15,6 +18,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
|
|||||||
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
|
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
|
||||||
public readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
public readonly IBindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
|
||||||
|
|
||||||
|
private IAdjustableClock adjustableClock;
|
||||||
|
|
||||||
public Timeline()
|
public Timeline()
|
||||||
{
|
{
|
||||||
ZoomDuration = 200;
|
ZoomDuration = 200;
|
||||||
@ -25,8 +30,10 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
|
|||||||
private WaveformGraph waveform;
|
private WaveformGraph waveform;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IBindableBeatmap beatmap)
|
private void load(IBindableBeatmap beatmap, IAdjustableClock adjustableClock)
|
||||||
{
|
{
|
||||||
|
this.adjustableClock = adjustableClock;
|
||||||
|
|
||||||
Child = waveform = new WaveformGraph
|
Child = waveform = new WaveformGraph
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -46,12 +53,132 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
|
|||||||
waveform.Waveform = Beatmap.Value.Waveform;
|
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()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.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 };
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ScrollbarContainer CreateScrollbar(Direction direction) => new TimelineScrollbar(this, direction);
|
||||||
|
|
||||||
|
private class TimelineScrollbar : ScrollbarContainer
|
||||||
|
{
|
||||||
|
private readonly Timeline timeline;
|
||||||
|
|
||||||
|
public TimelineScrollbar(Timeline timeline, Direction scrollDir)
|
||||||
|
: base(scrollDir)
|
||||||
|
{
|
||||||
|
this.timeline = timeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||||
|
{
|
||||||
|
if (base.OnMouseDown(state, args))
|
||||||
|
{
|
||||||
|
timeline.beginUserDrag();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||||
|
{
|
||||||
|
timeline.endUserDrag();
|
||||||
|
return base.OnMouseUp(state, args);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,13 +25,20 @@ namespace osu.Game.Tests.Visual
|
|||||||
Clock = new EditorClock(new ControlPointInfo(), 5000, BeatDivisor) { IsCoupled = false };
|
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]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Dependencies.Cache(BeatDivisor);
|
|
||||||
Dependencies.CacheAs<IFrameBasedClock>(Clock);
|
|
||||||
Dependencies.CacheAs<IAdjustableClock>(Clock);
|
|
||||||
|
|
||||||
Beatmap.BindValueChanged(beatmapChanged, true);
|
Beatmap.BindValueChanged(beatmapChanged, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user