1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-28 20:40:46 +08:00

Merge branch 'refs/heads/master' into release

This commit is contained in:
Dean Herbert
2016-12-02 22:12:30 +09:00
Unverified
87 changed files with 2765 additions and 843 deletions
@@ -14,9 +14,9 @@ using osu.Game;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Chat;
using osu.Game.Online.Chat.Display;
using OpenTK;
using osu.Framework.Allocation;
using osu.Game.Online.Chat.Drawables;
namespace osu.Desktop.VisualTests.Tests
{
@@ -45,7 +45,7 @@ namespace osu.Desktop.VisualTests.Tests
{
base.Reset();
if (api.State != APIAccess.APIState.Online)
if (api.State != APIState.Online)
api.OnStateChange += delegate { initializeChannels(); };
else
initializeChannels();
@@ -65,7 +65,7 @@ namespace osu.Desktop.VisualTests.Tests
{
careChannels = new List<Channel>();
if (api.State != APIAccess.APIState.Online)
if (api.State != APIState.Online)
return;
Add(flow = new FlowContainer
@@ -12,6 +12,7 @@ using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.Objects.Drawables;
using osu.Framework.Graphics.Containers;
using osu.Game.Modes;
namespace osu.Desktop.VisualTests.Tests
{
@@ -31,7 +32,7 @@ namespace osu.Desktop.VisualTests.Tests
Clock.ProcessFrame();
Container approachContainer = new Container { Depth = float.MaxValue, };
Container approachContainer = new Container { Depth = float.MinValue, };
Add(approachContainer);
@@ -49,8 +50,8 @@ namespace osu.Desktop.VisualTests.Tests
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Depth = -i,
State = ArmedState.Armed,
Depth = i,
State = ArmedState.Hit,
};
approachContainer.Add(d.ApproachCircle.CreateProxy());
@@ -2,7 +2,10 @@
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.GameModes.Testing;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Sprites;
using osu.Game.Screens.Menu;
using OpenTK.Graphics;
namespace osu.Desktop.VisualTests.Tests
{
@@ -15,6 +18,11 @@ namespace osu.Desktop.VisualTests.Tests
{
base.Reset();
Add(new Box
{
ColourInfo = ColourInfo.GradientVertical(Color4.Gray, Color4.WhiteSmoke),
RelativeSizeAxes = Framework.Graphics.Axes.Both,
});
Add(new ButtonSystem());
}
}
@@ -1,31 +1,32 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Desktop.VisualTests.Platform;
using osu.Framework.GameModes.Testing;
using osu.Game.Database;
using osu.Game.Modes;
using osu.Game.Screens.Select;
namespace osu.Desktop.VisualTests.Tests
{
class TestCasePlaySongSelect : TestCase
{
private BeatmapDatabase db;
private TestStorage storage;
public override string Name => @"Song Select";
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Desktop.VisualTests.Platform;
using osu.Framework.GameModes.Testing;
using osu.Game.Database;
using osu.Game.Modes;
using osu.Game.Screens.Select;
namespace osu.Desktop.VisualTests.Tests
{
class TestCasePlaySongSelect : TestCase
{
private BeatmapDatabase db, oldDb;
private TestStorage storage;
public override string Name => @"Song Select";
public override string Description => @"with fake data";
public override void Reset()
{
public override void Reset()
{
base.Reset();
oldDb = Dependencies.Get<BeatmapDatabase>();
if (db == null)
{
storage = new TestStorage(@"TestCasePlaySongSelect");
db = new BeatmapDatabase(storage);
Dependencies.Cache(db, true);
var sets = new List<BeatmapSetInfo>();
@@ -34,8 +35,14 @@ namespace osu.Desktop.VisualTests.Tests
db.Import(sets);
}
Add(new PlaySongSelect(db));
Add(new PlaySongSelect());
}
protected override void Dispose(bool isDisposing)
{
if (oldDb != null)
Dependencies.Cache(oldDb, true);
base.Dispose(isDisposing);
}
private BeatmapSetInfo createTestBeatmapSet(int i)
@@ -89,6 +96,6 @@ namespace osu.Desktop.VisualTests.Tests
},
}),
};
}
}
}
}
}
}
+38 -21
View File
@@ -2,6 +2,7 @@
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.GameModes.Testing;
using osu.Framework.MathUtils;
using osu.Framework.Timing;
@@ -9,6 +10,8 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats;
using OpenTK;
using osu.Framework.Graphics.Sprites;
using osu.Game.Database;
using osu.Game.Modes;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Screens.Play;
@@ -18,10 +21,17 @@ namespace osu.Desktop.VisualTests.Tests
{
class TestCasePlayer : TestCase
{
private WorkingBeatmap beatmap;
public override string Name => @"Player";
public override string Description => @"Showing everything to play the game.";
[BackgroundDependencyLoader]
private void load(BeatmapDatabase db)
{
beatmap = db.GetWorkingBeatmap(db.Query<BeatmapInfo>().Where(b => b.Mode == PlayMode.Osu).FirstOrDefault());
}
public override void Reset()
{
base.Reset();
@@ -29,31 +39,37 @@ namespace osu.Desktop.VisualTests.Tests
//ensure we are at offset 0
Clock = new FramedClock();
var objects = new List<HitObject>();
int time = 1500;
for (int i = 0; i < 50; i++)
if (beatmap == null)
{
objects.Add(new HitCircle()
var objects = new List<HitObject>();
int time = 1500;
for (int i = 0; i < 50; i++)
{
StartTime = time,
Position = new Vector2(i % 4 == 0 || i % 4 == 2 ? 0 : 512,
i % 4 < 2 ? 0 : 384),
NewCombo = i % 4 == 0
});
objects.Add(new HitCircle()
{
StartTime = time,
Position = new Vector2(i % 4 == 0 || i % 4 == 2 ? 0 : 512,
i % 4 < 2 ? 0 : 384),
NewCombo = i % 4 == 0
});
time += 500;
time += 500;
}
var decoder = new ConstructableBeatmapDecoder();
Beatmap b = new Beatmap
{
HitObjects = objects
};
decoder.Process(b);
beatmap = new WorkingBeatmap(b);
}
var decoder = new ConstructableBeatmapDecoder();
Beatmap b = new Beatmap
{
HitObjects = objects
};
decoder.Process(b);
Add(new Box
{
RelativeSizeAxes = Framework.Graphics.Axes.Both,
@@ -62,7 +78,8 @@ namespace osu.Desktop.VisualTests.Tests
Add(new Player
{
Beatmap = new WorkingBeatmap(b)
PreferredPlayMode = PlayMode.Osu,
Beatmap = beatmap
});
}
@@ -0,0 +1,148 @@
using System.Collections.Generic;
using OpenTK;
namespace osu.Game.Modes.Osu.Objects
{
public class BezierApproximator
{
private int count;
private List<Vector2> controlPoints;
private Vector2[] subdivisionBuffer1;
private Vector2[] subdivisionBuffer2;
private const float TOLERANCE = 0.5f;
private const float TOLERANCE_SQ = TOLERANCE * TOLERANCE;
public BezierApproximator(List<Vector2> controlPoints)
{
this.controlPoints = controlPoints;
count = controlPoints.Count;
subdivisionBuffer1 = new Vector2[count];
subdivisionBuffer2 = new Vector2[count * 2 - 1];
}
/// <summary>
/// Make sure the 2nd order derivative (approximated using finite elements) is within tolerable bounds.
/// NOTE: The 2nd order derivative of a 2d curve represents its curvature, so intuitively this function
/// checks (as the name suggests) whether our approximation is _locally_ "flat". More curvy parts
/// need to have a denser approximation to be more "flat".
/// </summary>
/// <param name="controlPoints">The control points to check for flatness.</param>
/// <returns>Whether the control points are flat enough.</returns>
private static bool IsFlatEnough(Vector2[] controlPoints)
{
for (int i = 1; i < controlPoints.Length - 1; i++)
if ((controlPoints[i - 1] - 2 * controlPoints[i] + controlPoints[i + 1]).LengthSquared > TOLERANCE_SQ)
return false;
return true;
}
/// <summary>
/// Subdivides n control points representing a bezier curve into 2 sets of n control points, each
/// describing a bezier curve equivalent to a half of the original curve. Effectively this splits
/// the original curve into 2 curves which result in the original curve when pieced back together.
/// </summary>
/// <param name="controlPoints">The control points to split.</param>
/// <param name="l">Output: The control points corresponding to the left half of the curve.</param>
/// <param name="r">Output: The control points corresponding to the right half of the curve.</param>
private void Subdivide(Vector2[] controlPoints, Vector2[] l, Vector2[] r)
{
Vector2[] midpoints = subdivisionBuffer1;
for (int i = 0; i < count; ++i)
midpoints[i] = controlPoints[i];
for (int i = 0; i < count; i++)
{
l[i] = midpoints[0];
r[count - i - 1] = midpoints[count - i - 1];
for (int j = 0; j < count - i - 1; j++)
midpoints[j] = (midpoints[j] + midpoints[j + 1]) / 2;
}
}
/// <summary>
/// This uses <a href="https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm">De Casteljau's algorithm</a> to obtain an optimal
/// piecewise-linear approximation of the bezier curve with the same amount of points as there are control points.
/// </summary>
/// <param name="controlPoints">The control points describing the bezier curve to be approximated.</param>
/// <param name="output">The points representing the resulting piecewise-linear approximation.</param>
private void Approximate(Vector2[] controlPoints, List<Vector2> output)
{
Vector2[] l = subdivisionBuffer2;
Vector2[] r = subdivisionBuffer1;
Subdivide(controlPoints, l, r);
for (int i = 0; i < count - 1; ++i)
l[count + i] = r[i + 1];
output.Add(controlPoints[0]);
for (int i = 1; i < count - 1; ++i)
{
int index = 2 * i;
Vector2 p = 0.25f * (l[index - 1] + 2 * l[index] + l[index + 1]);
output.Add(p);
}
}
/// <summary>
/// Creates a piecewise-linear approximation of a bezier curve, by adaptively repeatedly subdividing
/// the control points until their approximation error vanishes below a given threshold.
/// </summary>
/// <param name="controlPoints">The control points describing the curve.</param>
/// <returns>A list of vectors representing the piecewise-linear approximation.</returns>
public List<Vector2> CreateBezier()
{
List<Vector2> output = new List<Vector2>();
if (count == 0)
return output;
Stack<Vector2[]> toFlatten = new Stack<Vector2[]>();
Stack<Vector2[]> freeBuffers = new Stack<Vector2[]>();
// "toFlatten" contains all the curves which are not yet approximated well enough.
// We use a stack to emulate recursion without the risk of running into a stack overflow.
// (More specifically, we iteratively and adaptively refine our curve with a
// <a href="https://en.wikipedia.org/wiki/Depth-first_search">Depth-first search</a>
// over the tree resulting from the subdivisions we make.)
toFlatten.Push(controlPoints.ToArray());
Vector2[] leftChild = subdivisionBuffer2;
while (toFlatten.Count > 0)
{
Vector2[] parent = toFlatten.Pop();
if (IsFlatEnough(parent))
{
// If the control points we currently operate on are sufficiently "flat", we use
// an extension to De Casteljau's algorithm to obtain a piecewise-linear approximation
// of the bezier curve represented by our control points, consisting of the same amount
// of points as there are control points.
Approximate(parent, output);
freeBuffers.Push(parent);
continue;
}
// If we do not yet have a sufficiently "flat" (in other words, detailed) approximation we keep
// subdividing the curve we are currently operating on.
Vector2[] rightChild = freeBuffers.Count > 0 ? freeBuffers.Pop() : new Vector2[count];
Subdivide(parent, leftChild, rightChild);
// We re-use the buffer of the parent for one of the children, so that we save one allocation per iteration.
for (int i = 0; i < count; ++i)
parent[i] = leftChild[i];
toFlatten.Push(rightChild);
toFlatten.Push(parent);
}
output.Add(controlPoints[count - 1]);
return output;
}
}
}
@@ -2,6 +2,7 @@
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.ComponentModel;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Transformations;
using osu.Game.Modes.Objects.Drawables;
@@ -10,7 +11,7 @@ using OpenTK;
namespace osu.Game.Modes.Osu.Objects.Drawables
{
public class DrawableHitCircle : DrawableHitObject
public class DrawableHitCircle : DrawableOsuHitObject
{
private OsuHitObject osuObject;
@@ -23,7 +24,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
private GlowPiece glow;
private HitExplosion explosion;
public DrawableHitCircle(HitCircle h) : base(h)
public DrawableHitCircle(OsuHitObject h) : base(h)
{
osuObject = h;
@@ -39,7 +40,12 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
circle = new CirclePiece
{
Colour = osuObject.Colour,
Hit = Hit,
Hit = () =>
{
((PositionalJudgementInfo)Judgement).PositionOffset = Vector2.Zero; //todo: set to correct value
UpdateJudgement(true);
return true;
},
},
number = new NumberPiece(),
ring = new RingPiece(),
@@ -53,29 +59,54 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
Colour = osuObject.Colour,
}
};
//may not be so correct
Size = circle.DrawSize;
}
protected override void LoadComplete()
{
base.LoadComplete();
//may not be so correct
Size = circle.DrawSize;
//force application of the state that was set before we loaded.
UpdateState(State);
}
protected override void UpdateState(ArmedState state)
double hit50 = 150;
double hit100 = 80;
double hit300 = 30;
protected override void CheckJudgement(bool userTriggered)
{
if (!IsLoaded) return;
if (!userTriggered)
{
if (Judgement.TimeOffset > hit50)
Judgement.Result = HitResult.Miss;
return;
}
Flush(true); //move to DrawableHitObject
ApproachCircle.Flush(true);
double hitOffset = Math.Abs(Judgement.TimeOffset);
double t = HitTime ?? osuObject.StartTime;
if (hitOffset < hit50)
{
Judgement.Result = HitResult.Hit;
Alpha = 0;
OsuJudgementInfo osuInfo = Judgement as OsuJudgementInfo;
if (hitOffset < hit300)
osuInfo.Score = OsuScoreResult.Hit300;
else if (hitOffset < hit100)
osuInfo.Score = OsuScoreResult.Hit100;
else if (hitOffset < hit50)
osuInfo.Score = OsuScoreResult.Hit50;
}
else
Judgement.Result = HitResult.Miss;
}
protected override void UpdateInitialState()
{
base.UpdateInitialState();
//sane defaults
ring.Alpha = circle.Alpha = number.Alpha = glow.Alpha = 1;
@@ -83,34 +114,48 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
ApproachCircle.Scale = new Vector2(2);
explode.Alpha = 0;
Scale = new Vector2(0.5f); //this will probably need to be moved to DrawableHitObject at some point.
}
const float preempt = 600;
protected override void UpdatePreemptState()
{
base.UpdatePreemptState();
const float fadein = 400;
ApproachCircle.FadeIn(Math.Min(TIME_FADEIN * 2, TIME_PREEMPT));
ApproachCircle.ScaleTo(0.6f, TIME_PREEMPT);
}
Delay(t - Time.Current - preempt, true);
protected override void UpdateState(ArmedState state)
{
if (!IsLoaded) return;
FadeIn(fadein);
ApproachCircle.FadeIn(Math.Min(fadein * 2, preempt));
ApproachCircle.ScaleTo(0.6f, preempt);
Delay(preempt, true);
base.UpdateState(state);
ApproachCircle.FadeOut();
glow.FadeOut(400);
switch (state)
{
case ArmedState.Disarmed:
Delay(osuObject.Duration + 200);
FadeOut(200);
case ArmedState.Idle:
Delay(osuObject.Duration + TIME_PREEMPT);
FadeOut(TIME_FADEOUT);
explosion?.Expire();
explosion = null;
break;
case ArmedState.Armed:
case ArmedState.Miss:
ring.FadeOut();
circle.FadeOut();
number.FadeOut();
glow.FadeOut();
explosion?.Expire();
explosion = null;
Schedule(() => Add(explosion = new HitExplosion((OsuJudgementInfo)Judgement)));
FadeOut(800);
break;
case ArmedState.Hit:
const double flash_in = 30;
flash.FadeTo(0.8f, flash_in);
@@ -119,7 +164,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
explode.FadeIn(flash_in);
Schedule(() => Add(explosion = new HitExplosion(Judgement.Hit300)));
Schedule(() => Add(explosion = new HitExplosion((OsuJudgementInfo)Judgement)));
Delay(flash_in, true);
@@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Objects.Drawables;
namespace osu.Game.Modes.Osu.Objects.Drawables
{
public class DrawableOsuHitObject : DrawableHitObject
{
protected const float TIME_PREEMPT = 600;
protected const float TIME_FADEIN = 400;
protected const float TIME_FADEOUT = 500;
public DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject)
{
}
public override JudgementInfo CreateJudgementInfo() => new OsuJudgementInfo();
protected override void UpdateState(ArmedState state)
{
if (!IsLoaded) return;
Flush(true);
UpdateInitialState();
Delay(HitObject.StartTime - Time.Current - TIME_PREEMPT + Judgement.TimeOffset, true);
UpdatePreemptState();
Delay(TIME_PREEMPT, true);
}
protected virtual void UpdatePreemptState()
{
FadeIn(TIME_FADEIN);
}
protected virtual void UpdateInitialState()
{
Alpha = 0;
}
}
public class OsuJudgementInfo : PositionalJudgementInfo
{
public OsuScoreResult Score;
public ComboResult Combo;
}
public enum ComboResult
{
[Description(@"")]
None,
[Description(@"Good")]
Good,
[Description(@"Amazing")]
Perfect
}
public enum OsuScoreResult
{
[Description(@"Miss")]
Miss,
[Description(@"50")]
Hit50,
[Description(@"100")]
Hit100,
[Description(@"300")]
Hit300,
}
}
@@ -1,27 +1,52 @@
using osu.Framework.Graphics;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Objects.Drawables.Pieces;
using OpenTK;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transformations;
using OpenTK.Graphics;
using osu.Framework.Input;
using OpenTK.Graphics.ES30;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Textures;
namespace osu.Game.Modes.Osu.Objects.Drawables
{
class DrawableSlider : DrawableHitObject
class DrawableSlider : DrawableOsuHitObject
{
public DrawableSlider(Slider h) : base(h)
{
Origin = Anchor.Centre;
RelativePositionAxes = Axes.Both;
Position = new Vector2(h.Position.X / 512, h.Position.Y / 384);
private Slider slider;
for (float i = 0; i <= 1; i += 0.1f)
private DrawableHitCircle startCircle;
private Container ball;
private Body body;
public DrawableSlider(Slider s) : base(s)
{
slider = s;
Origin = Anchor.TopLeft;
Position = Vector2.Zero;
RelativeSizeAxes = Axes.Both;
Children = new Drawable[]
{
Add(new CirclePiece
body = new Body(s)
{
Colour = h.Colour,
Hit = Hit,
Position = h.Curve.PositionAt(i) - h.Position //non-relative?
});
}
Position = s.Position,
},
ball = new Ball(),
startCircle = new DrawableHitCircle(new HitCircle
{
StartTime = s.StartTime,
Position = s.Position,
Colour = s.Colour,
})
{
Depth = -1 //override time-based depth.
},
};
}
protected override void LoadComplete()
@@ -32,19 +57,234 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
UpdateState(State);
}
protected override void Update()
{
base.Update();
ball.Alpha = Time.Current >= slider.StartTime && Time.Current <= slider.EndTime ? 1 : 0;
double t = (Time.Current - slider.StartTime) / slider.Duration;
if (slider.RepeatCount > 1)
{
int currentRepeat = (int)(t * slider.RepeatCount);
t = (t * slider.RepeatCount) % 1;
if (currentRepeat % 2 == 1)
t = 1 - t;
}
ball.Position = slider.Curve.PositionAt(t);
}
protected override void CheckJudgement(bool userTriggered)
{
var j = Judgement as OsuJudgementInfo;
var sc = startCircle.Judgement as OsuJudgementInfo;
if (!userTriggered && Time.Current >= HitObject.EndTime)
{
j.Score = sc.Score;
j.Result = sc.Result;
}
}
protected override void UpdateState(ArmedState state)
{
if (!IsLoaded) return;
base.UpdateState(state);
Flush(true); //move to DrawableHitObject
Delay(HitObject.Duration);
FadeOut(300);
}
Alpha = 0;
private class Ball : Container
{
private Box follow;
Delay(HitObject.StartTime - 200 - Time.Current, true);
public Ball()
{
Masking = true;
AutoSizeAxes = Axes.Both;
BlendingMode = BlendingMode.Additive;
Origin = Anchor.Centre;
FadeIn(200);
Delay(200 + HitObject.Duration);
FadeOut(200);
Children = new Drawable[]
{
follow = new Box
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.Orange,
Width = 64,
Height = 64,
},
new Container
{
Masking = true,
AutoSizeAxes = Axes.Both,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Colour = Color4.Cyan,
CornerRadius = 32,
Children = new[]
{
new Box
{
Width = 64,
Height = 64,
},
}
}
};
}
private InputState lastState;
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
lastState = state;
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
lastState = state;
return base.OnMouseUp(state, args);
}
protected override bool OnMouseMove(InputState state)
{
lastState = state;
return base.OnMouseMove(state);
}
bool tracking;
protected bool Tracking
{
get { return tracking; }
set
{
if (value == tracking) return;
tracking = value;
follow.ScaleTo(tracking ? 2.4f : 1, 140, EasingTypes.Out);
follow.FadeTo(tracking ? 0.8f : 0, 140, EasingTypes.Out);
}
}
protected override void Update()
{
base.Update();
CornerRadius = DrawWidth / 2;
Tracking = lastState != null && Contains(lastState.Mouse.NativeState.Position) && lastState.Mouse.HasMainButtonPressed;
}
}
private class Body : Container
{
private Path path;
private BufferedContainer container;
private double? drawnProgress;
private Slider slider;
public Body(Slider s)
{
slider = s;
Children = new Drawable[]
{
container = new BufferedContainer
{
CacheDrawnFrameBuffer = true,
Children = new Drawable[]
{
path = new Path
{
Colour = s.Colour,
BlendingMode = BlendingMode.None,
},
}
}
};
container.Attach(RenderbufferInternalFormat.DepthComponent16);
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
// Surprisingly, this looks somewhat okay and works well as a test for self-overlaps.
// TODO: Don't do this.
path.Texture = textures.Get(@"Menu/logo");
}
protected override void LoadComplete()
{
base.LoadComplete();
path.PathWidth = 32;
}
protected override void Update()
{
base.Update();
if (updateSnaking())
{
// Autosizing does not give us the desired behaviour here.
// We want the container to have the same size as the slider,
// and to be positioned such that the slider head is at (0,0).
container.Size = path.Size;
container.Position = -path.HeadPosition;
container.ForceRedraw();
}
}
private bool updateSnaking()
{
double progress = MathHelper.Clamp((Time.Current - slider.StartTime + TIME_PREEMPT) / TIME_FADEIN, 0, 1);
if (progress == drawnProgress) return false;
bool madeChanges = false;
if (progress == 0)
{
//if we have gone backwards, just clear the path for now.
drawnProgress = 0;
path.ClearVertices();
madeChanges = true;
}
Vector2 startPosition = slider.Curve.PositionAt(0);
if (drawnProgress == null)
{
drawnProgress = 0;
path.AddVertex(slider.Curve.PositionAt(drawnProgress.Value) - startPosition);
madeChanges = true;
}
double segmentSize = 1 / (slider.Curve.Length / 5);
while (drawnProgress + segmentSize < progress)
{
drawnProgress += segmentSize;
path.AddVertex(slider.Curve.PositionAt(drawnProgress.Value) - startPosition);
madeChanges = true;
}
if (progress == 1 && drawnProgress != progress)
{
drawnProgress = progress;
path.AddVertex(slider.Curve.PositionAt(drawnProgress.Value) - startPosition);
madeChanges = true;
}
return madeChanges;
}
}
}
}
@@ -3,6 +3,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transformations;
using osu.Game.Modes.Objects.Drawables;
using OpenTK;
namespace osu.Game.Modes.Osu.Objects.Drawables
@@ -12,7 +13,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
private SpriteText line1;
private SpriteText line2;
public HitExplosion(Judgement judgement, ComboJudgement comboJudgement = ComboJudgement.None)
public HitExplosion(OsuJudgementInfo judgement)
{
AutoSizeAxes = Axes.Both;
Anchor = Anchor.Centre;
@@ -27,13 +28,13 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = judgement.GetDescription(),
Text = judgement.Score.GetDescription(),
Font = @"Venera",
TextSize = 20,
},
line2 = new SpriteText
{
Text = comboJudgement.GetDescription(),
Text = judgement.Combo.GetDescription(),
Font = @"Venera",
TextSize = 14,
}
@@ -49,8 +49,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
Hit?.Invoke();
return true;
return Hit?.Invoke() ?? false;
}
}
}
@@ -8,29 +8,24 @@ using OpenTK;
namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces
{
public class Triangles : Container
public class Triangles : Container<Triangle>
{
private Texture triangle;
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
triangle = textures.Get(@"Play/osu/triangle@2x");
}
protected override void LoadComplete()
{
base.LoadComplete();
const float size = 100;
for (int i = 0; i < 10; i++)
{
Add(new Sprite
Add(new Triangle
{
Texture = triangle,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Both,
Position = new Vector2(RNG.NextSingle(), RNG.NextSingle()),
Scale = new Vector2(RNG.NextSingle() * 0.4f + 0.2f),
Alpha = RNG.NextSingle() * 0.3f
// Scaling height by 0.866 results in equiangular triangles (== 60° and equal side length)
Size = new Vector2(size, 0.866f * size),
Alpha = RNG.NextSingle() * 0.3f,
});
}
}
+12 -184
View File
@@ -1,198 +1,26 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using OpenTK;
using osu.Game.Database;
using osu.Game.Beatmaps;
using System;
namespace osu.Game.Modes.Osu.Objects
{
public class Slider : OsuHitObject
{
public override double EndTime => StartTime + (RepeatCount + 1) * Curve.Length;
public override double EndTime => StartTime + RepeatCount * Curve.Length / Velocity;
public double Velocity;
public override void SetDefaultsFromBeatmap(Beatmap beatmap)
{
Velocity = 100 / beatmap.BeatLengthAt(StartTime, true) * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier;
}
public int RepeatCount;
public SliderCurve Curve;
}
public class SliderCurve
{
public double Length;
public List<Vector2> Path;
public CurveTypes CurveType;
private List<Vector2> calculatedPath;
public void Calculate()
{
switch (CurveType)
{
case CurveTypes.Linear:
calculatedPath = Path;
break;
default:
var bezier = new BezierApproximator(Path);
calculatedPath = bezier.CreateBezier();
break;
}
}
public Vector2 PositionAt(double progress)
{
int index = (int)(progress * (calculatedPath.Count - 1));
Vector2 pos = calculatedPath[index];
if (index != progress)
pos += (calculatedPath[index + 1] - pos) * (float)(progress - index);
return pos;
}
}
public class BezierApproximator
{
private int count;
private List<Vector2> controlPoints;
private Vector2[] subdivisionBuffer1;
private Vector2[] subdivisionBuffer2;
private const float TOLERANCE = 0.5f;
private const float TOLERANCE_SQ = TOLERANCE * TOLERANCE;
public BezierApproximator(List<Vector2> controlPoints)
{
this.controlPoints = controlPoints;
count = controlPoints.Count;
subdivisionBuffer1 = new Vector2[count];
subdivisionBuffer2 = new Vector2[count * 2 - 1];
}
/// <summary>
/// Make sure the 2nd order derivative (approximated using finite elements) is within tolerable bounds.
/// NOTE: The 2nd order derivative of a 2d curve represents its curvature, so intuitively this function
/// checks (as the name suggests) whether our approximation is _locally_ "flat". More curvy parts
/// need to have a denser approximation to be more "flat".
/// </summary>
/// <param name="controlPoints">The control points to check for flatness.</param>
/// <returns>Whether the control points are flat enough.</returns>
private static bool IsFlatEnough(Vector2[] controlPoints)
{
for (int i = 1; i < controlPoints.Length - 1; i++)
if ((controlPoints[i - 1] - 2 * controlPoints[i] + controlPoints[i + 1]).LengthSquared > TOLERANCE_SQ)
return false;
return true;
}
/// <summary>
/// Subdivides n control points representing a bezier curve into 2 sets of n control points, each
/// describing a bezier curve equivalent to a half of the original curve. Effectively this splits
/// the original curve into 2 curves which result in the original curve when pieced back together.
/// </summary>
/// <param name="controlPoints">The control points to split.</param>
/// <param name="l">Output: The control points corresponding to the left half of the curve.</param>
/// <param name="r">Output: The control points corresponding to the right half of the curve.</param>
private void Subdivide(Vector2[] controlPoints, Vector2[] l, Vector2[] r)
{
Vector2[] midpoints = subdivisionBuffer1;
for (int i = 0; i < count; ++i)
midpoints[i] = controlPoints[i];
for (int i = 0; i < count; i++)
{
l[i] = midpoints[0];
r[count - i - 1] = midpoints[count - i - 1];
for (int j = 0; j < count - i - 1; j++)
midpoints[j] = (midpoints[j] + midpoints[j + 1]) / 2;
}
}
/// <summary>
/// This uses <a href="https://en.wikipedia.org/wiki/De_Casteljau%27s_algorithm">De Casteljau's algorithm</a> to obtain an optimal
/// piecewise-linear approximation of the bezier curve with the same amount of points as there are control points.
/// </summary>
/// <param name="controlPoints">The control points describing the bezier curve to be approximated.</param>
/// <param name="output">The points representing the resulting piecewise-linear approximation.</param>
private void Approximate(Vector2[] controlPoints, List<Vector2> output)
{
Vector2[] l = subdivisionBuffer2;
Vector2[] r = subdivisionBuffer1;
Subdivide(controlPoints, l, r);
for (int i = 0; i < count - 1; ++i)
l[count + i] = r[i + 1];
output.Add(controlPoints[0]);
for (int i = 1; i < count - 1; ++i)
{
int index = 2 * i;
Vector2 p = 0.25f * (l[index - 1] + 2 * l[index] + l[index + 1]);
output.Add(p);
}
}
/// <summary>
/// Creates a piecewise-linear approximation of a bezier curve, by adaptively repeatedly subdividing
/// the control points until their approximation error vanishes below a given threshold.
/// </summary>
/// <param name="controlPoints">The control points describing the curve.</param>
/// <returns>A list of vectors representing the piecewise-linear approximation.</returns>
public List<Vector2> CreateBezier()
{
List<Vector2> output = new List<Vector2>();
if (count == 0)
return output;
Stack<Vector2[]> toFlatten = new Stack<Vector2[]>();
Stack<Vector2[]> freeBuffers = new Stack<Vector2[]>();
// "toFlatten" contains all the curves which are not yet approximated well enough.
// We use a stack to emulate recursion without the risk of running into a stack overflow.
// (More specifically, we iteratively and adaptively refine our curve with a
// <a href="https://en.wikipedia.org/wiki/Depth-first_search">Depth-first search</a>
// over the tree resulting from the subdivisions we make.)
toFlatten.Push(controlPoints.ToArray());
Vector2[] leftChild = subdivisionBuffer2;
while (toFlatten.Count > 0)
{
Vector2[] parent = toFlatten.Pop();
if (IsFlatEnough(parent))
{
// If the control points we currently operate on are sufficiently "flat", we use
// an extension to De Casteljau's algorithm to obtain a piecewise-linear approximation
// of the bezier curve represented by our control points, consisting of the same amount
// of points as there are control points.
Approximate(parent, output);
freeBuffers.Push(parent);
continue;
}
// If we do not yet have a sufficiently "flat" (in other words, detailed) approximation we keep
// subdividing the curve we are currently operating on.
Vector2[] rightChild = freeBuffers.Count > 0 ? freeBuffers.Pop() : new Vector2[count];
Subdivide(parent, leftChild, rightChild);
// We re-use the buffer of the parent for one of the children, so that we save one allocation per iteration.
for (int i = 0; i < count; ++i)
parent[i] = leftChild[i];
toFlatten.Push(rightChild);
toFlatten.Push(parent);
}
output.Add(controlPoints[count - 1]);
return output;
}
}
public enum CurveTypes
@@ -201,5 +29,5 @@ namespace osu.Game.Modes.Osu.Objects
Bezier,
Linear,
PerfectCurve
};
}
}
+67
View File
@@ -0,0 +1,67 @@
using System.Collections.Generic;
using OpenTK;
namespace osu.Game.Modes.Osu.Objects
{
public class SliderCurve
{
public double Length;
public List<Vector2> Path;
public CurveTypes CurveType;
private List<Vector2> calculatedPath;
private List<Vector2> calculateSubpath(List<Vector2> subpath)
{
switch (CurveType)
{
case CurveTypes.Linear:
return subpath;
default:
return new BezierApproximator(subpath).CreateBezier();
}
}
public void Calculate()
{
calculatedPath = new List<Vector2>();
// Sliders may consist of various subpaths separated by two consecutive vertices
// with the same position. The following loop parses these subpaths and computes
// their shape independently, consecutively appending them to calculatedPath.
List<Vector2> subpath = new List<Vector2>();
for (int i = 0; i < Path.Count; ++i)
{
subpath.Add(Path[i]);
if (i == Path.Count - 1 || Path[i] == Path[i + 1])
{
// If we already constructed a subpath previously, then the new subpath
// will have as starting position the end position of the previous subpath.
// Hence we can and should remove the previous endpoint to avoid a segment
// with 0 length.
if (calculatedPath.Count > 0)
calculatedPath.RemoveAt(calculatedPath.Count - 1);
calculatedPath.AddRange(calculateSubpath(subpath));
subpath.Clear();
}
}
}
public Vector2 PositionAt(double progress)
{
progress = MathHelper.Clamp(progress, 0, 1);
double index = progress * (calculatedPath.Count - 1);
int flooredIndex = (int)index;
Vector2 pos = calculatedPath[flooredIndex];
if (index != flooredIndex)
pos += (calculatedPath[flooredIndex + 1] - pos) * (float)(index - flooredIndex);
return pos;
}
}
}
+2
View File
@@ -17,6 +17,8 @@ namespace osu.Game.Modes.Osu
public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser();
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
protected override PlayMode PlayMode => PlayMode.Osu;
}
}
+12
View File
@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace osu.Game.Modes.Osu
{
class OsuScore : Score
{
}
}
+57
View File
@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Objects.Drawables;
namespace osu.Game.Modes.Osu
{
class OsuScoreProcessor : ScoreProcessor
{
protected override void UpdateCalculations(JudgementInfo judgement)
{
if (judgement != null)
{
switch (judgement.Result)
{
case HitResult.Hit:
Combo.Value++;
break;
case HitResult.Miss:
Combo.Value = 0;
break;
}
}
int score = 0;
int maxScore = 0;
foreach (OsuJudgementInfo j in Judgements)
{
switch (j.Score)
{
case OsuScoreResult.Miss:
maxScore += 300;
break;
case OsuScoreResult.Hit50:
score += 50;
maxScore += 300;
break;
case OsuScoreResult.Hit100:
score += 100;
maxScore += 300;
break;
case OsuScoreResult.Hit300:
score += 300;
maxScore += 300;
break;
}
}
TotalScore.Value = score;
Accuracy.Value = (double)score / maxScore;
}
}
}
-9
View File
@@ -37,15 +37,6 @@ namespace osu.Game.Modes.Osu.UI
AddInternal(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = Color4.Black,
Depth = float.MinValue,
Alpha = 0.5f,
},
approachCircles = new Container
{
RelativeSizeAxes = Axes.Both,
@@ -41,6 +41,8 @@
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Objects\BezierApproximator.cs" />
<Compile Include="Objects\Drawables\DrawableOsuHitObject.cs" />
<Compile Include="Objects\Drawables\Pieces\ApproachCircle.cs" />
<Compile Include="Objects\Drawables\Pieces\CirclePiece.cs" />
<Compile Include="Objects\Drawables\DrawableSlider.cs" />
@@ -52,6 +54,9 @@
<Compile Include="Objects\Drawables\Pieces\RingPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\Triangles.cs" />
<Compile Include="Objects\OsuHitObjectParser.cs" />
<Compile Include="Objects\SliderCurve.cs" />
<Compile Include="OsuScore.cs" />
<Compile Include="OsuScoreProcessor.cs" />
<Compile Include="UI\OsuComboCounter.cs" />
<Compile Include="UI\OsuHitRenderer.cs" />
<Compile Include="UI\OsuPlayfield.cs" />
+2
View File
@@ -18,6 +18,8 @@ namespace osu.Game.Modes.Catch
protected override PlayMode PlayMode => PlayMode.Catch;
public override ScoreProcessor CreateScoreProcessor() => null;
public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser();
}
}
+2
View File
@@ -19,6 +19,8 @@ namespace osu.Game.Modes.Mania
protected override PlayMode PlayMode => PlayMode.Mania;
public override ScoreProcessor CreateScoreProcessor() => null;
public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser();
}
}
+3
View File
@@ -1,6 +1,7 @@
//Copyright (c) 2007-2016 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.Modes.Objects;
using osu.Game.Modes.Osu.Objects;
@@ -18,6 +19,8 @@ namespace osu.Game.Modes.Taiko
protected override PlayMode PlayMode => PlayMode.Taiko;
public override ScoreProcessor CreateScoreProcessor() => null;
public override HitObjectParser CreateHitObjectParser() => new OsuHitObjectParser();
}
}
+22
View File
@@ -16,5 +16,27 @@ namespace osu.Game.Beatmaps
public List<HitObject> HitObjects { get; set; }
public List<ControlPoint> ControlPoints { get; set; }
public List<Color4> ComboColors { get; set; }
public double BeatLengthAt(double time, bool applyMultipliers = false)
{
int point = 0;
int samplePoint = 0;
for (int i = 0; i < ControlPoints.Count; i++)
if (ControlPoints[i].Time <= time)
{
if (ControlPoints[i].TimingChange)
point = i;
else
samplePoint = i;
}
double mult = 1;
if (applyMultipliers && samplePoint > point && ControlPoints[samplePoint].BeatLength < 0)
mult = ControlPoints[samplePoint].VelocityAdjustment;
return ControlPoints[point].BeatLength * mult;
}
}
}
@@ -0,0 +1,24 @@
//Copyright (c) 2007-2016 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.Sprites;
namespace osu.Game.Beatmaps.Drawables
{
class BeatmapBackgroundSprite : Sprite
{
private readonly WorkingBeatmap working;
public BeatmapBackgroundSprite(WorkingBeatmap working)
{
this.working = working;
}
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
{
Texture = working.Background;
}
}
}
+1 -10
View File
@@ -35,14 +35,8 @@ namespace osu.Game.Beatmaps.Drawables
switch (state)
{
case BeatmapGroupState.Expanded:
//if (!difficulties.Children.All(d => IsLoaded))
// Task.WhenAll(difficulties.Children.Select(d => d.Preload(Game))).ContinueWith(t => difficulties.Show());
//else
foreach (BeatmapPanel panel in BeatmapPanels)
{
panel.Hidden = false;
panel.FadeIn(250);
}
Header.State = PanelSelectedState.Selected;
if (SelectedPanel != null)
@@ -54,11 +48,7 @@ namespace osu.Game.Beatmaps.Drawables
SelectedPanel.State = PanelSelectedState.NotSelected;
foreach (BeatmapPanel panel in BeatmapPanels)
{
panel.Hidden = true;
panel.FadeOut(250);
}
break;
}
}
@@ -76,6 +66,7 @@ namespace osu.Game.Beatmaps.Drawables
BeatmapPanels = beatmap.BeatmapSetInfo.Beatmaps.Select(b => new BeatmapPanel(b)
{
Alpha = 0,
GainedSelection = panelGainedSelection,
RelativeSizeAxes = Axes.X,
}).ToList();
+20 -4
View File
@@ -2,13 +2,17 @@
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.MathUtils;
using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.UserInterface;
using OpenTK;
using OpenTK.Graphics;
@@ -22,6 +26,8 @@ namespace osu.Game.Beatmaps.Drawables
public Action<BeatmapPanel> GainedSelection;
Color4 deselectedColour = new Color4(20, 43, 51, 255);
protected override void Selected()
{
base.Selected();
@@ -36,7 +42,7 @@ namespace osu.Game.Beatmaps.Drawables
{
base.Deselected();
background.Colour = new Color4(20, 43, 51, 255);
background.Colour = deselectedColour;
}
public BeatmapPanel(BeatmapInfo beatmap)
@@ -44,12 +50,22 @@ namespace osu.Game.Beatmaps.Drawables
Beatmap = beatmap;
Height *= 0.60f;
Children = new Framework.Graphics.Drawable[]
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
},
new Triangles
{
// The border is drawn in the shader of the children. Being additive, triangles would over-emphasize
// the border wherever they cross it, and thus they get their own masking container without a border.
Masking = true,
CornerRadius = Content.CornerRadius,
RelativeSizeAxes = Axes.Both,
BlendingMode = BlendingMode.Additive,
Colour = deselectedColour,
},
new FlowContainer
{
Padding = new MarginPadding(5),
@@ -57,7 +73,7 @@ namespace osu.Game.Beatmaps.Drawables
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Children = new Framework.Graphics.Drawable[]
Children = new Drawable[]
{
new DifficultyIcon(FontAwesome.fa_dot_circle_o, new Color4(159, 198, 0, 255))
{
@@ -71,7 +87,7 @@ namespace osu.Game.Beatmaps.Drawables
Spacing = new Vector2(0, 5),
Direction = FlowDirection.VerticalOnly,
AutoSizeAxes = Axes.Both,
Children = new Framework.Graphics.Drawable[]
Children = new Drawable[]
{
new FlowContainer
{
@@ -27,7 +27,6 @@ namespace osu.Game.Beatmaps.Drawables
public BeatmapSetHeader(WorkingBeatmap beatmap)
{
this.beatmap = beatmap;
Hidden = false;
Children = new Drawable[]
{
@@ -72,6 +71,12 @@ namespace osu.Game.Beatmaps.Drawables
};
}
protected override void LoadComplete()
{
base.LoadComplete();
FadeInFromZero(250);
}
protected override void Selected()
{
base.Selected();
@@ -114,7 +119,7 @@ namespace osu.Game.Beatmaps.Drawables
Children = new[]
{
new FlowContainer
{
{
Depth = -1,
Direction = FlowDirection.HorizontalOnly,
RelativeSizeAxes = Axes.Both,
@@ -156,38 +161,17 @@ namespace osu.Game.Beatmaps.Drawables
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
{
{
new BeatmapBackgroundSprite(working)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
}.Preload(game, (bg) =>
{
Add(bg);
ForceRedraw();
});
}
class BeatmapBackground : Sprite
{
private readonly WorkingBeatmap working;
public BeatmapBackground(WorkingBeatmap working)
{
this.working = working;
}
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
{
Texture = working.Background;
}
protected override void LoadComplete()
{
base.LoadComplete();
Scale = new Vector2(1366 / (Texture?.Width ?? 1) * 0.6f);
}
}
}
}
+7 -2
View File
@@ -4,6 +4,7 @@
using osu.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Transformations;
using osu.Framework.Input;
using OpenTK;
using OpenTK.Graphics;
@@ -14,7 +15,12 @@ namespace osu.Game.Beatmaps.Drawables
{
public const float MAX_HEIGHT = 80;
public bool Hidden = true;
public override bool RemoveWhenNotAlive => false;
public bool IsOnScreen;
public override bool IsAlive => IsOnScreen && base.IsAlive;
private Container nestedContainer;
protected override Container<Drawable> Content => nestedContainer;
@@ -42,7 +48,6 @@ namespace osu.Game.Beatmaps.Drawables
{
base.LoadComplete();
applyState();
FadeInFromZero(250);
}
private void applyState()
+23 -1
View File
@@ -187,7 +187,25 @@ namespace osu.Game.Beatmaps.Formats
private void handleTimingPoints(Beatmap beatmap, string val)
{
// TODO
ControlPoint cp = null;
string[] split = val.Split(',');
if (split.Length > 2)
{
int kiai_flags = split.Length > 7 ? Convert.ToInt32(split[7], NumberFormatInfo.InvariantInfo) : 0;
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
cp = new ControlPoint
{
Time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo),
BeatLength = beatLength > 0 ? beatLength : 0,
VelocityAdjustment = beatLength < 0 ? -beatLength / 100.0 : 1,
TimingChange = split.Length <= 6 || split[6][0] == '1',
};
}
if (cp != null)
beatmap.ControlPoints.Add(cp);
}
private void handleColours(Beatmap beatmap, string key, string val)
@@ -275,8 +293,12 @@ namespace osu.Game.Beatmaps.Formats
break;
case Section.HitObjects:
var obj = parser?.Parse(val);
if (obj != null)
{
obj.SetDefaultsFromBeatmap(beatmap);
beatmap.HitObjects.Add(obj);
}
break;
}
}
+9
View File
@@ -12,5 +12,14 @@ namespace osu.Game.Beatmaps.Timing
public class ControlPoint
{
public double Time;
public double BeatLength;
public double VelocityAdjustment;
public bool TimingChange;
}
internal enum TimeSignatures
{
SimpleQuadruple = 4,
SimpleTriple = 3
}
}
+1 -1
View File
@@ -156,7 +156,7 @@ namespace osu.Game.Configuration
Set(OsuConfig.AudioDevice, string.Empty);
//Set(OsuConfig.ReleaseStream, ReleaseStream.Lazer, true);
Set(OsuConfig.UpdateFailCount, 0);
//Set(OsuConfig.SavePassword, Password != null);
Set(OsuConfig.SavePassword, false);
Set(OsuConfig.SaveUsername, true);
//Set(OsuConfig.TreeSortMode, TreeGroupMode.Show_All);
//Set(OsuConfig.TreeSortMode2, TreeSortMode.Title);
+1 -1
View File
@@ -22,7 +22,7 @@ namespace osu.Game.Graphics.Backgrounds
{
this.textureName = textureName;
RelativeSizeAxes = Axes.Both;
Depth = float.MinValue;
Depth = float.MaxValue;
Add(Sprite = new Sprite
{
@@ -0,0 +1,79 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.MathUtils;
using OpenTK;
namespace osu.Game.Graphics.Backgrounds
{
public class Triangles : Container<Triangle>
{
public Triangles()
{
Alpha = 0.3f;
}
private float triangleScale = 1;
public float TriangleScale
{
get { return triangleScale; }
set
{
triangleScale = value;
Children.ForEach(t => t.ScaleTo(triangleScale));
}
}
private int aimTriangleCount => (int)((DrawWidth * DrawHeight) / 800 / triangleScale);
protected override void Update()
{
base.Update();
foreach (Drawable d in Children)
{
d.Position -= new Vector2(0, (float)(d.Scale.X * (50 / DrawHeight) * (Time.Elapsed / 880)) / triangleScale);
if (d.DrawPosition.Y + d.DrawSize.Y * d.Scale.Y < 0)
d.Expire();
}
bool useRandomX = Children.Count() < aimTriangleCount / 2;
while (Children.Count() < aimTriangleCount)
addTriangle(useRandomX);
}
protected virtual Triangle CreateTriangle()
{
var scale = triangleScale * RNG.NextSingle() * 0.4f + 0.2f;
const float size = 100;
return new Triangle
{
Origin = Anchor.TopCentre,
RelativePositionAxes = Axes.Both,
Scale = new Vector2(scale),
// Scaling height by 0.866 results in equiangular triangles (== 60° and equal side length)
Size = new Vector2(size, 0.866f * size),
Alpha = RNG.NextSingle(),
Depth = scale,
};
}
private void addTriangle(bool randomX)
{
var sprite = CreateTriangle();
sprite.Position = new Vector2(RNG.NextSingle(), randomX ? RNG.NextSingle() : 1);
Add(sprite);
}
}
}
@@ -4,6 +4,7 @@ using osu.Framework.Input;
using OpenTK;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Transformations;
namespace osu.Game.Graphics.Containers
{
@@ -35,11 +36,16 @@ namespace osu.Game.Graphics.Containers
this.input = input;
}
bool firstUpdate = true;
protected override void Update()
{
base.Update();
content.Position = (ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2) * ParallaxAmount;
content.MoveTo((ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2) * ParallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint);
content.Scale = new Vector2(1 + ParallaxAmount);
firstUpdate = false;
}
}
}
+228
View File
@@ -0,0 +1,228 @@
//Copyright (c) 2007-2016 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.Shaders;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using OpenTK;
using System;
using osu.Framework.Graphics.OpenGL;
using osu.Framework.Graphics.OpenGL.Buffers;
using OpenTK.Graphics.ES30;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Colour;
namespace osu.Game.Graphics.Cursor
{
class CursorTrail : Drawable
{
public override bool Contains(Vector2 screenSpacePos) => true;
public override bool HandleInput => true;
private int currentIndex;
private Shader shader;
private Texture texture;
private Vector2 size => texture.Size * Scale;
private double timeOffset;
private float time;
private TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData();
private const int MAX_SPRITES = 2048;
private TrailPart[] parts = new TrailPart[MAX_SPRITES];
private Vector2? lastPosition;
protected override DrawNode CreateDrawNode() => new TrailDrawNode();
protected override void ApplyDrawNode(DrawNode node)
{
base.ApplyDrawNode(node);
TrailDrawNode tNode = node as TrailDrawNode;
tNode.Shader = shader;
tNode.Texture = texture;
tNode.Size = size;
tNode.Time = time;
tNode.Shared = trailDrawNodeSharedData;
for (int i = 0; i < parts.Length; ++i)
if (parts[i].InvalidationID > tNode.Parts[i].InvalidationID)
tNode.Parts[i] = parts[i];
}
public CursorTrail()
{
RelativeSizeAxes = Axes.Both;
for (int i = 0; i < MAX_SPRITES; i++)
{
parts[i].InvalidationID = 0;
parts[i].WasUpdated = true;
}
}
[BackgroundDependencyLoader]
private void load(ShaderManager shaders, TextureStore textures)
{
shader = shaders?.Load(@"CursorTrail", FragmentShaderDescriptor.Texture);
texture = textures.Get(@"Cursor/cursortrail");
}
protected override void Update()
{
base.Update();
Invalidate(Invalidation.DrawNode, shallPropagate: false);
int fadeClockResetThreshold = 1000000;
time = (float)(Time.Current - timeOffset) / 500f;
if (time > fadeClockResetThreshold)
ResetTime();
}
private void ResetTime()
{
for (int i = 0; i < parts.Length; ++i)
{
parts[i].Time -= time;
++parts[i].InvalidationID;
}
time = 0;
timeOffset = Time.Current;
}
protected override bool OnMouseMove(InputState state)
{
if (lastPosition == null)
{
lastPosition = state.Mouse.NativeState.Position;
return base.OnMouseMove(state);
}
Vector2 pos1 = lastPosition.Value;
Vector2 pos2 = state.Mouse.NativeState.Position;
Vector2 diff = pos2 - pos1;
float distance = diff.Length;
Vector2 direction = diff / distance;
float interval = (size.X / 2) * 0.9f;
for (float d = interval; d < distance; d += interval)
{
lastPosition = pos1 + direction * d;
addPosition(lastPosition.Value);
}
return base.OnMouseMove(state);
}
private void addPosition(Vector2 pos)
{
parts[currentIndex].Position = pos;
parts[currentIndex].Time = time;
++parts[currentIndex].InvalidationID;
currentIndex = (currentIndex + 1) % MAX_SPRITES;
}
struct TrailPart
{
public Vector2 Position;
public float Time;
public long InvalidationID;
public bool WasUpdated;
}
class TrailDrawNodeSharedData
{
public VertexBuffer<TexturedVertex2D> VertexBuffer;
}
class TrailDrawNode : DrawNode
{
public Shader Shader;
public Texture Texture;
public float Time;
public TrailDrawNodeSharedData Shared;
public TrailPart[] Parts = new TrailPart[MAX_SPRITES];
public Vector2 Size;
public TrailDrawNode()
{
for (int i = 0; i < MAX_SPRITES; i++)
{
Parts[i].InvalidationID = 0;
Parts[i].WasUpdated = false;
}
}
public override void Draw(Action<TexturedVertex2D> vertexAction)
{
if (Shared.VertexBuffer == null)
Shared.VertexBuffer = new QuadVertexBuffer<TexturedVertex2D>(MAX_SPRITES, BufferUsageHint.DynamicDraw);
Shader.GetUniform<float>("g_FadeClock").Value = Time;
int updateStart = -1, updateEnd = 0;
for (int i = 0; i < Parts.Length; ++i)
{
if (Parts[i].WasUpdated)
{
if (updateStart == -1)
updateStart = i;
updateEnd = i + 1;
int start = i * 4;
int end = start;
Vector2 pos = Parts[i].Position;
ColourInfo colour = DrawInfo.Colour;
colour.TopLeft.Linear.A = Parts[i].Time + colour.TopLeft.Linear.A;
colour.TopRight.Linear.A = Parts[i].Time + colour.TopRight.Linear.A;
colour.BottomLeft.Linear.A = Parts[i].Time + colour.BottomLeft.Linear.A;
colour.BottomRight.Linear.A = Parts[i].Time + colour.BottomRight.Linear.A;
Texture.DrawQuad(
new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y),
colour,
null,
v => Shared.VertexBuffer.Vertices[end++] = v);
Parts[i].WasUpdated = false;
}
else if (updateStart != -1)
{
Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
updateStart = -1;
}
}
// Update all remaining vertices that have been changed.
if (updateStart != -1)
Shared.VertexBuffer.UpdateRange(updateStart * 4, updateEnd * 4);
base.Draw(vertexAction);
Shader.Bind();
Texture.TextureGL.Bind();
Shared.VertexBuffer.Draw();
Shader.Unbind();
}
}
}
}
@@ -1,7 +1,7 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework;
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -17,9 +17,14 @@ namespace osu.Game.Graphics.Cursor
{
protected override Drawable CreateCursor() => new OsuCursor();
public OsuCursorContainer()
{
Add(new CursorTrail { Depth = 1 });
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
ActiveCursor.Scale = new OpenTK.Vector2(1);
ActiveCursor.Scale = new Vector2(1);
ActiveCursor.ScaleTo(1.2f, 100, EasingTypes.OutQuad);
return base.OnMouseDown(state, args);
}
@@ -52,5 +57,4 @@ namespace osu.Game.Graphics.Cursor
}
}
}
}
@@ -0,0 +1,160 @@
// Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Transformations;
using osu.Framework.Input;
namespace osu.Game.Graphics.UserInterface
{
// Basic back button as it was on stable (kinda). No skinning possible for now
public class BackButton : ClickableContainer
{
private TextAwesome icon;
private Container leftContainer;
private Container rightContainer;
private Box leftBox;
private Box rightBox;
private const double transform_time = 300.0;
private const int pulse_length = 250;
private const float shear = 0.1f;
private static readonly Vector2 size_extended = new Vector2(140, 50);
private static readonly Vector2 size_retracted = new Vector2(100, 50);
public BackButton()
{
Size = size_retracted;
Children = new Drawable[]
{
leftContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Width = 0.4f,
Children = new Drawable[]
{
leftBox = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = new Color4(195, 40, 140, 255),
Shear = new Vector2(shear, 0),
},
icon = new TextAwesome
{
Anchor = Anchor.Centre,
TextSize = 25,
Icon = FontAwesome.fa_osu_left_o
},
}
},
rightContainer = new Container
{
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
RelativeSizeAxes = Axes.Both,
Width = 0.6f,
Children = new Drawable[]
{
rightBox = new Box
{
Colour = new Color4(238, 51, 153, 255),
Origin = Anchor.TopLeft,
Anchor = Anchor.TopLeft,
RelativeSizeAxes = Axes.Both,
Shear = new Vector2(shear, 0),
EdgeSmoothness = new Vector2(1.5f, 0),
},
new SpriteText
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Text = @"Back",
}
}
}
};
}
public override bool Contains(Vector2 screenSpacePos)
{
return leftBox.Contains(screenSpacePos) || rightBox.Contains(screenSpacePos);
}
protected override bool OnHover(InputState state)
{
icon.ClearTransformations();
ResizeTo(size_extended, transform_time, EasingTypes.OutElastic);
int duration = 0; //(int)(Game.Audio.BeatLength / 2);
if (duration == 0) duration = pulse_length;
double offset = 0; //(1 - Game.Audio.SyncBeatProgress) * duration;
double startTime = Time.Current + offset;
// basic pulse
icon.Transforms.Add(new TransformScale
{
StartValue = new Vector2(1.1f),
EndValue = Vector2.One,
StartTime = startTime,
EndTime = startTime + duration,
Easing = EasingTypes.Out,
LoopCount = -1,
LoopDelay = duration
});
return true;
}
protected override void OnHoverLost(InputState state)
{
icon.ClearTransformations();
ResizeTo(size_retracted, transform_time, EasingTypes.OutElastic);
int duration = 0; //(int)(Game.Audio.BeatLength);
if (duration == 0) duration = pulse_length * 2;
double offset = 0; //(1 - Game.Audio.SyncBeatProgress) * duration;
double startTime = Time.Current + offset;
// slow pulse
icon.Transforms.Add(new TransformScale
{
StartValue = new Vector2(1.1f),
EndValue = Vector2.One,
StartTime = startTime,
EndTime = startTime + duration,
Easing = EasingTypes.Out,
LoopCount = -1,
LoopDelay = duration
});
}
protected override bool OnClick(InputState state)
{
var flash = new Box
{
RelativeSizeAxes = Axes.Both,
Shear = new Vector2(shear, 0),
Colour = new Color4(255, 255, 255, 128),
};
Add(flash);
flash.FadeOutFromOne(200);
flash.Expire();
return base.OnClick(state);
}
}
}
@@ -6,17 +6,14 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Threading;
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Audio;
using osu.Framework.Allocation;
namespace osu.Game.Graphics.UserInterface.Volume
{
internal class VolumeControl : OverlayContainer
{
public BindableDouble VolumeGlobal { get; set; }
public BindableDouble VolumeSample { get; set; }
public BindableDouble VolumeTrack { get; set; }
private VolumeMeter volumeMeterMaster;
private void volumeChanged(object sender, EventArgs e)
@@ -54,21 +51,18 @@ namespace osu.Game.Graphics.UserInterface.Volume
{
base.LoadComplete();
VolumeGlobal.ValueChanged += volumeChanged;
VolumeSample.ValueChanged += volumeChanged;
VolumeTrack.ValueChanged += volumeChanged;
volumeMeterMaster.Bindable = VolumeGlobal;
volumeMeterEffect.Bindable = VolumeSample;
volumeMeterMusic.Bindable = VolumeTrack;
volumeMeterMaster.Bindable.ValueChanged += volumeChanged;
volumeMeterEffect.Bindable.ValueChanged += volumeChanged;
volumeMeterMusic.Bindable.ValueChanged += volumeChanged;
}
protected override void Dispose(bool isDisposing)
{
VolumeGlobal.ValueChanged -= volumeChanged;
VolumeSample.ValueChanged -= volumeChanged;
VolumeTrack.ValueChanged -= volumeChanged;
base.Dispose(isDisposing);
volumeMeterMaster.Bindable.ValueChanged -= volumeChanged;
volumeMeterEffect.Bindable.ValueChanged -= volumeChanged;
volumeMeterMusic.Bindable.ValueChanged -= volumeChanged;
}
public void Adjust(InputState state)
@@ -82,6 +76,14 @@ namespace osu.Game.Graphics.UserInterface.Volume
volumeMeterMaster.TriggerWheel(state);
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
volumeMeterMaster.Bindable.Weld(audio.Volume);
volumeMeterEffect.Bindable.Weld(audio.VolumeSample);
volumeMeterMusic.Bindable.Weld(audio.VolumeTrack);
}
ScheduledDelegate popOutDelegate;
private VolumeMeter volumeMeterEffect;
@@ -13,7 +13,7 @@ namespace osu.Game.Graphics.UserInterface.Volume
internal class VolumeMeter : Container
{
private Box meterFill;
public BindableDouble Bindable;
public BindableDouble Bindable { get; private set; } = new BindableDouble();
public VolumeMeter(string meterName)
{
@@ -41,6 +41,7 @@ namespace osu.Game.Graphics.UserInterface.Volume
meterFill = new Box
{
Colour = Color4.White,
Scale = new Vector2(1, 0),
RelativeSizeAxes = Axes.Both,
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre
@@ -54,6 +55,8 @@ namespace osu.Game.Graphics.UserInterface.Volume
Origin = Anchor.TopCentre
}
};
Bindable.ValueChanged += delegate { updateFill(); };
}
protected override void LoadComplete()
@@ -68,7 +71,6 @@ namespace osu.Game.Graphics.UserInterface.Volume
private set
{
Bindable.Value = value;
updateFill();
}
}
-51
View File
@@ -1,51 +0,0 @@
//Copyright (c) 2007-2016 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.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using osu.Game.Modes.Objects;
using OpenTK;
namespace osu.Game.Modes
{
public class HitJudgementResolver
{
public JudgementResult CheckJudgement(HitObject h) => new JudgementResult { Combo = ComboJudgement.None, Judgement = Judgement.Hit300 };
}
public struct JudgementResult
{
public ComboJudgement Combo;
public Judgement Judgement;
public float TimeOffset;
public Vector2 PositionOffset;
}
public enum ComboJudgement
{
[Description(@"")]
None,
[Description(@"Good")]
Good,
[Description(@"Amazing")]
Perfect
}
public enum Judgement
{
[Description(@"Miss")]
Miss,
[Description(@"50")]
Hit50,
[Description(@"100")]
Hit100,
[Description(@"300")]
Hit300,
[Description(@"500")]
Hit500
}
}
@@ -2,29 +2,31 @@
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.ComponentModel;
using System.Diagnostics;
using osu.Framework;
using osu.Framework.Graphics.Containers;
using OpenTK;
using Container = osu.Framework.Graphics.Containers.Container;
namespace osu.Game.Modes.Objects.Drawables
{
public abstract class DrawableHitObject : Container, IStateful<ArmedState>
{
//todo: move to a more central implementation. this logic should not be at a drawable level.
public Action<DrawableHitObject> OnHit;
public Action<DrawableHitObject> OnMiss;
public Func<DrawableHitObject, bool> AllowHit;
public event Action<DrawableHitObject, JudgementInfo> OnJudgement;
public Container<DrawableHitObject> ChildObjects;
public JudgementResult Result;
public JudgementInfo Judgement;
public abstract JudgementInfo CreateJudgementInfo();
public HitObject HitObject;
public DrawableHitObject(HitObject hitObject)
{
HitObject = hitObject;
Depth = -(float)hitObject.StartTime;
Depth = (float)hitObject.StartTime;
}
private ArmedState state;
@@ -41,36 +43,55 @@ namespace osu.Game.Modes.Objects.Drawables
}
}
protected double? HitTime;
protected virtual bool Hit()
protected override void LoadComplete()
{
if (State != ArmedState.Disarmed)
base.LoadComplete();
Judgement = CreateJudgementInfo();
}
/// <summary>
/// Process a hit of this hitobject. Carries out judgement.
/// </summary>
/// <param name="judgement">Preliminary judgement information provided by the hit source.</param>
/// <returns>Whether a hit was processed.</returns>
protected bool UpdateJudgement(bool userTriggered)
{
if (Judgement.Result != null)
return false;
if (AllowHit?.Invoke(this) == false)
Judgement.TimeOffset = Time.Current - HitObject.EndTime;
CheckJudgement(userTriggered);
if (Judgement.Result == null)
return false;
HitTime = Time.Current;
switch (Judgement.Result)
{
default:
State = ArmedState.Hit;
break;
case HitResult.Miss:
State = ArmedState.Miss;
break;
}
OnJudgement?.Invoke(this, Judgement);
State = ArmedState.Armed;
return true;
}
private bool counted;
protected virtual void CheckJudgement(bool userTriggered)
{
//todo: consider making abstract.
}
protected override void Update()
{
base.Update();
if (Time.Current >= HitObject.EndTime && !counted)
{
counted = true;
if (state == ArmedState.Armed)
OnHit?.Invoke(this);
else
OnMiss?.Invoke(this);
}
UpdateJudgement(false);
}
protected abstract void UpdateState(ArmedState state);
@@ -78,7 +99,28 @@ namespace osu.Game.Modes.Objects.Drawables
public enum ArmedState
{
Disarmed,
Armed
Idle,
Hit,
Miss
}
public class PositionalJudgementInfo : JudgementInfo
{
public Vector2 PositionOffset;
}
public class JudgementInfo
{
public ulong? ComboAtHit;
public HitResult? Result;
public double TimeOffset;
}
public enum HitResult
{
[Description(@"Miss")]
Miss,
[Description(@"Hit")]
Hit,
}
}
+3
View File
@@ -1,6 +1,7 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Samples;
using OpenTK.Graphics;
@@ -21,5 +22,7 @@ namespace osu.Game.Modes.Objects
public double Duration => EndTime - StartTime;
public HitSampleInfo Sample;
public virtual void SetDefaultsFromBeatmap(Beatmap beatmap) { }
}
}
+2 -2
View File
@@ -18,12 +18,12 @@ namespace osu.Game.Modes
public abstract ScoreOverlay CreateScoreOverlay();
public abstract ScoreProcessor CreateScoreProcessor();
public abstract HitRenderer CreateHitRendererWith(List<HitObject> objects);
public abstract HitObjectParser CreateHitObjectParser();
public virtual HitJudgementResolver CreateHitJudgement() => new HitJudgementResolver();
public static void Register(Ruleset ruleset) => availableRulesets.TryAdd(ruleset.PlayMode, ruleset.GetType());
protected abstract PlayMode PlayMode { get; }
+19
View File
@@ -0,0 +1,19 @@
//Copyright (c) 2007-2016 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 System.Text;
using System.Threading.Tasks;
namespace osu.Game.Modes
{
public class Score
{
public double TotalScore { get; set; }
public double Accuracy { get; set; }
public double Combo { get; set; }
public double MaxCombo { get; set; }
}
}
+54
View File
@@ -0,0 +1,54 @@
//Copyright (c) 2007-2016 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 System.Text;
using System.Threading.Tasks;
using osu.Framework.Configuration;
using osu.Game.Modes.Objects.Drawables;
namespace osu.Game.Modes
{
public abstract class ScoreProcessor
{
public virtual Score GetScore() => new Score()
{
TotalScore = TotalScore,
Combo = Combo,
MaxCombo = HighestCombo,
Accuracy = Accuracy
};
public readonly BindableDouble TotalScore = new BindableDouble { MinValue = 0 };
public readonly BindableDouble Accuracy = new BindableDouble { MinValue = 0, MaxValue = 1 };
public readonly BindableInt Combo = new BindableInt();
public readonly BindableInt HighestCombo = new BindableInt();
public readonly List<JudgementInfo> Judgements = new List<JudgementInfo>();
public ScoreProcessor()
{
Combo.ValueChanged += delegate { HighestCombo.Value = Math.Max(HighestCombo.Value, Combo.Value); };
}
public void AddJudgement(JudgementInfo judgement)
{
Judgements.Add(judgement);
UpdateCalculations(judgement);
judgement.ComboAtHit = (ulong)Combo.Value;
}
/// <summary>
/// Update any values that potentially need post-processing on a judgement change.
/// </summary>
/// <param name="newJudgement">A new JudgementInfo that triggered this calculation. May be null.</param>
protected abstract void UpdateCalculations(JudgementInfo newJudgement);
}
}
+8
View File
@@ -262,5 +262,13 @@ namespace osu.Game.Modes.UI
(d as ComboCounter).DisplayedCount = CurrentValue;
}
}
public void Set(ulong value)
{
if (value == 0)
Roll();
else
Count = value;
}
}
}
+14 -13
View File
@@ -14,11 +14,21 @@ namespace osu.Game.Modes.UI
{
public abstract class HitRenderer : Container
{
public Action<HitObject> OnHit;
public Action<HitObject> OnMiss;
public event Action<JudgementInfo> OnJudgement;
public event Action OnAllJudged;
protected void TriggerOnJudgement(JudgementInfo j)
{
OnJudgement?.Invoke(j);
if (AllObjectsJudged)
OnAllJudged?.Invoke();
}
protected Playfield Playfield;
public bool AllObjectsJudged => Playfield.HitObjects.Children.First()?.Judgement.Result != null; //reverse depth sort means First() instead of Last().
public IEnumerable<DrawableHitObject> DrawableObjects => Playfield.HitObjects.Children;
}
@@ -68,22 +78,13 @@ namespace osu.Game.Modes.UI
if (drawableObject == null) continue;
drawableObject.OnHit = onHit;
drawableObject.OnMiss = onMiss;
drawableObject.OnJudgement += onJudgement;
Playfield.Add(drawableObject);
}
}
private void onMiss(DrawableHitObject obj)
{
OnMiss?.Invoke(obj.HitObject);
}
private void onHit(DrawableHitObject obj)
{
OnHit?.Invoke(obj.HitObject);
}
private void onJudgement(DrawableHitObject o, JudgementInfo j) => TriggerOnJudgement(j);
protected abstract DrawableHitObject GetVisualRepresentation(T h);
}
+4
View File
@@ -14,6 +14,8 @@ namespace osu.Game.Modes.UI
public virtual void Add(DrawableHitObject h) => HitObjects.Add(h);
public override bool Contains(Vector2 screenSpacePos) => true;
public Playfield()
{
AddInternal(HitObjects = new HitObjectContainer
@@ -25,6 +27,8 @@ namespace osu.Game.Modes.UI
public class HitObjectContainer : Container<DrawableHitObject>
{
protected override Vector2 DrawScale => new Vector2(DrawSize.X / 512);
public override bool Contains(Vector2 screenSpacePos) => true;
}
}
}
+9
View File
@@ -15,6 +15,7 @@ namespace osu.Game.Modes.UI
public ComboCounter ComboCounter;
public ScoreCounter ScoreCounter;
public PercentageCounter AccuracyCounter;
public Score Score { get; set; }
protected abstract KeyCounterCollection CreateKeyCounter();
protected abstract ComboCounter CreateComboCounter();
@@ -45,5 +46,13 @@ namespace osu.Game.Modes.UI
AccuracyCounter = CreateAccuracyCounter(),
};
}
public void BindProcessor(ScoreProcessor processor)
{
//bind processor bindables to combocounter, score display etc.
processor.TotalScore.ValueChanged += delegate { ScoreCounter?.Set((ulong)processor.TotalScore.Value); };
processor.Accuracy.ValueChanged += delegate { AccuracyCounter?.Set((float)processor.Accuracy.Value); };
processor.Combo.ValueChanged += delegate { ComboCounter?.Set((ulong)processor.Combo.Value); };
}
}
}
+78 -36
View File
@@ -3,9 +3,12 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Threading;
using osu.Framework;
using osu.Framework.Configuration;
using osu.Framework.Logging;
using osu.Framework.Threading;
using osu.Game.Online.API.Requests;
@@ -17,8 +20,8 @@ namespace osu.Game.Online.API
private OAuth authentication;
public string Endpoint = @"https://new.ppy.sh";
const string ClientId = @"daNBnfdv7SppRVc61z0XuOI13y6Hroiz";
const string ClientSecret = @"d6fgZuZeQ0eSXkEj5igdqQX6ztdtS6Ow";
const string ClientId = @"5";
const string ClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
ConcurrentQueue<APIRequest> queue = new ConcurrentQueue<APIRequest>();
@@ -26,15 +29,11 @@ namespace osu.Game.Online.API
public string Username;
private SecurePassword password;
//private SecurePassword password;
public string Password
{
set
{
password = string.IsNullOrEmpty(value) ? null : new SecurePassword(value);
}
}
public string Password;
public Bindable<User> LocalUser = new Bindable<User>();
public string Token
{
@@ -50,7 +49,7 @@ namespace osu.Game.Online.API
}
}
protected bool HasLogin => Token != null || (!string.IsNullOrEmpty(Username) && password != null);
protected bool HasLogin => Token != null || (!string.IsNullOrEmpty(Username) && !string.IsNullOrEmpty(Password));
private Thread thread;
@@ -65,6 +64,25 @@ namespace osu.Game.Online.API
thread.Start();
}
private List<IOnlineComponent> components = new List<IOnlineComponent>();
public void Register(IOnlineComponent component)
{
Scheduler.Add(delegate
{
components.Add(component);
component.APIStateChanged(this, state);
});
}
public void Unregister(IOnlineComponent component)
{
Scheduler.Add(delegate
{
components.Remove(component);
});
}
public string AccessToken => authentication.RequestAccessToken();
/// <summary>
@@ -102,7 +120,7 @@ namespace osu.Game.Online.API
if (State < APIState.Connecting)
State = APIState.Connecting;
if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(Username, password.Get(Representation.Raw)))
if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(Username, Password))
{
//todo: this fails even on network-related issues. we should probably handle those differently.
//NotificationManager.ShowMessage("Login failed!");
@@ -111,6 +129,17 @@ namespace osu.Game.Online.API
continue;
}
var userReq = new GetUserRequest();
userReq.Success += (u) => {
LocalUser.Value = u;
};
if (!handleRequest(userReq))
{
State = APIState.Failing;
continue;
}
//we're connected!
State = APIState.Online;
failureCount = 0;
@@ -142,7 +171,17 @@ namespace osu.Game.Online.API
private void ClearCredentials()
{
Username = null;
password = null;
Password = null;
}
public void Login(string username, string password)
{
Debug.Assert(State == APIState.Offline);
Username = username;
Password = password;
State = APIState.Connecting;
}
/// <summary>
@@ -154,6 +193,7 @@ namespace osu.Game.Online.API
{
try
{
Logger.Log($@"Performing request {req}", LoggingTarget.Network);
req.Perform(this);
State = APIState.Online;
@@ -221,6 +261,7 @@ namespace osu.Game.Online.API
log.Add($@"We just went {newState}!");
Scheduler.Add(delegate
{
components.ForEach(c => c.APIStateChanged(this, newState));
OnStateChange?.Invoke(oldState, newState);
});
}
@@ -237,29 +278,6 @@ namespace osu.Game.Online.API
public delegate void StateChangeDelegate(APIState oldState, APIState newState);
public enum APIState
{
/// <summary>
/// We cannot login (not enough credentials).
/// </summary>
Offline,
/// <summary>
/// We are having connectivity issues.
/// </summary>
Failing,
/// <summary>
/// We are in the process of (re-)connecting.
/// </summary>
Connecting,
/// <summary>
/// We are online.
/// </summary>
Online
}
private void flushQueue(bool failOldRequests = true)
{
var oldQueue = queue;
@@ -277,6 +295,7 @@ namespace osu.Game.Online.API
public void Logout()
{
ClearCredentials();
authentication.Clear();
State = APIState.Offline;
}
@@ -286,4 +305,27 @@ namespace osu.Game.Online.API
Scheduler.Update();
}
}
public enum APIState
{
/// <summary>
/// We cannot login (not enough credentials).
/// </summary>
Offline,
/// <summary>
/// We are having connectivity issues.
/// </summary>
Failing,
/// <summary>
/// We are in the process of (re-)connecting.
/// </summary>
Connecting,
/// <summary>
/// We are online.
/// </summary>
Online
}
}
+16
View File
@@ -0,0 +1,16 @@
//Copyright (c) 2007-2016 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 System.Text;
using System.Threading.Tasks;
namespace osu.Game.Online.API
{
public interface IOnlineComponent
{
void APIStateChanged(APIAccess api, APIState state);
}
}
+2 -2
View File
@@ -30,7 +30,7 @@ namespace osu.Game.Online.API
{
var req = new AccessTokenRequestPassword(username, password)
{
Url = $@"{endpoint}/oauth/access_token",
Url = $@"{endpoint}/oauth/token",
Method = HttpMethod.POST,
ClientId = clientId,
ClientSecret = clientSecret
@@ -55,7 +55,7 @@ namespace osu.Game.Online.API
{
var req = new AccessTokenRequestRefresh(refresh)
{
Url = $@"{endpoint}/oauth/access_token",
Url = $@"{endpoint}/oauth/token",
Method = HttpMethod.POST,
ClientId = clientId,
ClientSecret = clientSecret
@@ -0,0 +1,18 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Online.API.Requests
{
public class GetUserRequest : APIRequest<User>
{
private int? userId;
public GetUserRequest(int? userId = null)
{
this.userId = userId;
}
protected override string Target => userId.HasValue ? $@"users/{userId}" : @"me";
}
}
@@ -4,15 +4,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transformations;
using OpenTK;
using osu.Framework;
using osu.Framework.Allocation;
namespace osu.Game.Online.Chat.Display
namespace osu.Game.Online.Chat.Drawables
{
public class ChannelDisplay : Container
{
@@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Online.Chat.Display
namespace osu.Game.Online.Chat.Drawables
{
public class ChatLine : Container
{
+3
View File
@@ -10,6 +10,9 @@ namespace osu.Game.Online
[JsonProperty(@"username")]
public string Name;
[JsonProperty(@"id")]
public int Id;
[JsonProperty(@"colour")]
public string Colour;
}
+28 -16
View File
@@ -19,7 +19,9 @@ using osu.Framework.Logging;
using osu.Game.Graphics.UserInterface.Volume;
using osu.Game.Database;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Transformations;
using osu.Game.Modes;
using osu.Game.Overlays.Toolbar;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play;
@@ -30,7 +32,7 @@ namespace osu.Game
{
public Toolbar Toolbar;
private ChatConsole chat;
private ChatOverlay chat;
private MusicController musicController;
@@ -45,7 +47,7 @@ namespace osu.Game
string[] args;
public OptionsOverlay Options;
private OptionsOverlay options;
public OsuGame(string[] args = null)
{
@@ -59,7 +61,7 @@ namespace osu.Game
host.Size = new Vector2(Config.Get<int>(OsuConfig.Width), Config.Get<int>(OsuConfig.Height));
}
public void ToggleOptions() => Options.ToggleVisibility();
public void ToggleOptions() => options.ToggleVisibility();
[BackgroundDependencyLoader]
private void load()
@@ -97,12 +99,7 @@ namespace osu.Game
{
RelativeSizeAxes = Axes.Both,
},
volume = new VolumeControl
{
VolumeGlobal = Audio.Volume,
VolumeSample = Audio.VolumeSample,
VolumeTrack = Audio.VolumeTrack
},
volume = new VolumeControl(),
overlayContent = new Container{ RelativeSizeAxes = Axes.Both },
new GlobalHotkeys //exists because UserInputManager is at a level below us.
{
@@ -120,16 +117,18 @@ namespace osu.Game
});
//overlay elements
(chat = new ChatConsole(API) { Depth = 0 }).Preload(this, overlayContent.Add);
(Options = new OptionsOverlay { Depth = 1 }).Preload(this, overlayContent.Add);
(musicController = new MusicController() { Depth = 3 }).Preload(this, overlayContent.Add);
(chat = new ChatOverlay { Depth = 0 }).Preload(this, overlayContent.Add);
(options = new OptionsOverlay { Depth = -1 }).Preload(this, overlayContent.Add);
(musicController = new MusicController() { Depth = -3 }).Preload(this, overlayContent.Add);
Dependencies.Cache(options);
Dependencies.Cache(musicController);
(Toolbar = new Toolbar
{
Depth = 2,
Depth = -2,
OnHome = delegate { mainMenu?.MakeCurrent(); },
OnSettings = Options.ToggleVisibility,
OnPlayModeChange = delegate (PlayMode m) { PlayMode.Value = m; },
OnMusicController = musicController.ToggleVisibility
}).Preload(this, t =>
{
PlayMode.ValueChanged += delegate { Toolbar.SetGameMode(PlayMode.Value); };
@@ -137,6 +136,19 @@ namespace osu.Game
overlayContent.Add(Toolbar);
});
options.StateChanged += delegate
{
switch (options.State)
{
case Visibility.Hidden:
intro.MoveToX(0, OptionsOverlay.TRANSITION_LENGTH, EasingTypes.OutQuint);
break;
case Visibility.Visible:
intro.MoveToX(OptionsOverlay.SIDEBAR_WIDTH / 2, OptionsOverlay.TRANSITION_LENGTH, EasingTypes.OutQuint);
break;
}
};
Cursor.Alpha = 0;
}
@@ -154,7 +166,7 @@ namespace osu.Game
switch (args.Key)
{
case Key.O:
Options.ToggleVisibility();
options.ToggleVisibility();
return true;
}
}
+20 -5
View File
@@ -16,12 +16,14 @@ using osu.Game.Database;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.Processing;
using osu.Game.IPC;
using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Overlays;
using osu.Game.Online.API.Requests;
namespace osu.Game
{
public class OsuGameBase : BaseGame
public class OsuGameBase : BaseGame, IOnlineComponent
{
internal OsuConfigManager Config;
@@ -41,11 +43,11 @@ namespace osu.Game
private void load()
{
Dependencies.Cache(this);
Dependencies.Cache(new OsuConfigManager(Host.Storage));
Dependencies.Cache(Config);
Dependencies.Cache(new BeatmapDatabase(Host.Storage, Host));
//this completely overrides the framework default. will need to change once we make a proper FontStore.
Dependencies.Cache(Fonts = new FontStore { ScaleAdjust = 0.01f });
Dependencies.Cache(Fonts = new FontStore { ScaleAdjust = 0.01f }, true);
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/FontAwesome"));
Fonts.AddStore(new GlyphStore(Resources, @"Fonts/osuFont"));
@@ -73,6 +75,19 @@ namespace osu.Game
Password = Config.Get<string>(OsuConfig.Password),
Token = Config.Get<string>(OsuConfig.Token)
});
API.Register(this);
}
public void APIStateChanged(APIAccess api, APIState state)
{
switch (state)
{
case APIState.Online:
Config.Set(OsuConfig.Username, Config.Get<bool>(OsuConfig.SaveUsername) ? API.Username : string.Empty);
Config.Set(OsuConfig.Password, Config.Get<bool>(OsuConfig.SavePassword) ? API.Password : string.Empty);
break;
}
}
protected override void LoadComplete()
@@ -83,7 +98,7 @@ namespace osu.Game
{
Children = new[]
{
Cursor = new OsuCursorContainer { Depth = float.MaxValue }
Cursor = new OsuCursorContainer { Depth = float.MinValue }
}
});
}
@@ -1,12 +1,12 @@
//Copyright (c) 2007-2016 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.Diagnostics;
using System.Linq;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -16,11 +16,11 @@ using osu.Framework.Threading;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Chat;
using osu.Game.Online.Chat.Display;
using osu.Game.Online.Chat.Drawables;
namespace osu.Game.Overlays
{
public class ChatConsole : OverlayContainer
public class ChatOverlay : OverlayContainer, IOnlineComponent
{
private ChannelDisplay channelDisplay;
@@ -32,10 +32,8 @@ namespace osu.Game.Overlays
private APIAccess api;
public ChatConsole(APIAccess api)
public ChatOverlay()
{
this.api = api;
RelativeSizeAxes = Axes.X;
Size = new Vector2(1, 300);
Anchor = Anchor.BottomLeft;
@@ -45,7 +43,7 @@ namespace osu.Game.Overlays
{
new Box
{
Depth = float.MinValue,
Depth = float.MaxValue,
RelativeSizeAxes = Axes.Both,
Colour = new Color4(0.1f, 0.1f, 0.1f, 0.4f),
},
@@ -57,50 +55,16 @@ namespace osu.Game.Overlays
}
[BackgroundDependencyLoader]
private void load()
private void load(APIAccess api)
{
initializeChannels();
this.api = api;
api.Register(this);
}
private long? lastMessageId;
private List<Channel> careChannels;
private void initializeChannels()
{
careChannels = new List<Channel>();
//if (api.State != APIAccess.APIState.Online)
// return;
SpriteText loading;
Add(loading = new SpriteText
{
Text = @"Loading available channels...",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
TextSize = 40,
});
messageRequest?.Cancel();
ListChannelsRequest req = new ListChannelsRequest();
req.Success += delegate (List<Channel> channels)
{
Scheduler.Add(delegate
{
loading.FadeOut(100);
addChannel(channels.Find(c => c.Name == @"#osu"));
});
//addChannel(channels.Find(c => c.Name == @"#lobby"));
//addChannel(channels.Find(c => c.Name == @"#english"));
messageRequest = Scheduler.AddDelayed(() => FetchNewMessages(api), 1000, true);
};
api.Queue(req);
}
private void addChannel(Channel channel)
{
Add(channelDisplay = new ChannelDisplay(channel));
@@ -145,8 +109,58 @@ namespace osu.Game.Overlays
protected override void PopOut()
{
MoveToY(DrawSize.Y, transition_length, EasingTypes.InQuint);
FadeOut(transition_length, EasingTypes.InQuint);
MoveToY(DrawSize.Y, transition_length, EasingTypes.InSine);
FadeOut(transition_length, EasingTypes.InSine);
}
public void APIStateChanged(APIAccess api, APIState state)
{
switch (state)
{
case APIState.Online:
initializeChannels();
break;
default:
messageRequest?.Cancel();
break;
}
}
private void initializeChannels()
{
Clear();
careChannels = new List<Channel>();
//if (api.State != APIAccess.APIState.Online)
// return;
SpriteText loading;
Add(loading = new SpriteText
{
Text = @"Loading available channels...",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
TextSize = 40,
});
messageRequest?.Cancel();
ListChannelsRequest req = new ListChannelsRequest();
req.Success += delegate (List<Channel> channels)
{
Scheduler.Add(delegate
{
loading.FadeOut(100);
addChannel(channels.Find(c => c.Name == @"#osu"));
});
//addChannel(channels.Find(c => c.Name == @"#lobby"));
//addChannel(channels.Find(c => c.Name == @"#english"));
messageRequest = Scheduler.AddDelayed(() => FetchNewMessages(api), 1000, true);
};
api.Queue(req);
}
}
}
+14 -1
View File
@@ -16,6 +16,18 @@ namespace osu.Game.Overlays
public Action<float> SeekRequested;
private bool isDragging;
private bool enabled;
public bool IsEnabled
{
get { return enabled; }
set
{
enabled = value;
if (!enabled)
fill.Width = 0;
}
}
public DragBar()
{
RelativeSizeAxes = Axes.X;
@@ -34,13 +46,14 @@ namespace osu.Game.Overlays
public void UpdatePosition(float position)
{
if (isDragging) return;
if (isDragging || !IsEnabled) return;
fill.Width = position;
}
private void seek(InputState state)
{
if (!IsEnabled) return;
float seekLocation = state.Mouse.Position.X / DrawWidth;
SeekRequested?.Invoke(seekLocation);
fill.Width = seekLocation;
+2 -1
View File
@@ -240,6 +240,7 @@ namespace osu.Game.Overlays
private void workingChanged(object sender = null, EventArgs e = null)
{
progress.IsEnabled = (beatmapSource.Value != null);
if (beatmapSource.Value == current) return;
bool audioEquals = current?.BeatmapInfo.AudioEquals(beatmapSource.Value.BeatmapInfo) ?? false;
current = beatmapSource.Value;
@@ -386,7 +387,7 @@ namespace osu.Game.Overlays
this.beatmap = beatmap;
CacheDrawnFrameBuffer = true;
RelativeSizeAxes = Axes.Both;
Depth = float.MinValue;
Depth = float.MaxValue;
Children = new Drawable[]
{
@@ -1,4 +1,5 @@
using OpenTK;
using System;
using OpenTK;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@@ -10,38 +11,72 @@ using osu.Game.Online.API;
namespace osu.Game.Overlays.Options.General
{
public class LoginOptions : OptionsSubsection
public class LoginOptions : OptionsSubsection, IOnlineComponent
{
private Container loginForm;
protected override string Header => "Sign In";
public LoginOptions()
{
Children = new[]
{
loginForm = new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new[] { new LoadingAnimation() }
}
};
}
private Action performLogout;
protected override string Header => "Sign In";
[BackgroundDependencyLoader(permitNulls: true)]
private void load(APIAccess api)
{
api?.Register(this);
}
public void APIStateChanged(APIAccess api, APIState state)
{
if (api == null)
return;
loginForm.Children = new Drawable[]
switch (state)
{
new LoginForm(api)
};
case APIState.Offline:
Children = new Drawable[]
{
new LoginForm()
};
break;
case APIState.Failing:
Children = new Drawable[]
{
new SpriteText
{
Text = "Connection failing :(",
},
};
break;
case APIState.Connecting:
Children = new Drawable[]
{
new SpriteText
{
Text = "Connecting...",
},
};
break;
case APIState.Online:
Children = new Drawable[]
{
new SpriteText
{
Text = $"Connected as {api.Username}!",
},
new OsuButton
{
RelativeSizeAxes = Axes.X,
Text = "Sign out",
Action = api.Logout
}
};
break;
}
}
class LoginForm : FlowContainer
{
public LoginForm(APIAccess api)
private TextBox username;
private TextBox password;
private APIAccess api;
public LoginForm()
{
Direction = FlowDirection.VerticalOnly;
AutoSizeAxes = Axes.Y;
@@ -51,16 +86,29 @@ namespace osu.Game.Overlays.Options.General
Children = new Drawable[]
{
new SpriteText { Text = "Username" },
new TextBox { Height = 20, RelativeSizeAxes = Axes.X, Text = api?.Username ?? string.Empty },
username = new TextBox { Height = 20, RelativeSizeAxes = Axes.X, Text = api?.Username ?? string.Empty },
new SpriteText { Text = "Password" },
new TextBox { Height = 20, RelativeSizeAxes = Axes.X },
password = new PasswordTextBox { Height = 20, RelativeSizeAxes = Axes.X },
new OsuButton
{
RelativeSizeAxes = Axes.X,
Text = "Log in",
Action = performLogin
}
};
}
private void performLogin()
{
if (!string.IsNullOrEmpty(username.Text) && !string.IsNullOrEmpty(password.Text))
api.Login(username.Text, password.Text);
}
[BackgroundDependencyLoader(permitNulls: true)]
private void load(APIAccess api)
{
this.api = api;
}
}
}
}
+13 -9
View File
@@ -30,8 +30,12 @@ namespace osu.Game.Overlays
{
internal const float CONTENT_MARGINS = 10;
public const float TRANSITION_LENGTH = 600;
public const float SIDEBAR_WIDTH = OptionsSidebar.default_width;
private const float width = 400;
private const float sidebar_width = OptionsSidebar.default_width;
private const float sidebar_padding = 10;
private ScrollContainer scrollContainer;
@@ -71,7 +75,7 @@ namespace osu.Game.Overlays
ScrollDraggerVisible = false,
RelativeSizeAxes = Axes.Y,
Width = width,
Margin = new MarginPadding { Left = sidebar_width },
Margin = new MarginPadding { Left = SIDEBAR_WIDTH },
Children = new[]
{
new FlowContainer
@@ -108,7 +112,7 @@ namespace osu.Game.Overlays
},
sidebar = new OptionsSidebar
{
Width = sidebar_width,
Width = SIDEBAR_WIDTH,
Children = sidebarButtons = sections.Select(section =>
new SidebarButton
{
@@ -175,16 +179,16 @@ namespace osu.Game.Overlays
protected override void PopIn()
{
scrollContainer.MoveToX(0, 600, EasingTypes.OutQuint);
sidebar.MoveToX(0, 800, EasingTypes.OutQuint);
FadeTo(1, 300);
scrollContainer.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint);
sidebar.MoveToX(0, TRANSITION_LENGTH, EasingTypes.OutQuint);
FadeTo(1, TRANSITION_LENGTH / 2);
}
protected override void PopOut()
{
scrollContainer.MoveToX(-width, 600, EasingTypes.OutQuint);
sidebar.MoveToX(-sidebar_width, 600, EasingTypes.OutQuint);
FadeTo(0, 300);
scrollContainer.MoveToX(-width, TRANSITION_LENGTH, EasingTypes.OutQuint);
sidebar.MoveToX(-SIDEBAR_WIDTH, TRANSITION_LENGTH, EasingTypes.OutQuint);
FadeTo(0, TRANSITION_LENGTH / 2);
}
}
}
@@ -2,37 +2,37 @@
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transformations;
using osu.Framework.Input;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Colour;
using osu.Game.Modes;
using osu.Game.Screens.Play;
using osu.Framework.Input;
using osu.Game.Online.API;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays
namespace osu.Game.Overlays.Toolbar
{
public class Toolbar : OverlayContainer
{
private const float height = 50;
public Action OnSettings;
public Action OnHome;
public Action<PlayMode> OnPlayModeChange;
public Action OnMusicController;
private ToolbarModeSelector modeSelector;
private ToolbarButton userButton;
private Box solidBackground;
private Box gradientBackground;
private const int transition_time = 200;
private const int transition_time = 250;
private const float alpha_hovering = 0.8f;
private const float alpha_normal = 0.6f;
protected override void PopIn()
{
@@ -48,23 +48,26 @@ namespace osu.Game.Overlays
protected override bool OnHover(InputState state)
{
gradientBackground.FadeIn(200);
solidBackground.FadeTo(alpha_hovering, transition_time, EasingTypes.OutQuint);
gradientBackground.FadeIn(transition_time, EasingTypes.OutQuint);
return true;
}
protected override void OnHoverLost(InputState state)
{
gradientBackground.FadeOut(200);
solidBackground.FadeTo(alpha_normal, transition_time, EasingTypes.OutQuint);
gradientBackground.FadeOut(transition_time, EasingTypes.OutQuint);
}
public Toolbar()
{
Children = new Drawable[]
{
new Box
solidBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = new Color4(0.1f, 0.1f, 0.1f, 0.6f)
Colour = new Color4(0.1f, 0.1f, 0.1f, 1),
Alpha = alpha_normal,
},
gradientBackground = new Box
{
@@ -81,18 +84,9 @@ namespace osu.Game.Overlays
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{
new ToolbarButton
new ToolbarSettingsButton(),
new ToolbarHomeButton()
{
Icon = FontAwesome.fa_gear,
TooltipMain = "Settings",
TooltipSub = "Change your settings",
Action = () => OnSettings?.Invoke()
},
new ToolbarButton
{
Icon = FontAwesome.fa_home,
TooltipMain = "Home",
TooltipSub = "Return to the main menu",
Action = () => OnHome?.Invoke()
},
modeSelector = new ToolbarModeSelector
@@ -110,19 +104,12 @@ namespace osu.Game.Overlays
AutoSizeAxes = Axes.X,
Children = new []
{
new ToolbarButton
{
Icon = FontAwesome.fa_music,
Action = () => OnMusicController?.Invoke()
},
new ToolbarMusicButton(),
new ToolbarButton
{
Icon = FontAwesome.fa_search
},
userButton = new ToolbarButton
{
Icon = FontAwesome.fa_user,
},
new ToolbarUserButton(),
new ToolbarButton
{
Icon = FontAwesome.fa_bars
@@ -135,12 +122,6 @@ namespace osu.Game.Overlays
Size = new Vector2(1, height);
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
userButton.Text = config.Get<string>(OsuConfig.Username);
}
public void SetGameMode(PlayMode mode) => modeSelector.SetGameMode(mode);
}
}
@@ -4,20 +4,19 @@
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transformations;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework;
using osu.Framework.Graphics.Primitives;
namespace osu.Game.Overlays
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarButton : Container
{
public const float WIDTH = 60;
public FontAwesome Icon
{
get { return DrawableIcon.Icon; }
@@ -58,6 +57,7 @@ namespace osu.Game.Overlays
private FlowContainer tooltipContainer;
private SpriteText tooltip1;
private SpriteText tooltip2;
protected FlowContainer Flow;
public ToolbarButton()
{
@@ -66,16 +66,17 @@ namespace osu.Game.Overlays
HoverBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = new Color4(80, 80, 80, 180),
BlendingMode = BlendingMode.Additive,
Colour = new Color4(60, 60, 60, 255),
Alpha = 0,
},
new FlowContainer
Flow = new FlowContainer
{
Direction = FlowDirection.HorizontalOnly,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Padding = new MarginPadding { Left = 5, Right = 5 },
Padding = new MarginPadding { Left = 15, Right = 15 },
Spacing = new Vector2(5),
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Children = new Drawable[]
@@ -87,7 +88,6 @@ namespace osu.Game.Overlays
},
DrawableText = new SpriteText
{
Margin = new MarginPadding { Left = 5 },
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
},
@@ -118,7 +118,6 @@ namespace osu.Game.Overlays
};
RelativeSizeAxes = Axes.Y;
Size = new Vector2(WIDTH, 1);
}
protected override void Update()
@@ -126,7 +125,7 @@ namespace osu.Game.Overlays
base.Update();
//todo: find a way to avoid using this (autosize needs to be able to ignore certain drawables.. in this case the tooltip)
Size = new Vector2(WIDTH + (DrawableText.IsVisible ? DrawableText.DrawSize.X : 0), 1);
Size = new Vector2(Flow.DrawSize.X, 1);
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
@@ -134,21 +133,44 @@ namespace osu.Game.Overlays
protected override bool OnClick(InputState state)
{
Action?.Invoke();
HoverBackground.FlashColour(Color4.White, 400);
HoverBackground.FlashColour(new Color4(255, 255, 255, 100), 500, EasingTypes.OutQuint);
return true;
}
protected override bool OnHover(InputState state)
{
HoverBackground.FadeTo(0.4f, 200);
HoverBackground.FadeIn(200);
tooltipContainer.FadeIn(100);
return false;
}
protected override void OnHoverLost(InputState state)
{
HoverBackground.FadeTo(0, 200);
HoverBackground.FadeOut(200);
tooltipContainer.FadeOut(100);
}
}
public class OpaqueBackground : Container
{
public OpaqueBackground()
{
RelativeSizeAxes = Axes.Both;
Masking = true;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = new Color4(30, 30, 30, 255)
},
new Triangles
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.05f,
},
};
}
}
}
@@ -0,0 +1,19 @@
//Copyright (c) 2007-2016 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.Game.Graphics;
using osu.Game.Screens.Menu;
namespace osu.Game.Overlays.Toolbar
{
class ToolbarHomeButton : ToolbarButton
{
public ToolbarHomeButton()
{
Icon = FontAwesome.fa_home;
TooltipMain = "Home";
TooltipSub = "Return to the main menu";
}
}
}
@@ -2,14 +2,12 @@
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Extensions;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using OpenTK.Graphics;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Game.Modes;
using osu.Game.Screens.Play;
using OpenTK.Graphics;
namespace osu.Game.Overlays
namespace osu.Game.Overlays.Toolbar
{
public class ToolbarModeButton : ToolbarButton
{
@@ -30,7 +28,23 @@ namespace osu.Game.Overlays
{
set
{
//Background.Colour = value ? new Color4(100, 100, 100, 255) : new Color4(20, 20, 20, 255);
if (value)
{
DrawableIcon.Colour = Color4.White;
DrawableIcon.Masking = true;
DrawableIcon.EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Glow,
Colour = new Color4(255, 194, 224, 100),
Radius = 15,
Roundness = 15,
};
}
else
{
DrawableIcon.Masking = false;
DrawableIcon.Colour = new Color4(255, 194, 224, 255);
}
}
}
@@ -3,26 +3,25 @@
using System;
using System.Linq;
using osu.Framework.Caching;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transformations;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Modes;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework;
using osu.Framework.Caching;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Allocation;
using osu.Game.Modes;
using osu.Game.Screens.Play;
namespace osu.Game.Overlays
namespace osu.Game.Overlays.Toolbar
{
class ToolbarModeSelector : Container
{
const float padding = 10;
private FlowContainer modeButtons;
private Box modeButtonLine;
private Drawable modeButtonLine;
private ToolbarModeButton activeButton;
public Action<PlayMode> OnPlayModeChange;
@@ -33,11 +32,7 @@ namespace osu.Game.Overlays
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = new Color4(20, 20, 20, 255)
},
new OpaqueBackground(),
modeButtons = new FlowContainer
{
RelativeSizeAxes = Axes.Y,
@@ -45,14 +40,29 @@ namespace osu.Game.Overlays
Direction = FlowDirection.HorizontalOnly,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Padding = new MarginPadding { Left = 10, Right = 10 },
},
modeButtonLine = new Box
modeButtonLine = new Container
{
RelativeSizeAxes = Axes.X,
Size = new Vector2(0.3f, 3),
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopCentre,
Colour = Color4.White
Origin = Anchor.TopLeft,
Masking = true,
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Glow,
Colour = new Color4(255, 194, 224, 100),
Radius = 15,
Roundness = 15,
},
Children = new []
{
new Box
{
RelativeSizeAxes = Axes.Both,
}
}
}
};
@@ -72,9 +82,13 @@ namespace osu.Game.Overlays
}
});
}
}
// We need to set the size within LoadComplete, because
Size = new Vector2(amountButtons * ToolbarButton.WIDTH + padding * 2, 1);
protected override void Update()
{
base.Update();
Size = new Vector2(modeButtons.DrawSize.X, 1);
}
public void SetGameMode(PlayMode mode)
@@ -97,7 +111,7 @@ namespace osu.Game.Overlays
base.UpdateLayout();
if (!activeMode.EnsureValid())
activeMode.Refresh(() => modeButtonLine.MoveToX(activeButton.DrawPosition.X + activeButton.DrawSize.X / 2 + padding, 200, EasingTypes.OutQuint));
activeMode.Refresh(() => modeButtonLine.MoveToX(activeButton.DrawPosition.X, 200, EasingTypes.OutQuint));
}
}
}
@@ -0,0 +1,23 @@
//Copyright (c) 2007-2016 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.Game.Graphics;
namespace osu.Game.Overlays.Toolbar
{
class ToolbarMusicButton : ToolbarOverlayToggleButton
{
public ToolbarMusicButton()
{
Icon = FontAwesome.fa_music;
}
[BackgroundDependencyLoader]
private void load(MusicController music)
{
StateContainer = music;
Action = music.ToggleVisibility;
}
}
}
@@ -0,0 +1,71 @@
//Copyright (c) 2007-2016 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.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Configuration;
using osu.Game.Online.API;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays.Toolbar
{
class ToolbarOverlayToggleButton : ToolbarButton
{
private Box StateBackground;
private OverlayContainer stateContainer;
public OverlayContainer StateContainer
{
get { return stateContainer; }
set
{
stateContainer = value;
stateContainer.StateChanged += stateChanged;
}
}
public ToolbarOverlayToggleButton()
{
Add(StateBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = new Color4(150, 150, 150, 180),
BlendingMode = BlendingMode.Additive,
Depth = 2,
Alpha = 0,
});
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (stateContainer != null)
stateContainer.StateChanged -= stateChanged;
}
private void stateChanged(OverlayContainer c, Visibility state)
{
switch (state)
{
case Visibility.Hidden:
StateBackground.FadeOut(200);
break;
case Visibility.Visible:
StateBackground.FadeIn(200);
break;
}
}
}
}
@@ -0,0 +1,25 @@
//Copyright (c) 2007-2016 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.Game.Graphics;
namespace osu.Game.Overlays.Toolbar
{
class ToolbarSettingsButton : ToolbarOverlayToggleButton
{
public ToolbarSettingsButton()
{
Icon = FontAwesome.fa_gear;
TooltipMain = "Settings";
TooltipSub = "Change your settings";
}
[BackgroundDependencyLoader]
private void load(OptionsOverlay options)
{
StateContainer = options;
Action = options.ToggleVisibility;
}
}
}
@@ -0,0 +1,140 @@
//Copyright (c) 2007-2016 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.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Configuration;
using osu.Game.Online.API;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays.Toolbar
{
class ToolbarUserButton : ToolbarButton, IOnlineComponent
{
private Avatar avatar;
public ToolbarUserButton()
{
DrawableText.Font = @"Exo2.0-MediumItalic";
Add(new OpaqueBackground { Depth = 1 });
Flow.Add(avatar = new Avatar());
}
[BackgroundDependencyLoader]
private void load(APIAccess api, OsuConfigManager config)
{
api.Register(this);
}
public void APIStateChanged(APIAccess api, APIState state)
{
switch (state)
{
default:
Text = @"Guest";
avatar.UserId = 1;
break;
case APIState.Online:
Text = api.Username;
avatar.UserId = api.LocalUser.Value.Id;
break;
}
}
public class Avatar : Container
{
public Drawable Sprite;
private int userId;
private OsuGame game;
private Texture guestTexture;
public Avatar()
{
Size = new Vector2(32);
Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft;
CornerRadius = Size.X / 8;
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Radius = 4,
Colour = new Color4(0, 0, 0, 0.1f),
};
Masking = true;
}
[BackgroundDependencyLoader]
private void load(OsuGame game, TextureStore textures)
{
this.game = game;
guestTexture = textures.Get(@"Online/avatar-guest@2x");
}
public int UserId
{
get { return userId; }
set
{
if (userId == value)
return;
userId = value;
Sprite newSprite;
if (userId > 1)
newSprite = new OnlineSprite($@"https://a.ppy.sh/{userId}");
else
newSprite = new Sprite { Texture = guestTexture };
newSprite.FillMode = FillMode.Fit;
newSprite.Preload(game, s =>
{
Sprite?.FadeOut();
Sprite?.Expire();
Sprite = s;
Add(s);
s.FadeInFromZero(200);
});
}
}
public class OnlineSprite : Sprite
{
private readonly string url;
private readonly int userId;
public OnlineSprite(string url)
{
Debug.Assert(url != null);
this.url = url;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
Texture = textures.Get(url);
}
}
}
}
}
@@ -39,7 +39,7 @@ namespace osu.Game.Screens.Backgrounds
float newDepth = 0;
if (background != null)
{
newDepth = background.Depth - 1;
newDepth = background.Depth + 1;
background.Flush();
background.FadeOut(250);
background.Expire();
+3 -6
View File
@@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transformations;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Screens.Backgrounds;
using osu.Game.Graphics.UserInterface;
using OpenTK;
using OpenTK.Graphics;
@@ -17,7 +18,7 @@ namespace osu.Game.Screens
{
public class GameModeWhiteBox : OsuGameMode
{
private Button popButton;
private BackButton popButton;
const int transition_time = 1000;
@@ -113,14 +114,10 @@ namespace osu.Game.Screens
},
}
},
popButton = new Button
popButton = new BackButton
{
Text = @"Back",
RelativeSizeAxes = Axes.X,
Size = new Vector2(0.1f, 40),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Colour = new Color4(235, 51, 153, 255),
Alpha = 0,
Action = delegate {
Exit();
+21 -3
View File
@@ -19,7 +19,7 @@ namespace osu.Game.Screens.Menu
public class Button : Container, IStateful<ButtonState>
{
private Container iconText;
private Box box;
private Container box;
private Color4 colour;
private TextAwesome icon;
private string internalName;
@@ -51,15 +51,31 @@ namespace osu.Game.Screens.Menu
Children = new Drawable[]
{
box = new Box
box = new Container
{
Masking = true,
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = new Color4(0, 0, 0, 0.2f),
Roundness = 5,
Radius = 8,
},
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = colour,
Scale = new Vector2(0, 1),
Size = boxSize,
Shear = new Vector2(ButtonSystem.wedge_width / boxSize.Y, 0),
EdgeSmoothness = new Vector2(2, 0),
Children = new Drawable[]
{
new Box
{
EdgeSmoothness = new Vector2(2, 0),
RelativeSizeAxes = Axes.Both,
},
}
},
iconText = new Container
{
@@ -71,6 +87,7 @@ namespace osu.Game.Screens.Menu
{
icon = new TextAwesome
{
Shadow = true,
Anchor = Anchor.Centre,
TextSize = 30,
Position = new Vector2(0, 0),
@@ -78,6 +95,7 @@ namespace osu.Game.Screens.Menu
},
new SpriteText
{
Shadow = true,
Direction = FlowDirection.HorizontalOnly,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -1,6 +1,8 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
using System.Collections.Generic;
using System.Linq;
namespace osu.Game.Screens.Menu
{
@@ -15,6 +17,10 @@ namespace osu.Game.Screens.Menu
/// </summary>
public Drawable CentreTarget;
protected override IComparer<Drawable> DepthComparer => new ReverseCreationOrderDepthComparer();
protected override IEnumerable<Drawable> SortedChildren => base.SortedChildren.Reverse();
public override Anchor Origin => Anchor.Custom;
public override Vector2 OriginPosition
+15 -6
View File
@@ -8,6 +8,7 @@ using osu.Framework.Audio.Track;
using osu.Framework.GameModes;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Transformations;
using osu.Game.Graphics.Containers;
using osu.Game.Screens.Backgrounds;
using OpenTK.Graphics;
@@ -34,13 +35,21 @@ namespace osu.Game.Screens.Menu
{
Children = new Drawable[]
{
logo = new OsuLogo()
new ParallaxContainer
{
Alpha = 0,
BlendingMode = BlendingMode.Additive,
Interactive = false,
Colour = Color4.DarkGray,
Ripple = false
ParallaxAmount = 0.01f,
Children = new Drawable[]
{
logo = new OsuLogo
{
Alpha = 0,
Triangles = false,
BlendingMode = BlendingMode.Additive,
Interactive = false,
Colour = Color4.DarkGray,
Ripple = false
}
}
}
};
}
+111 -33
View File
@@ -2,14 +2,19 @@
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Transformations;
using osu.Framework.Input;
using osu.Framework.MathUtils;
using osu.Game.Graphics.Backgrounds;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Screens.Menu
{
@@ -21,16 +26,27 @@ namespace osu.Game.Screens.Menu
private Sprite logo;
private CircularContainer logoContainer;
private Container logoBounceContainer;
private Container logoHoverContainer;
private MenuVisualisation vis;
private Container colourAndTriangles;
public Action Action;
public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * 0.8f;
public float SizeForFlow => logo == null ? 0 : logo.DrawSize.X * logo.Scale.X * logoBounceContainer.Scale.X * logoHoverContainer.Scale.X * 0.78f;
private Sprite ripple;
private Container rippleContainer;
public bool Triangles
{
set
{
colourAndTriangles.Alpha = value ? 1 : 0;
}
}
public override bool Contains(Vector2 screenSpacePos)
{
return logoContainer.Contains(screenSpacePos);
@@ -61,41 +77,77 @@ namespace osu.Game.Screens.Menu
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
logoContainer = new CircularContainer
logoHoverContainer = new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Children = new[]
{
logo = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
},
},
rippleContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
ripple = new Sprite()
new BufferedContainer
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
logoContainer = new CircularContainer
{
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Scale = new Vector2(0.8f),
Children = new Drawable[]
{
colourAndTriangles = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = new Color4(233, 103, 161, 255),
},
new OsuLogoTriangles
{
RelativeSizeAxes = Axes.Both,
},
}
},
},
},
logo = new Sprite
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(0.5f),
},
}
},
rippleContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
ripple = new Sprite()
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
BlendingMode = BlendingMode.Additive,
Scale = new Vector2(0.5f),
Alpha = 0.15f
}
}
},
vis = new MenuVisualisation
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = logo.Size,
BlendingMode = BlendingMode.Additive,
Alpha = 0.05f
Alpha = 0.2f,
}
}
},
vis = new MenuVisualisation
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = logo.Size,
BlendingMode = BlendingMode.Additive,
Alpha = 0.2f,
}
}
}
@@ -105,15 +157,15 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
logo.Texture = textures.Get(@"Menu/logo");
ripple.Texture = textures.Get(@"Menu/logo");
logo.Texture = textures.Get(@"Menu/logo@2x");
ripple.Texture = textures.Get(@"Menu/logo@2x");
}
protected override void LoadComplete()
{
base.LoadComplete();
ripple.ScaleTo(1.1f, 500);
ripple.ScaleTo(ripple.Scale * 1.1f, 500);
ripple.FadeOut(500);
ripple.Loop(300);
}
@@ -122,14 +174,14 @@ namespace osu.Game.Screens.Menu
{
if (!Interactive) return false;
logoBounceContainer.ScaleTo(1.1f, 1000, EasingTypes.Out);
logoBounceContainer.ScaleTo(0.9f, 1000, EasingTypes.Out);
return true;
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
logoBounceContainer.ScaleTo(1.2f, 500, EasingTypes.OutElastic);
logoBounceContainer.ScaleTo(1f, 500, EasingTypes.OutElastic);
return true;
}
@@ -144,13 +196,39 @@ namespace osu.Game.Screens.Menu
protected override bool OnHover(InputState state)
{
if (!Interactive) return false;
logoBounceContainer.ScaleTo(1.2f, 500, EasingTypes.OutElastic);
logoHoverContainer.ScaleTo(1.2f, 500, EasingTypes.OutElastic);
return true;
}
protected override void OnHoverLost(InputState state)
{
logoBounceContainer.ScaleTo(1, 500, EasingTypes.OutElastic);
logoHoverContainer.ScaleTo(1, 500, EasingTypes.OutElastic);
}
class OsuLogoTriangles : Triangles
{
public OsuLogoTriangles()
{
TriangleScale = 4;
Alpha = 1;
}
protected override Triangle CreateTriangle()
{
var triangle = base.CreateTriangle();
triangle.Alpha = 1;
triangle.Colour = getTriangleShade();
return triangle;
}
private Color4 getTriangleShade()
{
float val = RNG.NextSingle();
return Interpolation.ValueAt(val,
new Color4(222, 91, 149, 255),
new Color4(255, 125, 183, 255),
0, 1);
}
}
}
}
+1 -1
View File
@@ -111,7 +111,7 @@ namespace osu.Game.Screens
{
AddInternal(new ParallaxContainer
{
Depth = float.MinValue,
Depth = float.MaxValue,
Children = new[]
{
Background = bg
+37 -15
View File
@@ -1,6 +1,7 @@
//Copyright (c) 2007-2016 ppy Pty Ltd <contact@ppy.sh>.
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
@@ -18,6 +19,8 @@ using OpenTK.Input;
using MouseState = osu.Framework.Input.MouseState;
using OpenTK;
using osu.Framework.GameModes;
using osu.Game.Modes.UI;
using osu.Game.Screens.Ranking;
namespace osu.Game.Screens.Play
{
@@ -37,6 +40,9 @@ namespace osu.Game.Screens.Play
private Ruleset ruleset;
private ScoreProcessor scoreProcessor;
private HitRenderer hitRenderer;
[BackgroundDependencyLoader]
private void load(AudioManager audio, BeatmapDatabase beatmaps, OsuGameBase game)
{
@@ -83,13 +89,15 @@ namespace osu.Game.Screens.Play
ruleset = Ruleset.GetRuleset(usablePlayMode);
var scoreOverlay = ruleset.CreateScoreOverlay();
var hitRenderer = ruleset.CreateHitRendererWith(beatmap.HitObjects);
scoreOverlay.BindProcessor(scoreProcessor = ruleset.CreateScoreProcessor());
hitRenderer.OnHit += delegate (HitObject h) { scoreOverlay.OnHit(h); };
hitRenderer.OnMiss += delegate (HitObject h) { scoreOverlay.OnMiss(h); };
hitRenderer = ruleset.CreateHitRendererWith(beatmap.HitObjects);
hitRenderer.OnJudgement += scoreProcessor.AddJudgement;
hitRenderer.OnAllJudged += hitRenderer_OnAllJudged;
if (Autoplay)
hitRenderer.Schedule(() => hitRenderer.DrawableObjects.ForEach(h => h.State = ArmedState.Armed));
hitRenderer.Schedule(() => hitRenderer.DrawableObjects.ForEach(h => h.State = ArmedState.Hit));
Children = new Drawable[]
{
@@ -105,6 +113,18 @@ namespace osu.Game.Screens.Play
};
}
private void hitRenderer_OnAllJudged()
{
Delay(1000);
Schedule(delegate
{
Push(new Results
{
Score = scoreProcessor.GetScore()
});
});
}
protected override void OnEntering(GameMode last)
{
base.OnEntering(last);
@@ -125,26 +145,28 @@ namespace osu.Game.Screens.Play
{
}
bool leftViaKeyboard;
bool rightViaKeyboard;
protected override void TransformState(InputState state)
{
base.TransformState(state);
MouseState mouse = (MouseState)state.Mouse;
foreach (Key k in state.Keyboard.Keys)
if (state.Keyboard != null)
{
switch (k)
{
case Key.Z:
mouse.ButtonStates.Find(s => s.Button == MouseButton.Left).State = true;
break;
case Key.X:
mouse.ButtonStates.Find(s => s.Button == MouseButton.Right).State = true;
break;
}
leftViaKeyboard = state.Keyboard.Keys.Contains(Key.Z);
rightViaKeyboard = state.Keyboard.Keys.Contains(Key.X);
}
if (state.Mouse != null)
{
if (leftViaKeyboard) mouse.ButtonStates.Find(s => s.Button == MouseButton.Left).State = true;
if (rightViaKeyboard) mouse.ButtonStates.Find(s => s.Button == MouseButton.Right).State = true;
}
}
}
}
}
}
+71 -3
View File
@@ -2,19 +2,29 @@
//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.GameModes;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transformations;
using osu.Game.Modes;
using osu.Game.Screens.Backgrounds;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Screens.Ranking
{
class Results : GameModeWhiteBox
class Results : OsuGameMode
{
protected override BackgroundMode CreateBackground() => new BackgroundModeCustom(@"Backgrounds/bg4");
protected override BackgroundMode CreateBackground() => new BackgroundModeBeatmap(Beatmap);
private static readonly Vector2 BACKGROUND_BLUR = new Vector2(20);
ScoreDisplay scoreDisplay;
protected override void OnEntering(GameMode last)
{
base.OnEntering(last);
Background.Schedule(() => Background.FadeColour(Color4.DarkGray, 500));
Background.Schedule(() => (Background as BackgroundModeBeatmap)?.BlurTo(BACKGROUND_BLUR, 1000));
}
protected override bool OnExiting(GameMode next)
@@ -22,5 +32,63 @@ namespace osu.Game.Screens.Ranking
Background.Schedule(() => Background.FadeColour(Color4.White, 500));
return base.OnExiting(next);
}
public Score Score
{
set
{
scoreDisplay?.FadeOut(500);
scoreDisplay?.Expire();
scoreDisplay = new ScoreDisplay(value)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
Add(scoreDisplay);
scoreDisplay.FadeIn(500);
scoreDisplay.ScaleTo(0.1f);
scoreDisplay.ScaleTo(1, 1000, EasingTypes.OutElastic);
scoreDisplay.RotateTo(360 * 5, 1000, EasingTypes.OutElastic);
}
}
}
class ScoreDisplay : Container
{
public ScoreDisplay(Score s)
{
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
new FlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FlowDirection.VerticalOnly,
Children = new Drawable[]
{
new SpriteText
{
TextSize = 40,
Text = $@"Accuracy: {s.Accuracy:#0.00%}",
},
new SpriteText
{
TextSize = 40,
Text = $@"Score: {s.TotalScore}",
},
new SpriteText
{
TextSize = 40,
Text = $@"MaxCombo: {s.MaxCombo}",
}
}
}
};
}
}
}
@@ -1,4 +1,6 @@
using System;
using osu.Framework;
using osu.Framework.Allocation;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics;
@@ -8,31 +10,52 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Framework.Graphics.Colour;
using osu.Game.Beatmaps.Drawables;
namespace osu.Game.Screens.Select
{
class BeatmapInfoOverlay : Container
class BeatmapInfoWedge : Container
{
private static readonly Vector2 wedged_container_shear = new Vector2(0.15f, 0);
private Container beatmapInfoContainer;
private BaseGame game;
public BeatmapInfoWedge()
{
Shear = wedged_container_shear;
Masking = true;
BorderColour = new Color4(221, 255, 255, 255);
BorderThickness = 2.5f;
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Glow,
Colour = new Color4(130, 204, 255, 150),
Radius = 20,
Roundness = 15,
};
}
[BackgroundDependencyLoader]
private void load(BaseGame game)
{
this.game = game;
}
public void UpdateBeatmap(WorkingBeatmap beatmap)
{
if (beatmap == null)
return;
float newDepth = 0;
if (beatmapInfoContainer != null)
{
newDepth = beatmapInfoContainer.Depth - 1;
beatmapInfoContainer.FadeOut(250);
beatmapInfoContainer.Expire();
}
var lastContainer = beatmapInfoContainer;
FadeIn(250);
float newDepth = lastContainer?.Depth + 1 ?? 0;
BeatmapSetInfo beatmapSetInfo = beatmap.BeatmapSetInfo;
BeatmapInfo beatmapInfo = beatmap.BeatmapInfo;
Add(beatmapInfoContainer = new BufferedContainer
(beatmapInfoContainer = new BufferedContainer
{
Depth = newDepth,
PixelSnapping = true,
@@ -51,18 +74,17 @@ namespace osu.Game.Screens.Select
},
// We use a container, such that we can set the colour gradient to go across the
// vertices of the masked container instead of the vertices of the (larger) sprite.
beatmap.Background == null ? new Container() : new Container
new Container
{
RelativeSizeAxes = Axes.Both,
ColourInfo = ColourInfo.GradientVertical(Color4.White, new Color4(1f, 1f, 1f, 0.3f)),
Children = new []
{
// Zoomed-in and cropped beatmap background
new Sprite
new BeatmapBackgroundSprite(beatmap)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Texture = beatmap.Background,
FillMode = FillMode.Fill,
},
},
@@ -117,6 +139,14 @@ namespace osu.Game.Screens.Select
}
}
}
}).Preload(game, delegate(Drawable d)
{
FadeIn(250);
lastContainer?.FadeOut(250);
lastContainer?.Expire();
Add(d);
});
}
}
+84 -39
View File
@@ -10,39 +10,85 @@ using osu.Game.Database;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Lists;
using osu.Game.Beatmaps.Drawables;
using osu.Framework.Timing;
namespace osu.Game.Screens.Select
{
class CarouselContainer : ScrollContainer
{
private Container<Panel> scrollableContent;
private List<BeatmapGroup> groups = new List<BeatmapGroup>();
private List<Panel> panels = new List<Panel>();
public BeatmapGroup SelectedGroup { get; private set; }
public BeatmapPanel SelectedPanel { get; private set; }
private List<float> yPositions = new List<float>();
public CarouselContainer()
{
DistanceDecayJump = 0.01;
Add(scrollableContent = new Container<Panel>
private List<float> yPositions = new List<float>();
private CarouselLifetimeList<Panel> Lifetime;
public CarouselContainer()
{
DistanceDecayJump = 0.01;
Add(scrollableContent = new Container<Panel>(Lifetime = new CarouselLifetimeList<Panel>(DepthComparer))
{
RelativeSizeAxes = Axes.X,
});
}
internal class CarouselLifetimeList<T> : LifetimeList<Panel>
{
public CarouselLifetimeList(IComparer<Panel> comparer)
: base(comparer)
{
}
public int StartIndex;
public int EndIndex;
public override bool Update(FrameTimeInfo time)
{
RelativeSizeAxes = Axes.X,
});
}
bool anyAliveChanged = false;
//check existing items to make sure they haven't died.
foreach (var item in AliveItems.ToArray())
{
item.UpdateTime(time);
if (!item.IsAlive)
{
//todo: make this more efficient
int i = IndexOf(item);
anyAliveChanged |= CheckItem(item, ref i);
}
}
//handle custom range
for (int i = StartIndex; i < EndIndex; i++)
{
var item = this[i];
item.UpdateTime(time);
anyAliveChanged |= CheckItem(item, ref i);
}
return anyAliveChanged;
}
}
public void AddGroup(BeatmapGroup group)
{
group.State = BeatmapGroupState.Collapsed;
groups.Add(group);
panels.Add(group.Header);
group.Header.Depth = -scrollableContent.Children.Count();
scrollableContent.Add(group.Header);
foreach (BeatmapPanel panel in group.BeatmapPanels)
panels.Add(panel);
{
panel.Depth = -scrollableContent.Children.Count();
scrollableContent.Add(panel);
}
computeYPositions();
}
@@ -74,12 +120,19 @@ namespace osu.Game.Screens.Select
if (group.State == BeatmapGroupState.Expanded)
{
group.Header.MoveToX(-100, 500, EasingTypes.OutExpo);
var headerY = group.Header.Position.Y;
foreach (BeatmapPanel panel in group.BeatmapPanels)
{
if (panel == SelectedPanel)
selectedY = currentY + panel.DrawHeight / 2 - DrawHeight / 2;
panel.MoveToX(-50, 500, EasingTypes.OutExpo);
//on first display we want to begin hidden under our group's header.
if (panel.Alpha == 0)
panel.MoveToY(headerY);
movePanel(panel, true, ref currentY);
}
}
@@ -88,7 +141,10 @@ namespace osu.Game.Screens.Select
group.Header.MoveToX(0, 500, EasingTypes.OutExpo);
foreach (BeatmapPanel panel in group.BeatmapPanels)
{
panel.MoveToX(0, 500, EasingTypes.OutExpo);
movePanel(panel, false, ref currentY);
}
}
}
@@ -114,11 +170,7 @@ namespace osu.Game.Screens.Select
public void SelectGroup(BeatmapGroup group, BeatmapPanel panel)
{
if (SelectedGroup != null && SelectedGroup != group)
{
SelectedGroup.State = BeatmapGroupState.Collapsed;
foreach (BeatmapPanel p in group.BeatmapPanels)
p.MoveToY(group.Header.Position.Y);
}
SelectedGroup = group;
panel.State = PanelSelectedState.Selected;
@@ -128,7 +180,7 @@ namespace osu.Game.Screens.Select
ScrollTo(selectedY);
}
private static float offsetX(Panel panel, float dist, float halfHeight)
private static float offsetX(float dist, float halfHeight)
{
// The radius of the circle the carousel moves on.
const float CIRCLE_RADIUS = 4;
@@ -138,28 +190,16 @@ namespace osu.Game.Screens.Select
return 125 + x;
}
private void addPanel(int index)
{
Panel panel = panels[index];
if (panel.Hidden)
return;
if (!scrollableContent.Contains(panel))
{
panel.Depth = index + (panel is BeatmapSetHeader ? panels.Count : 0);
scrollableContent.Add(panel);
}
}
protected override void Update()
{
base.Update();
float drawHeight = DrawHeight;
scrollableContent.RemoveAll(delegate (Panel p)
Lifetime.AliveItems.ForEach(delegate (Panel p)
{
float panelPosY = p.Position.Y;
return panelPosY < Current - p.DrawHeight || panelPosY > Current + drawHeight || !IsVisible;
p.IsOnScreen = panelPosY >= Current - p.DrawHeight && panelPosY <= Current + drawHeight;
});
int firstIndex = yPositions.BinarySearch(Current - Panel.MAX_HEIGHT);
@@ -167,19 +207,24 @@ namespace osu.Game.Screens.Select
int lastIndex = yPositions.BinarySearch(Current + drawHeight);
if (lastIndex < 0) lastIndex = ~lastIndex;
for (int i = firstIndex; i < lastIndex; ++i)
addPanel(i);
Lifetime.StartIndex = firstIndex;
Lifetime.EndIndex = lastIndex;
float halfHeight = drawHeight / 2;
foreach (Panel panel in scrollableContent.Children)
for (int i = firstIndex; i < lastIndex; ++i)
{
var panel = Lifetime[i];
panel.IsOnScreen = true;
float panelDrawY = panel.Position.Y - Current + panel.DrawHeight / 2;
float dist = Math.Abs(1f - panelDrawY / halfHeight);
// Setting the origin position serves as an additive position on top of potential
// local transformation we may want to apply (e.g. when a panel gets selected, we
// may want to smoothly transform it leftwards.)
panel.OriginPosition = new Vector2(-offsetX(panel, dist, halfHeight), 0);
panel.OriginPosition = new Vector2(-offsetX(dist, halfHeight), 0);
// We are applying a multiplicative alpha (which is internally done by nesting an
// additional container and setting that container's alpha) such that we can
+12 -19
View File
@@ -19,6 +19,7 @@ using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Modes;
using osu.Game.Screens.Backgrounds;
using osu.Game.Graphics.UserInterface;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Screens.Play;
@@ -38,9 +39,8 @@ namespace osu.Game.Screens.Select
private TrackManager trackManager;
private static readonly Vector2 wedged_container_size = new Vector2(0.5f, 225);
private static readonly Vector2 wedged_container_shear = new Vector2(0.15f, 0);
private static readonly Vector2 wedged_container_start_position = new Vector2(0, 50);
private BeatmapInfoOverlay wedgedBeatmapInfoOverlay;
private BeatmapInfoWedge beatmapInfoWedge;
private static readonly Vector2 BACKGROUND_BLUR = new Vector2(20);
private CancellationTokenSource initialAddSetsTask;
@@ -73,11 +73,8 @@ namespace osu.Game.Screens.Select
}
}
/// <param name="database">Optionally provide a database to use instead of the OsuGame one.</param>
public PlaySongSelect(BeatmapDatabase database = null)
public PlaySongSelect()
{
this.database = database;
const float carouselWidth = 640;
const float bottomToolHeight = 50;
Children = new Drawable[]
@@ -102,24 +99,13 @@ namespace osu.Game.Screens.Select
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
},
wedgedBeatmapInfoOverlay = new BeatmapInfoOverlay
beatmapInfoWedge = new BeatmapInfoWedge
{
Alpha = 0,
Position = wedged_container_start_position,
Size = wedged_container_size,
RelativeSizeAxes = Axes.X,
Shear = wedged_container_shear,
Margin = new MarginPadding { Top = 20, Right = 20, },
Masking = true,
BorderColour = new Color4(221, 255, 255, 255),
BorderThickness = 2.5f,
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Glow,
Colour = new Color4(130, 204, 255, 150),
Radius = 20,
Roundness = 15,
},
},
new Container
{
@@ -135,6 +121,13 @@ namespace osu.Game.Screens.Select
Size = Vector2.One,
Colour = new Color4(0, 0, 0, 0.5f),
},
new BackButton
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
//RelativeSizeAxes = Axes.Y,
Action = () => Exit()
},
new Button
{
Anchor = Anchor.CentreRight,
@@ -239,7 +232,7 @@ namespace osu.Game.Screens.Select
(Background as BackgroundModeBeatmap)?.BlurTo(BACKGROUND_BLUR, 1000);
}
wedgedBeatmapInfoOverlay.UpdateBeatmap(beatmap);
beatmapInfoWedge.UpdateBeatmap(beatmap);
}
/// <summary>
+21 -9
View File
@@ -63,8 +63,15 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Modes\HitJudgementResolver.cs" />
<Compile Include="Beatmaps\Drawables\BeatmapBackgroundSprite.cs" />
<Compile Include="Graphics\Backgrounds\Triangles.cs" />
<Compile Include="Graphics\Cursor\CursorTrail.cs" />
<Compile Include="Graphics\UserInterface\BackButton.cs" />
<Compile Include="Modes\Objects\HitObjectParser.cs" />
<Compile Include="Modes\Score.cs" />
<Compile Include="Modes\ScoreProcesssor.cs" />
<Compile Include="Online\API\IOnlineComponent.cs" />
<Compile Include="Online\API\Requests\GetUserRequest.cs" />
<Compile Include="Overlays\DragBar.cs" />
<Compile Include="Overlays\MusicController.cs" />
<Compile Include="Beatmaps\Beatmap.cs" />
@@ -85,6 +92,11 @@
<Compile Include="Beatmaps\Timing\SampleChange.cs" />
<Compile Include="Beatmaps\Timing\TimingChange.cs" />
<Compile Include="Configuration\OsuConfigManager.cs" />
<Compile Include="Overlays\Toolbar\ToolbarHomeButton.cs" />
<Compile Include="Overlays\Toolbar\ToolbarMusicButton.cs" />
<Compile Include="Overlays\Toolbar\ToolbarSettingsButton.cs" />
<Compile Include="Overlays\Toolbar\ToolbarOverlayToggleButton.cs" />
<Compile Include="Overlays\Toolbar\ToolbarUserButton.cs" />
<Compile Include="Screens\BackgroundMode.cs" />
<Compile Include="Screens\Backgrounds\BackgroundModeBeatmap.cs" />
<Compile Include="Screens\Backgrounds\BackgroundModeCustom.cs" />
@@ -146,20 +158,20 @@
<Compile Include="Online\API\SecurePassword.cs" />
<Compile Include="Online\API\Requests\ListChannels.cs" />
<Compile Include="Online\Chat\Channel.cs" />
<Compile Include="Online\Chat\Display\ChannelDisplay.cs" />
<Compile Include="Online\Chat\Display\ChatLine.cs" />
<Compile Include="Online\Chat\Drawables\ChannelDisplay.cs" />
<Compile Include="Online\Chat\Drawables\ChatLine.cs" />
<Compile Include="Online\Chat\Message.cs" />
<Compile Include="Online\User.cs" />
<Compile Include="OsuGame.cs" />
<Compile Include="OsuGameBase.cs" />
<Compile Include="Overlays\ChatConsole.cs" />
<Compile Include="Overlays\ChatOverlay.cs" />
<Compile Include="Overlays\OptionsOverlay.cs" />
<Compile Include="Overlays\Toolbar.cs" />
<Compile Include="Overlays\ToolbarButton.cs" />
<Compile Include="Overlays\ToolbarModeButton.cs" />
<Compile Include="Overlays\ToolbarModeSelector.cs" />
<Compile Include="Overlays\Toolbar\Toolbar.cs" />
<Compile Include="Overlays\Toolbar\ToolbarButton.cs" />
<Compile Include="Overlays\Toolbar\ToolbarModeButton.cs" />
<Compile Include="Overlays\Toolbar\ToolbarModeSelector.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Screens\Select\BeatmapInfoOverlay.cs" />
<Compile Include="Screens\Select\BeatmapInfoWedge.cs" />
<Compile Include="Users\User.cs" />
<Compile Include="Graphics\UserInterface\Volume\VolumeControl.cs" />
<Compile Include="Database\BeatmapDatabase.cs" />