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

Merge remote-tracking branch 'upstream/master' into user_overlay_tooltip

This commit is contained in:
Dean Herbert 2017-10-13 16:56:11 +09:00
commit 77c98a34e3
41 changed files with 1346 additions and 286 deletions

@ -1 +1 @@
Subproject commit 07e84f60b0d2ee443f366cb2e34bf25b680983dd
Subproject commit 3760443ea9bf682a1bbf6cfa6aa00ea9541c12aa

View File

@ -5,9 +5,9 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using System.Collections.Generic;
using System;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.UI;
namespace osu.Game.Rulesets.Catch.Beatmaps
{
@ -17,14 +17,37 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
protected override IEnumerable<CatchBaseHit> ConvertHitObject(HitObject obj, Beatmap beatmap)
{
if (!(obj is IHasXPosition))
var curveData = obj as IHasCurve;
var positionData = obj as IHasPosition;
var comboData = obj as IHasCombo;
if (positionData == null)
yield break;
if (curveData != null)
{
yield return new JuiceStream
{
StartTime = obj.StartTime,
Samples = obj.Samples,
ControlPoints = curveData.ControlPoints,
CurveType = curveData.CurveType,
Distance = curveData.Distance,
RepeatSamples = curveData.RepeatSamples,
RepeatCount = curveData.RepeatCount,
X = positionData.X / CatchPlayfield.BASE_WIDTH,
NewCombo = comboData?.NewCombo ?? false
};
yield break;
}
yield return new Fruit
{
StartTime = obj.StartTime,
NewCombo = (obj as IHasCombo)?.NewCombo ?? false,
X = ((IHasXPosition)obj).X / OsuPlayfield.BASE_SIZE.X
Samples = obj.Samples,
NewCombo = comboData?.NewCombo ?? false,
X = positionData.X / CatchPlayfield.BASE_WIDTH
};
}
}

View File

@ -0,0 +1,60 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public abstract class DrawableCatchHitObject<TObject> : DrawableCatchHitObject
where TObject : CatchBaseHit
{
public new TObject HitObject;
protected DrawableCatchHitObject(TObject hitObject)
: base(hitObject)
{
HitObject = hitObject;
}
}
public abstract class DrawableCatchHitObject : DrawableScrollingHitObject<CatchBaseHit>
{
protected DrawableCatchHitObject(CatchBaseHit hitObject)
: base(hitObject)
{
RelativePositionAxes = Axes.Both;
X = hitObject.X;
Y = (float)HitObject.StartTime;
}
public Func<CatchBaseHit, bool> CheckPosition;
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (timeOffset > 0)
AddJudgement(new Judgement { Result = CheckPosition?.Invoke(HitObject) ?? false ? HitResult.Perfect : HitResult.Miss });
}
private const float preempt = 1000;
protected override void UpdateState(ArmedState state)
{
using (BeginAbsoluteSequence(HitObject.StartTime - preempt))
{
// animation
this.FadeIn(200);
}
switch (state)
{
case ArmedState.Miss:
using (BeginAbsoluteSequence(HitObject.StartTime, true))
this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out);
break;
}
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) 2007-2017 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.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableDroplet : DrawableCatchHitObject<Droplet>
{
public DrawableDroplet(Droplet h)
: base(h)
{
Origin = Anchor.Centre;
Size = new Vector2(Pulp.PULP_SIZE);
AccentColour = h.ComboColour;
Masking = false;
}
[BackgroundDependencyLoader]
private void load()
{
Child = new Pulp
{
AccentColour = AccentColour,
Scale = new Vector2(0.8f),
};
}
}
}

View File

@ -1,72 +1,29 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
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.Framework.MathUtils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableFruit : DrawableScrollingHitObject<CatchBaseHit>
public class DrawableFruit : DrawableCatchHitObject<Fruit>
{
private const float pulp_size = 20;
private class Pulp : Circle, IHasAccentColour
{
public Pulp()
{
Size = new Vector2(pulp_size);
Blending = BlendingMode.Additive;
Colour = Color4.White.Opacity(0.9f);
}
private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour; }
set
{
accentColour = value;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Lighten(100),
};
}
}
}
public DrawableFruit(CatchBaseHit h)
public DrawableFruit(Fruit h)
: base(h)
{
Origin = Anchor.Centre;
Size = new Vector2(pulp_size * 2.2f, pulp_size * 2.8f);
RelativePositionAxes = Axes.Both;
X = h.X;
Size = new Vector2(Pulp.PULP_SIZE * 2.2f, Pulp.PULP_SIZE * 2.8f);
AccentColour = HitObject.ComboColour;
Masking = false;
Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
}
public Func<CatchBaseHit, bool> CheckPosition;
[BackgroundDependencyLoader]
private void load()
{
@ -114,30 +71,5 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
}
};
}
private const float preempt = 1000;
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (timeOffset > 0)
AddJudgement(new Judgement { Result = CheckPosition?.Invoke(HitObject) ?? false ? HitResult.Perfect : HitResult.Miss });
}
protected override void UpdateState(ArmedState state)
{
using (BeginAbsoluteSequence(HitObject.StartTime - preempt))
{
// animation
this.FadeIn(200);
}
switch (state)
{
case ArmedState.Miss:
using (BeginAbsoluteSequence(HitObject.StartTime, true))
this.FadeOut(250).RotateTo(Rotation * 2, 250, Easing.Out);
break;
}
}
}
}

View File

@ -0,0 +1,54 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableJuiceStream : DrawableCatchHitObject<JuiceStream>
{
private readonly Container dropletContainer;
public DrawableJuiceStream(JuiceStream s) : base(s)
{
RelativeSizeAxes = Axes.Both;
Height = (float)HitObject.Duration;
X = 0;
Child = dropletContainer = new Container
{
RelativeSizeAxes = Axes.Both,
RelativeChildOffset = new Vector2(0, (float)HitObject.StartTime),
RelativeChildSize = new Vector2(1, (float)HitObject.Duration)
};
foreach (CatchBaseHit tick in s.Ticks)
{
TinyDroplet tiny = tick as TinyDroplet;
if (tiny != null)
{
AddNested(new DrawableDroplet(tiny) { Scale = new Vector2(0.5f) });
continue;
}
Droplet droplet = tick as Droplet;
if (droplet != null)
AddNested(new DrawableDroplet(droplet));
Fruit fruit = tick as Fruit;
if (fruit != null)
AddNested(new DrawableFruit(fruit));
}
}
protected override void AddNested(DrawableHitObject<CatchBaseHit> h)
{
((DrawableCatchHitObject)h).CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
dropletContainer.Add(h);
base.AddNested(h);
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable.Pieces
{
public class Pulp : Circle, IHasAccentColour
{
public const float PULP_SIZE = 20;
public Pulp()
{
Size = new Vector2(PULP_SIZE);
Blending = BlendingMode.Additive;
Colour = Color4.White.Opacity(0.9f);
}
private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour; }
set
{
accentColour = value;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Glow,
Radius = 5,
Colour = accentColour.Lighten(100),
};
}
}
}
}

View File

@ -0,0 +1,169 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using osu.Framework.Lists;
namespace osu.Game.Rulesets.Catch.Objects
{
public class JuiceStream : CatchBaseHit, IHasCurve
{
/// <summary>
/// Positional distance that results in a duration of one second, before any speed adjustments.
/// </summary>
private const float base_scoring_distance = 100;
public readonly SliderCurve Curve = new SliderCurve();
public int RepeatCount { get; set; } = 1;
public double Velocity;
public double TickDistance;
public override void ApplyDefaults(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate;
}
public IEnumerable<CatchBaseHit> Ticks
{
get
{
SortedList<CatchBaseHit> ticks = new SortedList<CatchBaseHit>((a, b) => a.StartTime.CompareTo(b.StartTime));
if (TickDistance == 0)
return ticks;
var length = Curve.Distance;
var tickDistance = Math.Min(TickDistance, length);
var repeatDuration = length / Velocity;
var minDistanceFromEnd = Velocity * 0.01;
ticks.Add(new Fruit
{
Samples = Samples,
ComboColour = ComboColour,
StartTime = StartTime,
X = X
});
for (var repeat = 0; repeat < RepeatCount; repeat++)
{
var repeatStartTime = StartTime + repeat * repeatDuration;
var reversed = repeat % 2 == 1;
for (var d = tickDistance; d <= length; d += tickDistance)
{
if (d > length - minDistanceFromEnd)
break;
var timeProgress = d / length;
var distanceProgress = reversed ? 1 - timeProgress : timeProgress;
var lastTickTime = repeatStartTime + timeProgress * repeatDuration;
ticks.Add(new Droplet
{
StartTime = lastTickTime,
ComboColour = ComboColour,
X = Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
});
}
double tinyTickInterval = tickDistance / length * repeatDuration;
while (tinyTickInterval > 100)
tinyTickInterval /= 2;
for (double t = 0; t < repeatDuration; t += tinyTickInterval)
{
double progress = reversed ? 1 - t / repeatDuration : t / repeatDuration;
ticks.Add(new TinyDroplet
{
StartTime = repeatStartTime + t,
ComboColour = ComboColour,
X = Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
Samples = new SampleInfoList(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
});
}
ticks.Add(new Fruit
{
Samples = Samples,
ComboColour = ComboColour,
StartTime = repeatStartTime + repeatDuration,
X = Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
});
}
return ticks;
}
}
public double EndTime => StartTime + RepeatCount * Curve.Distance / Velocity;
public float EndX => Curve.PositionAt(ProgressAt(1)).X / CatchPlayfield.BASE_WIDTH;
public double Duration => EndTime - StartTime;
public double Distance
{
get { return Curve.Distance; }
set { Curve.Distance = value; }
}
public List<Vector2> ControlPoints
{
get { return Curve.ControlPoints; }
set { Curve.ControlPoints = value; }
}
public List<SampleInfoList> RepeatSamples { get; set; } = new List<SampleInfoList>();
public CurveType CurveType
{
get { return Curve.CurveType; }
set { Curve.CurveType = value; }
}
public Vector2 PositionAt(double progress) => Curve.PositionAt(ProgressAt(progress));
public double ProgressAt(double progress)
{
double p = progress * RepeatCount % 1;
if (RepeatAt(progress) % 2 == 1)
p = 1 - p;
return p;
}
public int RepeatAt(double progress) => (int)(progress * RepeatCount);
}
}

View File

@ -0,0 +1,9 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Catch.Objects
{
public class TinyDroplet : Droplet
{
}
}

View File

@ -21,6 +21,19 @@ namespace osu.Game.Rulesets.Catch.Scoring
{
foreach (var obj in beatmap.HitObjects)
{
var stream = obj as JuiceStream;
if (stream != null)
{
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
foreach (var unused in stream.Ticks)
AddJudgement(new CatchJudgement { Result = HitResult.Perfect });
continue;
}
var fruit = obj as Fruit;
if (fruit != null)

View File

@ -2,8 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests
{
@ -13,15 +11,5 @@ namespace osu.Game.Rulesets.Catch.Tests
public TestCaseCatchPlayer() : base(typeof(CatchRuleset))
{
}
protected override Beatmap CreateBeatmap()
{
var beatmap = new Beatmap();
for (int i = 0; i < 256; i++)
beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 });
return beatmap;
}
}
}

View File

@ -0,0 +1,27 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
namespace osu.Game.Rulesets.Catch.Tests
{
[TestFixture]
public class TestCaseCatchStacker : Game.Tests.Visual.TestCasePlayer
{
public TestCaseCatchStacker() : base(typeof(CatchRuleset))
{
}
protected override Beatmap CreateBeatmap()
{
var beatmap = new Beatmap();
for (int i = 0; i < 256; i++)
beatmap.HitObjects.Add(new Fruit { X = 0.5f, StartTime = i * 100, NewCombo = i % 8 == 0 });
return beatmap;
}
}
}

View File

@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class CatchPlayfield : ScrollingPlayfield
{
public static readonly float BASE_WIDTH = 512;
protected override Container<Drawable> Content => content;
private readonly Container<Drawable> content;
@ -28,8 +30,6 @@ namespace osu.Game.Rulesets.Catch.UI
Reversed.Value = true;
Size = new Vector2(1);
Anchor = Anchor.TopCentre;
Origin = Anchor.TopCentre;
@ -74,7 +74,7 @@ namespace osu.Game.Rulesets.Catch.UI
base.Add(h);
var fruit = (DrawableFruit)h;
var fruit = (DrawableCatchHitObject)h;
fruit.CheckPosition = CheckIfWeCanCatch;
}
@ -83,7 +83,11 @@ namespace osu.Game.Rulesets.Catch.UI
if (judgement.IsHit)
{
Vector2 screenPosition = judgedObject.ScreenSpaceDrawQuad.Centre;
Remove(judgedObject);
// todo: don't do this
(judgedObject.Parent as Container<DrawableHitObject>)?.Remove(judgedObject);
(judgedObject.Parent as Container)?.Remove(judgedObject);
catcher.Add(judgedObject, screenPosition);
}
}

View File

@ -32,8 +32,13 @@ namespace osu.Game.Rulesets.Catch.UI
protected override DrawableHitObject<CatchBaseHit> GetVisualRepresentation(CatchBaseHit h)
{
if (h is Fruit)
return new DrawableFruit(h);
var fruit = h as Fruit;
if (fruit != null)
return new DrawableFruit(fruit);
var stream = h as JuiceStream;
if (stream != null)
return new DrawableJuiceStream(stream);
return null;
}

View File

@ -50,14 +50,21 @@
<Compile Include="Beatmaps\CatchBeatmapProcessor.cs" />
<Compile Include="CatchDifficultyCalculator.cs" />
<Compile Include="CatchInputManager.cs" />
<Compile Include="Objects\Drawable\DrawableCatchHitObject.cs" />
<Compile Include="Objects\Drawable\DrawableDroplet.cs" />
<Compile Include="Objects\Drawable\DrawableJuiceStream.cs" />
<Compile Include="Objects\Drawable\Pieces\Pulp.cs" />
<Compile Include="Objects\JuiceStream.cs" />
<Compile Include="Scoring\CatchScoreProcessor.cs" />
<Compile Include="Judgements\CatchJudgement.cs" />
<Compile Include="Objects\CatchBaseHit.cs" />
<Compile Include="Objects\Drawable\DrawableFruit.cs" />
<Compile Include="Objects\Droplet.cs" />
<Compile Include="Objects\Fruit.cs" />
<Compile Include="Objects\TinyDroplet.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Tests\TestCaseCatcher.cs" />
<Compile Include="Tests\TestCaseCatchStacker.cs" />
<Compile Include="Tests\TestCaseCatchPlayer.cs" />
<Compile Include="UI\Catcher.cs" />
<Compile Include="UI\CatchRulesetContainer.cs" />
@ -79,11 +86,6 @@
<Name>osu.Framework</Name>
<Private>False</Private>
</ProjectReference>
<ProjectReference Include="..\osu.Game.Rulesets.Osu\osu.Game.Rulesets.Osu.csproj">
<Project>{C92A607B-1FDD-4954-9F92-03FF547D9080}</Project>
<Name>osu.Game.Rulesets.Osu</Name>
<Private>False</Private>
</ProjectReference>
<ProjectReference Include="..\osu.Game\osu.Game.csproj">
<Project>{2a66dd92-adb1-4994-89e2-c94e04acda0d}</Project>
<Name>osu.Game</Name>

View File

@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
if (records.Count > 0)
{
var record = records.Peek();
while (Time.Current - records.Peek().Time > spm_count_duration)
while (records.Count > 0 && Time.Current - records.Peek().Time > spm_count_duration)
record = records.Dequeue();
SpinsPerMinute = (currentRotation - record.Rotation) / (Time.Current - record.Time) * 1000 * 60 / 360;
}

View File

@ -573,6 +573,8 @@ namespace osu.Game.Beatmaps
}
catch { return new TrackVirtual(); }
}
protected override Waveform GetWaveform() => new Waveform(store.GetStream(getPathForFile(Metadata.AudioFile)));
}
/// <summary>

View File

@ -43,6 +43,7 @@ namespace osu.Game.Beatmaps
protected abstract Beatmap GetBeatmap();
protected abstract Texture GetBackground();
protected abstract Track GetTrack();
protected virtual Waveform GetWaveform() => new Waveform();
private Beatmap beatmap;
private readonly object beatmapLock = new object();
@ -96,6 +97,17 @@ namespace osu.Game.Beatmaps
}
}
private Waveform waveform;
private readonly object waveformLock = new object();
public Waveform Waveform
{
get
{
lock (waveformLock)
return waveform ?? (waveform = GetWaveform());
}
}
public bool TrackLoaded => track != null;
public void TransferTo(WorkingBeatmap other)
@ -114,6 +126,8 @@ namespace osu.Game.Beatmaps
{
background?.Dispose();
background = null;
waveform?.Dispose();
}
public void DisposeTrack()

View File

@ -1,27 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics.Containers;
using OpenTK;
using osu.Framework.Graphics;
namespace osu.Game.Graphics.Processing
{
internal class RatioAdjust : Container
{
public RatioAdjust()
{
RelativeSizeAxes = Axes.Both;
}
protected override void Update()
{
base.Update();
Vector2 parent = Parent.DrawSize;
Scale = new Vector2(Math.Min(parent.Y / 768f, parent.X / 1024f));
Size = new Vector2(1 / Scale.X);
}
}
}

View File

@ -27,13 +27,28 @@ namespace osu.Game.Graphics.UserInterface
set { flashColour = value; }
}
private Color4? iconColour;
/// <summary>
/// The icon colour. This does not affect <see cref="IconButton.Colour"/>.
/// </summary>
public Color4 IconColour
{
get { return icon.Colour; }
set { icon.Colour = value; }
get { return iconColour ?? Color4.White; }
set
{
iconColour = value;
icon.Colour = value;
}
}
private Color4? iconHoverColour;
/// <summary>
/// The icon colour while the <see cref="IconButton"/> is hovered.
/// </summary>
public Color4 IconHoverColour
{
get { return iconHoverColour ?? IconColour; }
set { iconHoverColour = value; }
}
private Color4? hoverColour;
@ -133,12 +148,14 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnHover(InputState state)
{
hover.FadeIn(500, Easing.OutQuint);
icon.FadeColour(IconHoverColour, 500, Easing.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
hover.FadeOut(500, Easing.OutQuint);
icon.FadeColour(IconColour, 500, Easing.OutQuint);
base.OnHoverLost(state);
}

View File

@ -51,7 +51,7 @@ namespace osu.Game.Online.API.Requests
private int onlineId { get; set; }
[JsonProperty(@"creator")]
private string creatorUsername;
private string creatorUsername { get; set; }
[JsonProperty(@"user_id")]
private long creatorId = 1;

View File

@ -15,7 +15,6 @@ using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.Processing;
using osu.Game.Online.API;
using SQLite.Net;
using osu.Framework.Graphics.Performance;
@ -186,7 +185,7 @@ namespace osu.Game
GlobalKeyBindingInputManager globalBinding;
base.Content.Add(new RatioAdjust
base.Content.Add(new DrawSizePreservingFillContainer
{
Children = new Drawable[]
{

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@ -12,9 +11,10 @@ using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Overlays.Direct;
using osu.Framework.Configuration;
namespace osu.Game.Overlays.BeatmapSet
{
@ -22,73 +22,16 @@ namespace osu.Game.Overlays.BeatmapSet
{
private const float transition_duration = 500;
private readonly Container audioWrapper;
private readonly Box bg, progress;
private readonly SpriteIcon icon;
private readonly LoadingAnimation loadingAnimation;
private readonly PlayButton playButton;
private Track preview;
private Track preview => playButton.Preview;
private Bindable<bool> playing => playButton.Playing;
private bool loading
{
set
{
if (value)
{
loadingAnimation.Show();
icon.FadeOut(transition_duration * 5, Easing.OutQuint);
}
else
{
loadingAnimation.Hide();
icon.FadeIn(transition_duration, Easing.OutQuint);
}
}
}
private BeatmapSetInfo beatmapSet;
public BeatmapSetInfo BeatmapSet
{
get { return beatmapSet; }
set
{
if (value == beatmapSet) return;
beatmapSet = value;
Playing = false;
preview = null;
}
}
private bool playing;
public bool Playing
{
get { return playing; }
set
{
if (value == playing) return;
playing = value;
if (preview == null)
{
loading = true;
audioWrapper.Child = new AsyncLoadWrapper(new AudioLoadWrapper(BeatmapSet)
{
OnLoadComplete = d =>
{
loading = false;
preview = (d as AudioLoadWrapper)?.Preview;
Playing = Playing;
updatePlayingState();
},
});
return;
}
updatePlayingState();
}
get { return playButton.BeatmapSet; }
set { playButton.BeatmapSet = value; }
}
public PreviewButton()
@ -97,7 +40,6 @@ namespace osu.Game.Overlays.BeatmapSet
Children = new Drawable[]
{
audioWrapper = new Container(),
bg = new Box
{
RelativeSizeAxes = Axes.Both,
@ -116,22 +58,16 @@ namespace osu.Game.Overlays.BeatmapSet
Alpha = 0f,
},
},
icon = new SpriteIcon
playButton = new PlayButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.fa_play,
Size = new Vector2(18),
Shadow = false,
},
loadingAnimation = new LoadingAnimation
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
};
Action = () => Playing = !Playing;
Action = () => playing.Value = !playing.Value;
playing.ValueChanged += newValue => progress.FadeTo(newValue ? 1 : 0, 100);
}
[BackgroundDependencyLoader]
@ -144,20 +80,15 @@ namespace osu.Game.Overlays.BeatmapSet
{
base.Update();
if (Playing && preview != null)
if (playing.Value && preview != null)
{
progress.Width = (float)(preview.CurrentTime / preview.Length);
if (preview.HasCompleted)
{
Playing = false;
preview = null;
}
}
}
protected override void Dispose(bool isDisposing)
{
Playing = false;
playing.Value = false;
base.Dispose(isDisposing);
}
@ -172,47 +103,5 @@ namespace osu.Game.Overlays.BeatmapSet
bg.FadeColour(Color4.Black.Opacity(0.25f), 100);
base.OnHoverLost(state);
}
private void updatePlayingState()
{
if (preview == null) return;
if (Playing)
{
icon.Icon = FontAwesome.fa_stop;
progress.FadeIn(100);
preview.Seek(0);
preview.Start();
}
else
{
icon.Icon = FontAwesome.fa_play;
progress.FadeOut(100);
preview.Stop();
}
}
private class AudioLoadWrapper : Drawable
{
private readonly string preview;
public Track Preview;
public AudioLoadWrapper(BeatmapSetInfo set)
{
preview = set.OnlineInfo.Preview;
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
if (!string.IsNullOrEmpty(preview))
{
Preview = audio.Track.Get(preview);
Preview.Volume.Value = 0.5;
}
}
}
}
}

View File

@ -21,6 +21,11 @@ namespace osu.Game.Overlays.Direct
private const float vertical_padding = 5;
private FillFlowContainer bottomPanel;
private PlayButton playButton;
private Box progressBar;
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap)
{
@ -87,6 +92,15 @@ namespace osu.Game.Overlays.Direct
{
RelativeSizeAxes = Axes.Both,
},
progressBar = new Box
{
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
BypassAutoSizeAxes = Axes.Both,
Size = new Vector2(0, 3),
Alpha = 0,
Colour = colours.Yellow,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
@ -178,6 +192,12 @@ namespace osu.Game.Overlays.Direct
new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0),
},
},
playButton = new PlayButton(SetInfo)
{
Margin = new MarginPadding { Top = 5, Left = 10 },
Size = new Vector2(30),
Alpha = 0,
},
});
}
}

View File

@ -28,8 +28,14 @@ namespace osu.Game.Overlays.Direct
Height = height;
}
private PlayButton playButton;
private Box progressBar;
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
[BackgroundDependencyLoader]
private void load(LocalisationEngine localisation)
private void load(LocalisationEngine localisation, OsuColour colours)
{
Content.CornerRadius = 5;
@ -48,29 +54,50 @@ namespace osu.Game.Overlays.Direct
{
new FillFlowContainer
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Direction = FillDirection.Horizontal,
LayoutEasing = Easing.OutQuint,
LayoutDuration = 120,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
new OsuSpriteText
playButton = new PlayButton(SetInfo)
{
Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
TextSize = 18,
Font = @"Exo2.0-BoldItalic",
},
new OsuSpriteText
{
Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = @"Exo2.0-BoldItalic",
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Size = new Vector2(height / 2),
FillMode = FillMode.Fit,
Alpha = 0,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Children = GetDifficultyIcons(),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new OsuSpriteText
{
Current = localisation.GetUnicodePreference(SetInfo.Metadata.TitleUnicode, SetInfo.Metadata.Title),
TextSize = 18,
Font = @"Exo2.0-BoldItalic",
},
new OsuSpriteText
{
Current = localisation.GetUnicodePreference(SetInfo.Metadata.ArtistUnicode, SetInfo.Metadata.Artist),
Font = @"Exo2.0-BoldItalic",
},
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Children = GetDifficultyIcons(),
},
},
},
},
}
},
new FillFlowContainer
{
@ -126,6 +153,16 @@ namespace osu.Game.Overlays.Direct
},
},
},
progressBar = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
BypassAutoSizeAxes = Axes.Y,
Size = new Vector2(0, 3),
Alpha = 0,
Colour = colours.Yellow,
},
});
}
}

View File

@ -20,6 +20,8 @@ using osu.Game.Online.API;
using osu.Framework.Logging;
using osu.Game.Overlays.Notifications;
using osu.Game.Online.API.Requests;
using osu.Framework.Configuration;
using osu.Framework.Audio.Track;
namespace osu.Game.Overlays.Direct
{
@ -39,6 +41,11 @@ namespace osu.Game.Overlays.Direct
private NotificationOverlay notifications;
private BeatmapSetOverlay beatmapSetOverlay;
public Track Preview => PlayButton.Preview;
public Bindable<bool> PreviewPlaying => PlayButton.Playing;
protected abstract PlayButton PlayButton { get; }
protected abstract Box PreviewBar { get; }
protected override Container<Drawable> Content => content;
protected DirectPanel(BeatmapSetInfo setInfo)
@ -104,10 +111,21 @@ namespace osu.Game.Overlays.Direct
attachDownload(downloadRequest);
}
protected override void Update()
{
base.Update();
if (PreviewPlaying && Preview != null)
{
PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length);
}
}
protected override bool OnHover(InputState state)
{
content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint);
content.MoveToY(-4, hover_transition_time, Easing.OutQuint);
PlayButton.FadeIn(120, Easing.InOutQuint);
return base.OnHover(state);
}
@ -116,6 +134,8 @@ namespace osu.Game.Overlays.Direct
{
content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint);
content.MoveToY(0, hover_transition_time, Easing.OutQuint);
if (!PreviewPlaying)
PlayButton.FadeOut(120, Easing.InOutQuint);
base.OnHoverLost(state);
}
@ -123,6 +143,7 @@ namespace osu.Game.Overlays.Direct
protected override bool OnClick(InputState state)
{
ShowInformation();
PreviewPlaying.Value = false;
return true;
}
@ -183,6 +204,9 @@ namespace osu.Game.Overlays.Direct
{
base.LoadComplete();
this.FadeInFromZero(200, Easing.Out);
PreviewPlaying.ValueChanged += newValue => PlayButton.FadeTo(newValue || IsHovered ? 1 : 0, 120, Easing.InOutQuint);
PreviewPlaying.ValueChanged += newValue => PreviewBar.FadeTo(newValue ? 1 : 0, 120, Easing.InOutQuint);
}
protected List<DifficultyIcon> GetDifficultyIcons()

View File

@ -0,0 +1,181 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Direct
{
public class PlayButton : Container
{
public readonly Bindable<bool> Playing = new Bindable<bool>();
public Track Preview { get; private set; }
private BeatmapSetInfo beatmapSet;
public BeatmapSetInfo BeatmapSet
{
get { return beatmapSet; }
set
{
if (value == beatmapSet) return;
beatmapSet = value;
Playing.Value = false;
trackLoader = null;
Preview = null;
}
}
private Color4 hoverColour;
private readonly SpriteIcon icon;
private readonly LoadingAnimation loadingAnimation;
private const float transition_duration = 500;
private bool loading
{
set
{
if (value)
{
loadingAnimation.Show();
icon.FadeOut(transition_duration * 5, Easing.OutQuint);
}
else
{
loadingAnimation.Hide();
icon.FadeIn(transition_duration, Easing.OutQuint);
}
}
}
public PlayButton(BeatmapSetInfo setInfo = null)
{
BeatmapSet = setInfo;
AddRange(new Drawable[]
{
icon = new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fit,
RelativeSizeAxes = Axes.Both,
Icon = FontAwesome.fa_play,
},
loadingAnimation = new LoadingAnimation(),
});
Playing.ValueChanged += playing =>
{
icon.Icon = playing ? FontAwesome.fa_pause : FontAwesome.fa_play;
icon.FadeColour(playing || IsHovered ? hoverColour : Color4.White, 120, Easing.InOutQuint);
updatePreviewTrack(playing);
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colour)
{
hoverColour = colour.Yellow;
}
protected override bool OnClick(InputState state)
{
Playing.Value = !Playing.Value;
return true;
}
protected override bool OnHover(InputState state)
{
icon.FadeColour(hoverColour, 120, Easing.InOutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
if (!Playing.Value)
icon.FadeColour(Color4.White, 120, Easing.InOutQuint);
base.OnHoverLost(state);
}
protected override void Update()
{
base.Update();
if (Preview?.HasCompleted ?? false)
{
Playing.Value = false;
Preview = null;
}
}
private void updatePreviewTrack(bool playing)
{
if (playing)
{
if (Preview == null)
{
beginAudioLoad();
return;
}
Preview.Seek(0);
Preview.Start();
}
else
{
Preview?.Stop();
}
}
private TrackLoader trackLoader;
private void beginAudioLoad()
{
if (trackLoader != null) return;
Add(new AsyncLoadWrapper(trackLoader = new TrackLoader($"https://b.ppy.sh/preview/{BeatmapSet.OnlineBeatmapSetID}.mp3")
{
OnLoadComplete = d =>
{
// we may have been replaced by another loader
if (trackLoader != d) return;
Preview = (d as TrackLoader)?.Preview;
Playing.TriggerChange();
},
}));
}
private class TrackLoader : Drawable
{
private readonly string preview;
public Track Preview;
public TrackLoader(string preview)
{
this.preview = preview;
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
if (!string.IsNullOrEmpty(preview))
{
Preview = audio.Track.Get(preview);
Preview.Volume.Value = 0.5;
}
}
}
}
}

View File

@ -32,6 +32,7 @@ namespace osu.Game.Overlays
private readonly FillFlowContainer resultCountsContainer;
private readonly OsuSpriteText resultCountsText;
private FillFlowContainer<DirectPanel> panels;
private DirectPanel playing;
protected override Color4 BackgroundColour => OsuColour.FromHex(@"485e74");
protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"465b71");
@ -201,6 +202,12 @@ namespace osu.Game.Overlays
panels.FadeOut(200);
panels.Expire();
panels = null;
if (playing != null)
{
playing.PreviewPlaying.Value = false;
playing = null;
}
}
if (BeatmapSets == null) return;
@ -227,6 +234,17 @@ namespace osu.Game.Overlays
{
if (panels != null) ScrollFlow.Remove(panels);
ScrollFlow.Add(panels = newPanels);
foreach (DirectPanel panel in p.Children)
panel.PreviewPlaying.ValueChanged += newValue =>
{
if (newValue)
{
if (playing != null && playing != panel)
playing.PreviewPlaying.Value = false;
playing = panel;
}
};
});
}

View File

@ -159,7 +159,7 @@ namespace osu.Game.Overlays
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Both,
});
}

View File

@ -90,6 +90,7 @@ namespace osu.Game.Overlays.MedalSplash
},
description = new TextFlowContainer
{
TextAnchor = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
@ -115,15 +116,16 @@ namespace osu.Game.Overlays.MedalSplash
medalSprite.Texture = textures.Get(medal.ImageUrl);
medalGlow.Texture = textures.Get(@"MedalSplash/medal-glow");
description.Colour = colours.BlueLight;
unlocked.Position = new Vector2(0f, medalContainer.Size.Y / 2 + 10);
infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90);
}
protected override void LoadComplete()
{
base.LoadComplete();
updateState();
unlocked.Position = new Vector2(0f, medalContainer.DrawSize.Y / 2 + 10);
infoFlow.Position = new Vector2(0f, unlocked.Position.Y + 90);
}
public DisplayState State
@ -172,6 +174,7 @@ namespace osu.Game.Overlays.MedalSplash
this.ScaleTo(scale_when_full, duration, Easing.OutExpo);
this.MoveToY(MedalOverlay.DISC_SIZE / 2 - 60, duration, Easing.OutExpo);
unlocked.Show();
name.FadeInFromZero(duration + 100);
description.FadeInFromZero(duration * 2);
break;

View File

@ -200,4 +200,4 @@ namespace osu.Game.Rulesets.Objects
return interpolateVertices(indexOfDistance(d), d) + Offset;
}
}
}
}

View File

@ -6,6 +6,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
namespace osu.Game.Screens.Edit.Screens.Compose
{
@ -13,6 +14,7 @@ namespace osu.Game.Screens.Edit.Screens.Compose
{
public Compose()
{
ScrollableTimeline timeline;
Children = new[]
{
new Container
@ -31,11 +33,22 @@ namespace osu.Game.Screens.Edit.Screens.Compose
{
Name = "Content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 17, Vertical = 10 }
Padding = new MarginPadding { Horizontal = 17, Vertical = 10 },
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 115 },
Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both }
}
}
}
}
}
};
timeline.Beatmap.BindTo(Beatmap);
}
}
}

View File

@ -0,0 +1,33 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Audio;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
{
public class BeatmapWaveformGraph : CompositeDrawable
{
public readonly Bindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
private readonly WaveformGraph graph;
public BeatmapWaveformGraph()
{
InternalChild = graph = new WaveformGraph { RelativeSizeAxes = Axes.Both };
Beatmap.ValueChanged += b => graph.Waveform = b.Waveform;
}
/// <summary>
/// Gets or sets the <see cref="WaveformGraph.Resolution"/>.
/// </summary>
public float Resolution
{
get { return graph.Resolution; }
set { graph.Resolution = value; }
}
}
}

View File

@ -0,0 +1,131 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
{
public class ScrollableTimeline : CompositeDrawable
{
public readonly Bindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
private readonly ScrollingTimelineContainer timelineContainer;
public ScrollableTimeline()
{
Masking = true;
CornerRadius = 5;
OsuCheckbox hitObjectsCheckbox;
OsuCheckbox hitSoundsCheckbox;
OsuCheckbox waveformCheckbox;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex("111")
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new Container
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex("222")
},
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Y,
Width = 160,
Padding = new MarginPadding { Horizontal = 15 },
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 4),
Children = new[]
{
hitObjectsCheckbox = new OsuCheckbox { LabelText = "Hitobjects" },
hitSoundsCheckbox = new OsuCheckbox { LabelText = "Hitsounds" },
waveformCheckbox = new OsuCheckbox { LabelText = "Waveform" }
}
}
}
},
new Container
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex("333")
},
new Container<TimelineButton>
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Masking = true,
Children = new[]
{
new TimelineButton
{
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
Icon = FontAwesome.fa_search_plus,
Action = () => timelineContainer.Zoom++
},
new TimelineButton
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Y,
Height = 0.5f,
Icon = FontAwesome.fa_search_minus,
Action = () => timelineContainer.Zoom--
},
}
}
}
},
timelineContainer = new ScrollingTimelineContainer { RelativeSizeAxes = Axes.Y }
}
}
};
hitObjectsCheckbox.Current.Value = true;
hitSoundsCheckbox.Current.Value = true;
waveformCheckbox.Current.Value = true;
timelineContainer.Beatmap.BindTo(Beatmap);
timelineContainer.WaveformVisible.BindTo(waveformCheckbox.Current);
}
protected override void Update()
{
base.Update();
timelineContainer.Size = new Vector2(DrawSize.X - timelineContainer.DrawPosition.X, 1);
}
}
}

View File

@ -0,0 +1,141 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using OpenTK;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
{
internal class ScrollingTimelineContainer : ScrollContainer
{
public readonly Bindable<bool> HitObjectsVisible = new Bindable<bool>();
public readonly Bindable<bool> HitSoundsVisible = new Bindable<bool>();
public readonly Bindable<bool> WaveformVisible = new Bindable<bool>();
public readonly Bindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
private readonly BeatmapWaveformGraph waveform;
public ScrollingTimelineContainer()
: base(Direction.Horizontal)
{
Masking = true;
Add(waveform = new BeatmapWaveformGraph
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex("222"),
Depth = float.MaxValue
});
Content.AutoSizeAxes = Axes.None;
Content.RelativeSizeAxes = Axes.Both;
waveform.Beatmap.BindTo(Beatmap);
WaveformVisible.ValueChanged += waveformVisibilityChanged;
Zoom = 10;
}
private float minZoom = 1;
/// <summary>
/// The minimum zoom level allowed.
/// </summary>
public float MinZoom
{
get { return minZoom; }
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value));
if (minZoom == value)
return;
minZoom = value;
// Update the zoom level
Zoom = Zoom;
}
}
private float maxZoom = 30;
/// <summary>
/// The maximum zoom level allowed.
/// </summary>
public float MaxZoom
{
get { return maxZoom; }
set
{
if (value <= 0)
throw new ArgumentOutOfRangeException(nameof(value));
if (maxZoom == value)
return;
maxZoom = value;
// Update the zoom level
Zoom = Zoom;
}
}
private float zoom = 1;
/// <summary>
/// The current zoom level.
/// </summary>
public float Zoom
{
get { return zoom; }
set
{
value = MathHelper.Clamp(value, MinZoom, MaxZoom);
if (zoom == value)
return;
zoom = value;
// Make the zoom target default to the center of the graph if it hasn't been set
if (relativeContentZoomTarget == null)
relativeContentZoomTarget = ToSpaceOfOtherDrawable(DrawSize / 2, Content).X / Content.DrawSize.X;
if (localZoomTarget == null)
localZoomTarget = DrawSize.X / 2;
Content.ResizeWidthTo(Zoom);
// Update the scroll position to focus on the zoom target
float scrollPos = Content.DrawSize.X * relativeContentZoomTarget.Value - localZoomTarget.Value;
ScrollTo(scrollPos, false);
relativeContentZoomTarget = null;
localZoomTarget = null;
}
}
/// <summary>
/// Zoom target as a relative position in the <see cref="Content"/> space.
/// </summary>
private float? relativeContentZoomTarget;
/// <summary>
/// Zoom target as a position in our local space.
/// </summary>
private float? localZoomTarget;
protected override bool OnWheel(InputState state)
{
if (!state.Keyboard.ControlPressed)
return base.OnWheel(state);
relativeContentZoomTarget = Content.ToLocalSpace(state.Mouse.NativeState.Position).X / Content.DrawSize.X;
localZoomTarget = ToLocalSpace(state.Mouse.NativeState.Position).X;
Zoom += state.Mouse.WheelDelta;
return true;
}
private void waveformVisibilityChanged(bool visible) => waveform.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
}
}

View File

@ -0,0 +1,52 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Edit.Screens.Compose.Timeline
{
public class TimelineButton : CompositeDrawable
{
public Action Action;
public readonly BindableBool Enabled = new BindableBool(true);
public FontAwesome Icon
{
get { return button.Icon; }
set { button.Icon = value; }
}
private readonly IconButton button;
public TimelineButton()
{
InternalChild = button = new IconButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
IconColour = OsuColour.Gray(0.35f),
IconHoverColour = Color4.White,
HoverColour = OsuColour.Gray(0.25f),
FlashColour = OsuColour.Gray(0.5f),
Action = () => Action?.Invoke()
};
button.Enabled.BindTo(Enabled);
Width = button.ButtonSize.X;
}
protected override void Update()
{
base.Update();
button.ButtonSize = new Vector2(button.ButtonSize.X, DrawHeight);
}
}
}

View File

@ -0,0 +1,46 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays;
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
namespace osu.Game.Tests.Visual
{
public class TestCaseEditorComposeTimeline : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(ScrollableTimeline), typeof(ScrollingTimelineContainer), typeof(BeatmapWaveformGraph), typeof(TimelineButton) };
private readonly ScrollableTimeline timeline;
public TestCaseEditorComposeTimeline()
{
Children = new Drawable[]
{
new MusicController
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
State = Visibility.Visible
},
timeline = new ScrollableTimeline
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(1000, 100)
}
};
}
[BackgroundDependencyLoader]
private void load(OsuGameBase osuGame)
{
timeline.Beatmap.BindTo(osuGame.Beatmap);
}
}
}

View File

@ -25,14 +25,18 @@ namespace osu.Game.Tests.Visual
Children = new[]
{
new NamedIconButton("No change", new IconButton()),
new NamedIconButton("Green colours", new IconButton
new NamedIconButton("Background colours", new IconButton
{
IconColour = Color4.LightGreen,
FlashColour = Color4.DarkGreen,
HoverColour = Color4.Green
HoverColour = Color4.Green,
}),
new NamedIconButton("Full-width", new IconButton { ButtonSize = new Vector2(200, 30) }),
new NamedIconButton("Unchanging size", new IconButton(), false)
new NamedIconButton("Unchanging size", new IconButton(), false),
new NamedIconButton("Icon colours", new IconButton
{
IconColour = Color4.Green,
IconHoverColour = Color4.Red
})
}
};
}

View File

@ -1,7 +1,10 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Game.Overlays;
using osu.Game.Overlays.MedalSplash;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
@ -10,6 +13,12 @@ namespace osu.Game.Tests.Visual
{
public override string Description => @"medal get!";
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(MedalOverlay),
typeof(DrawableMedal),
};
public TestCaseMedalOverlay()
{
AddStep(@"display", () =>

View File

@ -0,0 +1,88 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
namespace osu.Game.Tests.Visual
{
internal class TestCaseWaveform : OsuTestCase
{
private readonly Bindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
public TestCaseWaveform()
{
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)
{
var newDisplay = new BeatmapWaveformGraph
{
RelativeSizeAxes = Axes.Both,
Resolution = 1f / i
};
newDisplay.Beatmap.BindTo(beatmapBacking);
flow.Add(new Container
{
RelativeSizeAxes = Axes.X,
Height = 100,
Children = new Drawable[]
{
newDisplay,
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.75f
},
new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = $"Resolution: {1f / i:0.00}"
}
}
}
}
});
}
}
[BackgroundDependencyLoader]
private void load(OsuGameBase osuGame) => beatmapBacking.BindTo(osuGame.Beatmap);
}
}

View File

@ -57,7 +57,6 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<PlatformTarget>AnyCPU</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<RunCodeAnalysis>false</RunCodeAnalysis>
<Prefer32Bit>false</Prefer32Bit>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
@ -77,7 +76,6 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<PlatformTarget>AnyCPU</PlatformTarget>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<Prefer32Bit>false</Prefer32Bit>
<TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup>
@ -103,7 +101,6 @@
<UseVSHostingProcess>false</UseVSHostingProcess>
<LangVersion>6</LangVersion>
<ErrorReport>prompt</ErrorReport>
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
<StartArguments>--tests</StartArguments>
<Prefer32Bit>false</Prefer32Bit>
</PropertyGroup>
@ -266,6 +263,7 @@
<Compile Include="Beatmaps\Drawables\BeatmapPanel.cs" />
<Compile Include="Beatmaps\Drawables\BeatmapSetCover.cs" />
<Compile Include="Beatmaps\Drawables\BeatmapSetHeader.cs" />
<Compile Include="Screens\Edit\Screens\Compose\Timeline\BeatmapWaveformGraph.cs" />
<Compile Include="Beatmaps\Drawables\DifficultyColouredContainer.cs" />
<Compile Include="Beatmaps\Drawables\DifficultyIcon.cs" />
<Compile Include="Beatmaps\Drawables\Panel.cs" />
@ -274,6 +272,7 @@
<Compile Include="Beatmaps\Formats\OsuLegacyDecoder.cs" />
<Compile Include="Beatmaps\IO\ArchiveReader.cs" />
<Compile Include="Beatmaps\IO\LegacyFilesystemReader.cs" />
<Compile Include="Screens\Edit\Screens\Compose\Timeline\TimelineButton.cs" />
<Compile Include="Screens\Play\BreaksOverlay\ArrowsOverlay.cs" />
<Compile Include="Screens\Play\BreaksOverlay\BlurredIcon.cs" />
<Compile Include="Screens\Play\BreaksOverlay\BreakOverlay.cs" />
@ -312,7 +311,6 @@
<Compile Include="Graphics\Cursor\OsuTooltipContainer.cs" />
<Compile Include="Graphics\IHasAccentColour.cs" />
<Compile Include="Graphics\OsuColour.cs" />
<Compile Include="Graphics\Processing\RatioAdjust.cs" />
<Compile Include="Graphics\SpriteIcon.cs" />
<Compile Include="Graphics\Sprites\OsuSpriteText.cs" />
<Compile Include="Graphics\UserInterface\BackButton.cs" />
@ -395,6 +393,7 @@
<Compile Include="Overlays\Chat\ChannelSelectionOverlay.cs" />
<Compile Include="Overlays\Chat\ChatLine.cs" />
<Compile Include="Overlays\Chat\ChatTabControl.cs" />
<Compile Include="Overlays\Direct\PlayButton.cs" />
<Compile Include="Overlays\Chat\DrawableChannel.cs" />
<Compile Include="Overlays\DialogOverlay.cs" />
<Compile Include="Overlays\Dialog\PopupDialog.cs" />
@ -629,6 +628,8 @@
<Compile Include="Screens\Edit\Screens\Design\Design.cs" />
<Compile Include="Screens\Edit\Screens\EditorScreen.cs" />
<Compile Include="Screens\Edit\Screens\Compose\Compose.cs" />
<Compile Include="Screens\Edit\Screens\Compose\Timeline\ScrollableTimeline.cs" />
<Compile Include="Screens\Edit\Screens\Compose\Timeline\ScrollingTimelineContainer.cs" />
<Compile Include="Screens\Loader.cs" />
<Compile Include="Screens\Menu\Button.cs" />
<Compile Include="Screens\Menu\ButtonSystem.cs" />
@ -755,6 +756,7 @@
<Compile Include="Tests\Visual\TestCaseDrawableRoom.cs" />
<Compile Include="Tests\Visual\TestCaseDrawings.cs" />
<Compile Include="Tests\Visual\TestCaseEditor.cs" />
<Compile Include="Tests\Visual\TestCaseEditorComposeTimeline.cs" />
<Compile Include="Tests\Visual\TestCaseEditorMenuBar.cs" />
<Compile Include="Tests\Visual\TestCaseEditorSummaryTimeline.cs" />
<Compile Include="Tests\Visual\TestCaseGamefield.cs" />
@ -788,6 +790,7 @@
<Compile Include="Tests\Visual\TestCaseTwoLayerButton.cs" />
<Compile Include="Tests\Visual\TestCaseUserPanel.cs" />
<Compile Include="Tests\Visual\TestCaseUserProfile.cs" />
<Compile Include="Tests\Visual\TestCaseWaveform.cs" />
<Compile Include="Users\Avatar.cs" />
<Compile Include="Users\Country.cs" />
<Compile Include="Users\Medal.cs" />