1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-22 05:23:05 +08:00

Merge branch 'master' into seperateCursorSize

This commit is contained in:
Dean Herbert 2017-03-22 08:11:45 +09:00 committed by GitHub
commit b00f86d809
68 changed files with 808 additions and 472 deletions

@ -1 +1 @@
Subproject commit db310bfc10cd1c9ed12c9e19cdc0edfa53117353 Subproject commit e6394035d443d4498b71e845e5763dd3faf98c7c

View File

@ -105,6 +105,9 @@
<ItemGroup> <ItemGroup>
<Folder Include="Properties\" /> <Folder Include="Properties\" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -1,7 +1,10 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Screens.Testing; using osu.Framework.Screens.Testing;
using osu.Game.Graphics;
using osu.Game.Screens.Select.Options; using osu.Game.Screens.Select.Options;
namespace osu.Desktop.VisualTests.Tests namespace osu.Desktop.VisualTests.Tests
@ -16,6 +19,11 @@ namespace osu.Desktop.VisualTests.Tests
var overlay = new BeatmapOptionsOverlay(); var overlay = new BeatmapOptionsOverlay();
overlay.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, Color4.Purple, null, Key.Number1);
overlay.AddButton(@"Clear", @"local scores", FontAwesome.fa_eraser, Color4.Purple, null, Key.Number2);
overlay.AddButton(@"Edit", @"Beatmap", FontAwesome.fa_pencil, Color4.Yellow, null, Key.Number3);
overlay.AddButton(@"Delete", @"Beatmap", FontAwesome.fa_trash, Color4.Pink, null, Key.Number4, float.MaxValue);
Add(overlay); Add(overlay);
AddButton(@"Toggle", overlay.ToggleVisibility); AddButton(@"Toggle", overlay.ToggleVisibility);

View File

@ -144,5 +144,12 @@ namespace osu.Desktop.VisualTests.Tests
if (proxyable != null) if (proxyable != null)
approachContainer.Add(proxyable.ProxiedLayer.CreateProxy()); approachContainer.Add(proxyable.ProxiedLayer.CreateProxy());
} }
private enum HitObjectType
{
Circle,
Slider,
Spinner
}
} }
} }

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Platform;
using osu.Framework.Screens.Testing; using osu.Framework.Screens.Testing;
using osu.Game; using osu.Game;
using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Backgrounds;
@ -19,5 +20,11 @@ namespace osu.Desktop.VisualTests
// we depend on some dependencies to be loaded within OsuGameBase.load(). // we depend on some dependencies to be loaded within OsuGameBase.load().
Add(new TestBrowser()); Add(new TestBrowser());
} }
public override void SetHost(GameHost host)
{
base.SetHost(host);
host.Window.CursorState = CursorState.Hidden;
}
} }
} }

View File

@ -43,6 +43,8 @@ namespace osu.Desktop
var desktopWindow = host.Window as DesktopGameWindow; var desktopWindow = host.Window as DesktopGameWindow;
if (desktopWindow != null) if (desktopWindow != null)
{ {
desktopWindow.CursorState = CursorState.Hidden;
desktopWindow.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location); desktopWindow.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location);
desktopWindow.Title = Name; desktopWindow.Title = Name;

View File

@ -215,6 +215,7 @@ namespace osu.Desktop.Overlays
Origin = Anchor.Centre, Origin = Anchor.Centre,
Icon = FontAwesome.fa_upload, Icon = FontAwesome.fa_upload,
Colour = Color4.White, Colour = Color4.White,
TextSize = 20
} }
}); });
} }

View File

@ -128,9 +128,11 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
case ArmedState.Idle: case ArmedState.Idle:
Delay(duration + TIME_PREEMPT); Delay(duration + TIME_PREEMPT);
FadeOut(TIME_FADEOUT); FadeOut(TIME_FADEOUT);
Expire(true);
break; break;
case ArmedState.Miss: case ArmedState.Miss:
FadeOut(TIME_FADEOUT / 5); FadeOut(TIME_FADEOUT / 5);
Expire();
break; break;
case ArmedState.Hit: case ArmedState.Hit:
const double flash_in = 40; const double flash_in = 40;
@ -150,6 +152,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
FadeOut(800); FadeOut(800);
ScaleTo(Scale * 1.5f, 400, EasingTypes.OutQuad); ScaleTo(Scale * 1.5f, 400, EasingTypes.OutQuad);
Expire();
break; break;
} }
} }

View File

@ -168,6 +168,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
ball.FadeOut(160); ball.FadeOut(160);
FadeOut(800); FadeOut(800);
Expire();
} }
public Drawable ProxiedLayer => initialCircle.ApproachCircle; public Drawable ProxiedLayer => initialCircle.ApproachCircle;

View File

@ -146,9 +146,11 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
{ {
case ArmedState.Hit: case ArmedState.Hit:
ScaleTo(Scale * 1.2f, 320, EasingTypes.Out); ScaleTo(Scale * 1.2f, 320, EasingTypes.Out);
Expire();
break; break;
case ArmedState.Miss: case ArmedState.Miss:
ScaleTo(Scale * 0.8f, 320, EasingTypes.In); ScaleTo(Scale * 0.8f, 320, EasingTypes.In);
Expire();
break; break;
} }
} }

View File

@ -5,6 +5,5 @@ namespace osu.Game.Modes.Osu.Objects
{ {
public class HitCircle : OsuHitObject public class HitCircle : OsuHitObject
{ {
public override HitObjectType Type => HitObjectType.Circle;
} }
} }

View File

@ -36,8 +36,6 @@ namespace osu.Game.Modes.Osu.Objects
public float Scale { get; set; } = 1; public float Scale { get; set; } = 1;
public abstract HitObjectType Type { get; }
public Color4 ComboColour { get; set; } public Color4 ComboColour { get; set; }
public virtual bool NewCombo { get; set; } public virtual bool NewCombo { get; set; }
public int ComboIndex { get; set; } public int ComboIndex { get; set; }

View File

@ -148,11 +148,11 @@ namespace osu.Game.Modes.Osu.Objects
double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate; double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
double decay = Math.Pow(DECAY_BASE[(int)type], timeElapsed / 1000); double decay = Math.Pow(DECAY_BASE[(int)type], timeElapsed / 1000);
if (BaseHitObject.Type == HitObjectType.Spinner) if (BaseHitObject is Spinner)
{ {
// Do nothing for spinners // Do nothing for spinners
} }
else if (BaseHitObject.Type == HitObjectType.Slider) else if (BaseHitObject is Slider)
{ {
switch (type) switch (type)
{ {
@ -180,7 +180,7 @@ namespace osu.Game.Modes.Osu.Objects
break; break;
} }
} }
else if (BaseHitObject.Type == HitObjectType.Circle) else if (BaseHitObject is HitCircle)
{ {
addition = spacingWeight(DistanceTo(previousHitObject), type) * spacing_weight_scaling[(int)type]; addition = spacingWeight(DistanceTo(previousHitObject), type) * spacing_weight_scaling[(int)type];
} }

View File

@ -103,7 +103,5 @@ namespace osu.Game.Modes.Osu.Objects
} }
} }
} }
public override HitObjectType Type => HitObjectType.Slider;
} }
} }

View File

@ -6,7 +6,5 @@ namespace osu.Game.Modes.Osu.Objects
public class SliderTick : OsuHitObject public class SliderTick : OsuHitObject
{ {
public int RepeatIndex { get; set; } public int RepeatIndex { get; set; }
public override HitObjectType Type => HitObjectType.SliderTick;
} }
} }

View File

@ -10,8 +10,6 @@ namespace osu.Game.Modes.Osu.Objects
public double EndTime { get; set; } public double EndTime { get; set; }
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
public override HitObjectType Type => HitObjectType.Spinner;
public override bool NewCombo => true; public override bool NewCombo => true;
} }
} }

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Modes.Objects.Types;
using osu.Game.Modes.Osu.Beatmaps; using osu.Game.Modes.Osu.Beatmaps;
using osu.Game.Modes.Osu.Objects; using osu.Game.Modes.Osu.Objects;
using System; using System;
@ -26,8 +27,7 @@ namespace osu.Game.Modes.Osu
protected override void PreprocessHitObjects() protected override void PreprocessHitObjects()
{ {
foreach (var h in Objects) foreach (var h in Objects)
if (h.Type == HitObjectType.Slider) (h as IHasCurve)?.Curve?.Calculate();
((Slider)h).Curve.Calculate();
} }
protected override double CalculateInternal(Dictionary<string, string> categoryDifficulty) protected override double CalculateInternal(Dictionary<string, string> categoryDifficulty)

View File

@ -67,7 +67,6 @@
<Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" /> <Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBall.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBall.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBody.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBody.cs" />
<Compile Include="Objects\HitObjectType.cs" />
<Compile Include="Objects\OsuHitObjectDifficulty.cs" /> <Compile Include="Objects\OsuHitObjectDifficulty.cs" />
<Compile Include="Objects\SliderTick.cs" /> <Compile Include="Objects\SliderTick.cs" />
<Compile Include="OsuAutoReplay.cs" /> <Compile Include="OsuAutoReplay.cs" />

View File

@ -7,13 +7,13 @@ using System.Collections.Generic;
namespace osu.Game.Modes.Taiko.Beatmaps namespace osu.Game.Modes.Taiko.Beatmaps
{ {
internal class TaikoBeatmapConverter : IBeatmapConverter<TaikoBaseHit> internal class TaikoBeatmapConverter : IBeatmapConverter<TaikoHitObject>
{ {
public Beatmap<TaikoBaseHit> Convert(Beatmap original) public Beatmap<TaikoHitObject> Convert(Beatmap original)
{ {
return new Beatmap<TaikoBaseHit>(original) return new Beatmap<TaikoHitObject>(original)
{ {
HitObjects = new List<TaikoBaseHit>() // Todo: Implement HitObjects = new List<TaikoHitObject>() // Todo: Implement
}; };
} }
} }

View File

@ -6,13 +6,13 @@ using osu.Game.Modes.Taiko.Objects;
namespace osu.Game.Modes.Taiko.Beatmaps namespace osu.Game.Modes.Taiko.Beatmaps
{ {
internal class TaikoBeatmapProcessor : IBeatmapProcessor<TaikoBaseHit> internal class TaikoBeatmapProcessor : IBeatmapProcessor<TaikoHitObject>
{ {
public void SetDefaults(TaikoBaseHit hitObject, Beatmap<TaikoBaseHit> beatmap) public void SetDefaults(TaikoHitObject hitObject, Beatmap<TaikoHitObject> beatmap)
{ {
} }
public void PostProcess(Beatmap<TaikoBaseHit> beatmap) public void PostProcess(Beatmap<TaikoHitObject> beatmap)
{ {
} }
} }

View File

@ -1,13 +1,11 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Modes.Osu.Objects namespace osu.Game.Modes.Taiko.Judgements
{ {
public enum HitObjectType public enum TaikoHitResult
{ {
Circle, Good,
Slider, Great
Spinner,
SliderTick
} }
} }

View File

@ -7,5 +7,77 @@ namespace osu.Game.Modes.Taiko.Judgements
{ {
public class TaikoJudgementInfo : JudgementInfo public class TaikoJudgementInfo : JudgementInfo
{ {
/// <summary>
/// The maximum score value.
/// </summary>
public const TaikoHitResult MAX_HIT_RESULT = TaikoHitResult.Great;
/// <summary>
/// The score value.
/// </summary>
public TaikoHitResult TaikoResult;
/// <summary>
/// The score value for the combo portion of the score.
/// </summary>
public int ScoreValue => NumericResultForScore(TaikoResult);
/// <summary>
/// The score value for the accuracy portion of the score.
/// </summary>
public int AccuracyScoreValue => NumericResultForAccuracy(TaikoResult);
/// <summary>
/// The maximum score value for the combo portion of the score.
/// </summary>
public int MaxScoreValue => NumericResultForScore(MAX_HIT_RESULT);
/// <summary>
/// The maximum score value for the accuracy portion of the score.
/// </summary>
public int MaxAccuracyScoreValue => NumericResultForAccuracy(MAX_HIT_RESULT);
/// <summary>
/// Whether this Judgement has a secondary hit in the case of finishers.
/// </summary>
public bool SecondHit;
/// <summary>
/// Computes the numeric score value for the combo portion of the score.
/// For the accuracy portion of the score (including accuracy percentage), see <see cref="NumericResultForAccuracy(TaikoHitResult)"/>.
/// </summary>
/// <param name="result">The result to compute the score value for.</param>
/// <returns>The numeric score value.</returns>
protected virtual int NumericResultForScore(TaikoHitResult result)
{
switch (result)
{
default:
return 0;
case TaikoHitResult.Good:
return 100;
case TaikoHitResult.Great:
return 300;
}
}
/// <summary>
/// Computes the numeric score value for the accuracy portion of the score.
/// For the combo portion of the score, see <see cref="NumericResultForScore(TaikoHitResult)"/>.
/// </summary>
/// <param name="result">The result to compute the score value for.</param>
/// <returns>The numeric score value.</returns>
protected virtual int NumericResultForAccuracy(TaikoHitResult result)
{
switch (result)
{
default:
return 0;
case TaikoHitResult.Good:
return 150;
case TaikoHitResult.Great:
return 300;
}
}
} }
} }

View File

@ -12,9 +12,9 @@ namespace osu.Game.Modes.Taiko.Objects.Drawable
{ {
internal class DrawableTaikoHit : Sprite internal class DrawableTaikoHit : Sprite
{ {
private TaikoBaseHit h; private TaikoHitObject h;
public DrawableTaikoHit(TaikoBaseHit h) public DrawableTaikoHit(TaikoHitObject h)
{ {
this.h = h; this.h = h;

View File

@ -1,20 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Modes.Objects;
namespace osu.Game.Modes.Taiko.Objects
{
public class TaikoBaseHit : HitObject
{
public float Scale = 1;
public TaikoColour Type;
}
public enum TaikoColour
{
Red,
Blue
}
}

View File

@ -0,0 +1,59 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps.Timing;
using osu.Game.Database;
using osu.Game.Modes.Objects;
namespace osu.Game.Modes.Taiko.Objects
{
public class TaikoHitObject : HitObject
{
/// <summary>
/// HitCircle radius.
/// </summary>
public const float CIRCLE_RADIUS = 64;
/// <summary>
/// The hit window that results in a "GREAT" hit.
/// </summary>
public double HitWindowGreat = 35;
/// <summary>
/// The hit window that results in a "GOOD" hit.
/// </summary>
public double HitWindowGood = 80;
/// <summary>
/// The hit window that results in a "MISS".
/// </summary>
public double HitWindowMiss = 95;
/// <summary>
/// The time to scroll in the HitObject.
/// </summary>
public double PreEmpt;
/// <summary>
/// Whether this HitObject is in Kiai time.
/// </summary>
public bool Kiai;
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);
PreEmpt = 600 / (timing.SliderVelocityAt(StartTime) * difficulty.SliderMultiplier) * 1000;
ControlPoint overridePoint;
Kiai = timing.TimingPointAt(StartTime, out overridePoint).KiaiMode;
if (overridePoint != null)
Kiai |= overridePoint.KiaiMode;
HitWindowGreat = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 50, 35, 20);
HitWindowGood = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 120, 80, 50);
HitWindowMiss = BeatmapDifficulty.DifficultyRange(difficulty.OverallDifficulty, 135, 95, 70);
}
}
}

View File

@ -8,7 +8,7 @@ using System.Collections.Generic;
namespace osu.Game.Modes.Taiko namespace osu.Game.Modes.Taiko
{ {
public class TaikoDifficultyCalculator : DifficultyCalculator<TaikoBaseHit> public class TaikoDifficultyCalculator : DifficultyCalculator<TaikoHitObject>
{ {
public TaikoDifficultyCalculator(Beatmap beatmap) : base(beatmap) public TaikoDifficultyCalculator(Beatmap beatmap) : base(beatmap)
{ {
@ -19,6 +19,6 @@ namespace osu.Game.Modes.Taiko
return 0; return 0;
} }
protected override IBeatmapConverter<TaikoBaseHit> CreateBeatmapConverter() => new TaikoBeatmapConverter(); protected override IBeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter();
} }
} }

View File

@ -7,13 +7,13 @@ using osu.Game.Modes.UI;
namespace osu.Game.Modes.Taiko namespace osu.Game.Modes.Taiko
{ {
internal class TaikoScoreProcessor : ScoreProcessor<TaikoBaseHit, TaikoJudgementInfo> internal class TaikoScoreProcessor : ScoreProcessor<TaikoHitObject, TaikoJudgementInfo>
{ {
public TaikoScoreProcessor() public TaikoScoreProcessor()
{ {
} }
public TaikoScoreProcessor(HitRenderer<TaikoBaseHit, TaikoJudgementInfo> hitRenderer) public TaikoScoreProcessor(HitRenderer<TaikoHitObject, TaikoJudgementInfo> hitRenderer)
: base(hitRenderer) : base(hitRenderer)
{ {
} }

View File

@ -10,7 +10,7 @@ using osu.Game.Modes.UI;
namespace osu.Game.Modes.Taiko.UI namespace osu.Game.Modes.Taiko.UI
{ {
public class TaikoHitRenderer : HitRenderer<TaikoBaseHit, TaikoJudgementInfo> public class TaikoHitRenderer : HitRenderer<TaikoHitObject, TaikoJudgementInfo>
{ {
public TaikoHitRenderer(WorkingBeatmap beatmap) public TaikoHitRenderer(WorkingBeatmap beatmap)
: base(beatmap) : base(beatmap)
@ -19,12 +19,12 @@ namespace osu.Game.Modes.Taiko.UI
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
protected override IBeatmapConverter<TaikoBaseHit> CreateBeatmapConverter() => new TaikoBeatmapConverter(); protected override IBeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter();
protected override IBeatmapProcessor<TaikoBaseHit> CreateBeatmapProcessor() => new TaikoBeatmapProcessor(); protected override IBeatmapProcessor<TaikoHitObject> CreateBeatmapProcessor() => new TaikoBeatmapProcessor();
protected override Playfield<TaikoBaseHit, TaikoJudgementInfo> CreatePlayfield() => new TaikoPlayfield(); protected override Playfield<TaikoHitObject, TaikoJudgementInfo> CreatePlayfield() => new TaikoPlayfield();
protected override DrawableHitObject<TaikoBaseHit, TaikoJudgementInfo> GetVisualRepresentation(TaikoBaseHit h) => null; protected override DrawableHitObject<TaikoHitObject, TaikoJudgementInfo> GetVisualRepresentation(TaikoHitObject h) => null;
} }
} }

View File

@ -13,7 +13,7 @@ using osu.Game.Modes.Taiko.Judgements;
namespace osu.Game.Modes.Taiko.UI namespace osu.Game.Modes.Taiko.UI
{ {
public class TaikoPlayfield : Playfield<TaikoBaseHit, TaikoJudgementInfo> public class TaikoPlayfield : Playfield<TaikoHitObject, TaikoJudgementInfo>
{ {
public TaikoPlayfield() public TaikoPlayfield()
{ {

View File

@ -50,9 +50,10 @@
<Compile Include="Beatmaps\TaikoBeatmapConverter.cs" /> <Compile Include="Beatmaps\TaikoBeatmapConverter.cs" />
<Compile Include="Beatmaps\TaikoBeatmapProcessor.cs" /> <Compile Include="Beatmaps\TaikoBeatmapProcessor.cs" />
<Compile Include="Judgements\TaikoJudgementInfo.cs" /> <Compile Include="Judgements\TaikoJudgementInfo.cs" />
<Compile Include="Judgements\TaikoHitResult.cs" />
<Compile Include="TaikoDifficultyCalculator.cs" /> <Compile Include="TaikoDifficultyCalculator.cs" />
<Compile Include="Objects\Drawable\DrawableTaikoHit.cs" /> <Compile Include="Objects\Drawable\DrawableTaikoHit.cs" />
<Compile Include="Objects\TaikoBaseHit.cs" /> <Compile Include="Objects\TaikoHitObject.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="TaikoScoreProcessor.cs" /> <Compile Include="TaikoScoreProcessor.cs" />
<Compile Include="UI\TaikoHitRenderer.cs" /> <Compile Include="UI\TaikoHitRenderer.cs" />

View File

@ -43,21 +43,6 @@ namespace osu.Game.Beatmaps
TimingInfo = original?.TimingInfo ?? TimingInfo; TimingInfo = original?.TimingInfo ?? TimingInfo;
ComboColors = original?.ComboColors ?? ComboColors; ComboColors = original?.ComboColors ?? ComboColors;
} }
/// <summary>
/// Finds the slider velocity at a time.
/// </summary>
/// <param name="time">The time to find the slider velocity at.</param>
/// <returns>The slider velocity in positional length units.</returns>
public double SliderVelocityAt(double time)
{
double scoringDistance = 100 * BeatmapInfo.Difficulty.SliderMultiplier;
double beatDistance = TimingInfo.BeatDistanceAt(time);
if (beatDistance > 0)
return scoringDistance / beatDistance * 1000;
return scoringDistance;
}
} }
/// <summary> /// <summary>
@ -70,5 +55,14 @@ namespace osu.Game.Beatmaps
/// </summary> /// </summary>
/// <returns>The star difficulty.</returns> /// <returns>The star difficulty.</returns>
public double CalculateStarDifficulty() => Ruleset.GetRuleset(BeatmapInfo.Mode).CreateDifficultyCalculator(this).Calculate(); public double CalculateStarDifficulty() => Ruleset.GetRuleset(BeatmapInfo.Mode).CreateDifficultyCalculator(this).Calculate();
/// <summary>
/// Constructs a new beatmap.
/// </summary>
/// <param name="original">The original beatmap to use the parameters of.</param>
public Beatmap(Beatmap original = null)
: base(original)
{
}
} }
} }

View File

@ -10,7 +10,7 @@ using osu.Game.Database;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
internal class BeatmapGroup : IStateful<BeatmapGroupState> public class BeatmapGroup : IStateful<BeatmapGroupState>
{ {
public BeatmapPanel SelectedPanel; public BeatmapPanel SelectedPanel;
@ -63,8 +63,6 @@ namespace osu.Game.Beatmaps.Drawables
{ {
BeatmapSet = beatmapSet; BeatmapSet = beatmapSet;
WorkingBeatmap beatmap = database.GetWorkingBeatmap(BeatmapSet.Beatmaps.FirstOrDefault()); WorkingBeatmap beatmap = database.GetWorkingBeatmap(BeatmapSet.Beatmaps.FirstOrDefault());
foreach (var b in BeatmapSet.Beatmaps)
b.StarDifficulty = (float)(database.GetWorkingBeatmap(b).Beatmap?.CalculateStarDifficulty() ?? -1f);
Header = new BeatmapSetHeader(beatmap) Header = new BeatmapSetHeader(beatmap)
{ {

View File

@ -18,7 +18,7 @@ using osu.Game.Graphics.Sprites;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
internal class BeatmapPanel : Panel public class BeatmapPanel : Panel
{ {
public BeatmapInfo Beatmap; public BeatmapInfo Beatmap;
private Sprite background; private Sprite background;
@ -59,6 +59,8 @@ namespace osu.Game.Beatmaps.Drawables
protected override void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden) protected override void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden)
{ {
if (!IsLoaded) return;
base.ApplyState(last); base.ApplyState(last);
if (last == PanelSelectedState.Hidden && State != last) if (last == PanelSelectedState.Hidden && State != last)
@ -138,7 +140,7 @@ namespace osu.Game.Beatmaps.Drawables
}, },
starCounter = new StarCounter starCounter = new StarCounter
{ {
Count = beatmap.StarDifficulty, Count = (float)beatmap.StarDifficulty,
Scale = new Vector2(0.8f), Scale = new Vector2(0.8f),
} }
} }

View File

@ -17,7 +17,7 @@ using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
internal class BeatmapSetHeader : Panel public class BeatmapSetHeader : Panel
{ {
public Action<BeatmapSetHeader> GainedSelection; public Action<BeatmapSetHeader> GainedSelection;
private SpriteText title, artist; private SpriteText title, artist;
@ -32,10 +32,6 @@ namespace osu.Game.Beatmaps.Drawables
Children = new Drawable[] Children = new Drawable[]
{ {
new PanelBackground(beatmap)
{
RelativeSizeAxes = Axes.Both,
},
new FillFlowContainer new FillFlowContainer
{ {
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
@ -74,13 +70,23 @@ namespace osu.Game.Beatmaps.Drawables
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config, OsuGameBase game)
{ {
this.config = config; this.config = config;
preferUnicode = config.GetBindable<bool>(OsuConfig.ShowUnicode); preferUnicode = config.GetBindable<bool>(OsuConfig.ShowUnicode);
preferUnicode.ValueChanged += preferUnicode_changed; preferUnicode.ValueChanged += preferUnicode_changed;
preferUnicode_changed(preferUnicode, null); preferUnicode_changed(preferUnicode, null);
new PanelBackground(beatmap)
{
RelativeSizeAxes = Axes.Both,
Depth = 1,
}.LoadAsync(game, b =>
{
Add(b);
b.FadeInFromZero(200);
});
} }
private void preferUnicode_changed(object sender, EventArgs e) private void preferUnicode_changed(object sender, EventArgs e)
@ -98,16 +104,18 @@ namespace osu.Game.Beatmaps.Drawables
private class PanelBackground : BufferedContainer private class PanelBackground : BufferedContainer
{ {
private readonly WorkingBeatmap working;
public PanelBackground(WorkingBeatmap working) public PanelBackground(WorkingBeatmap working)
{ {
this.working = working;
CacheDrawnFrameBuffer = true; CacheDrawnFrameBuffer = true;
Children = new[] Children = new Drawable[]
{ {
new BeatmapBackgroundSprite(working)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
},
new FillFlowContainer new FillFlowContainer
{ {
Depth = -1, Depth = -1,
@ -151,21 +159,6 @@ namespace osu.Game.Beatmaps.Drawables
}, },
}; };
} }
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
{
new BeatmapBackgroundSprite(working)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
}.LoadAsync(game, bg =>
{
Add(bg);
ForceRedraw();
});
}
} }
public void AddDifficultyIcons(IEnumerable<BeatmapPanel> panels) public void AddDifficultyIcons(IEnumerable<BeatmapPanel> panels)

View File

@ -12,7 +12,7 @@ using osu.Framework.Extensions.Color4Extensions;
namespace osu.Game.Beatmaps.Drawables namespace osu.Game.Beatmaps.Drawables
{ {
internal class Panel : Container, IStateful<PanelSelectedState> public class Panel : Container, IStateful<PanelSelectedState>
{ {
public const float MAX_HEIGHT = 80; public const float MAX_HEIGHT = 80;
@ -51,6 +51,8 @@ namespace osu.Game.Beatmaps.Drawables
protected virtual void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden) protected virtual void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden)
{ {
if (!IsLoaded) return;
switch (state) switch (state)
{ {
case PanelSelectedState.Hidden: case PanelSelectedState.Hidden:
@ -115,7 +117,7 @@ namespace osu.Game.Beatmaps.Drawables
} }
} }
internal enum PanelSelectedState public enum PanelSelectedState
{ {
Hidden, Hidden,
NotSelected, NotSelected,

View File

@ -10,6 +10,7 @@ using osu.Game.Beatmaps.Samples;
using osu.Game.Beatmaps.Timing; using osu.Game.Beatmaps.Timing;
using osu.Game.Modes; using osu.Game.Modes;
using osu.Game.Modes.Objects; using osu.Game.Modes.Objects;
using osu.Game.Beatmaps.Legacy;
namespace osu.Game.Beatmaps.Formats namespace osu.Game.Beatmaps.Formats
{ {
@ -206,7 +207,8 @@ namespace osu.Game.Beatmaps.Formats
VelocityAdjustment = beatLength < 0 ? -beatLength / 100.0 : 1, VelocityAdjustment = beatLength < 0 ? -beatLength / 100.0 : 1,
TimingChange = split.Length <= 6 || split[6][0] == '1', TimingChange = split.Length <= 6 || split[6][0] == '1',
KiaiMode = (effectFlags & 1) > 0, KiaiMode = (effectFlags & 1) > 0,
OmitFirstBarLine = (effectFlags & 8) > 0 OmitFirstBarLine = (effectFlags & 8) > 0,
TimeSignature = (TimeSignatures)int.Parse(split[2])
}; };
} }
@ -244,6 +246,16 @@ namespace osu.Game.Beatmaps.Formats
} }
} }
protected override Beatmap ParseFile(TextReader stream)
{
return new LegacyBeatmap(base.ParseFile(stream));
}
public override Beatmap Decode(TextReader stream)
{
return new LegacyBeatmap(base.Decode(stream));
}
protected override void ParseFile(TextReader stream, Beatmap beatmap) protected override void ParseFile(TextReader stream, Beatmap beatmap)
{ {
HitObjectParser parser = null; HitObjectParser parser = null;

View File

@ -0,0 +1,21 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Beatmaps.Legacy
{
/// <summary>
/// A type of Beatmap loaded from a legacy .osu beatmap file (version &lt;=15).
/// </summary>
public class LegacyBeatmap : Beatmap
{
/// <summary>
/// Constructs a new beatmap.
/// </summary>
/// <param name="original">The original beatmap to use the parameters of.</param>
internal LegacyBeatmap(Beatmap original = null)
: base(original)
{
HitObjects = original?.HitObjects;
}
}
}

View File

@ -11,18 +11,12 @@ namespace osu.Game.Beatmaps.Timing
TimingChange = true, TimingChange = true,
}; };
public TimeSignatures TimeSignature;
public double Time; public double Time;
public double BeatLength; public double BeatLength;
public double VelocityAdjustment; public double VelocityAdjustment;
public bool TimingChange; public bool TimingChange;
public bool KiaiMode; public bool KiaiMode;
public bool OmitFirstBarLine; public bool OmitFirstBarLine;
}
internal enum TimeSignatures
{
SimpleQuadruple = 4,
SimpleTriple = 3
} }
} }

View File

@ -0,0 +1,11 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Beatmaps.Timing
{
public enum TimeSignatures
{
SimpleQuadruple = 4,
SimpleTriple = 3
}
}

View File

@ -102,5 +102,21 @@ namespace osu.Game.Beatmaps.Timing
return timingPoint ?? ControlPoint.Default; return timingPoint ?? ControlPoint.Default;
} }
/// <summary>
/// Finds the slider velocity at a time.
/// </summary>
/// <param name="time">The time to find the slider velocity at.</param>
/// <returns>The slider velocity in milliseconds.</returns>
public double SliderVelocityAt(double time)
{
const double base_scoring_distance = 100;
double beatDistance = BeatDistanceAt(time);
if (beatDistance > 0)
return base_scoring_distance / beatDistance * 1000;
return base_scoring_distance;
}
} }
} }

View File

@ -123,16 +123,15 @@ namespace osu.Game.Database
/// <param name="paths">Multiple locations on disk</param> /// <param name="paths">Multiple locations on disk</param>
public void Import(IEnumerable<string> paths) public void Import(IEnumerable<string> paths)
{ {
Stack<BeatmapSetInfo> sets = new Stack<BeatmapSetInfo>();
foreach (string p in paths) foreach (string p in paths)
{
try try
{ {
BeatmapSetInfo set = getBeatmapSet(p); BeatmapSetInfo set = getBeatmapSet(p);
//If we have an ID then we already exist in the database. //If we have an ID then we already exist in the database.
if (set.ID == 0) if (set.ID == 0)
sets.Push(set); Import(new[] { set });
// We may or may not want to delete the file depending on where it is stored. // We may or may not want to delete the file depending on where it is stored.
// e.g. reconstructing/repairing database with beatmaps from default storage. // e.g. reconstructing/repairing database with beatmaps from default storage.
@ -152,9 +151,7 @@ namespace osu.Game.Database
e = e.InnerException ?? e; e = e.InnerException ?? e;
Logger.Error(e, @"Could not import beatmap set"); Logger.Error(e, @"Could not import beatmap set");
} }
}
// Batch commit with multiple sets to database
Import(sets);
} }
/// <summary> /// <summary>
@ -236,6 +233,8 @@ namespace osu.Game.Database
// TODO: Diff beatmap metadata with set metadata and leave it here if necessary // TODO: Diff beatmap metadata with set metadata and leave it here if necessary
beatmap.BeatmapInfo.Metadata = null; beatmap.BeatmapInfo.Metadata = null;
beatmap.BeatmapInfo.StarDifficulty = beatmap.CalculateStarDifficulty();
beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo); beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo);
} }
beatmapSet.StoryboardFile = archive.StoryboardFilename; beatmapSet.StoryboardFile = archive.StoryboardFilename;

View File

@ -75,16 +75,7 @@ namespace osu.Game.Database
// Metadata // Metadata
public string Version { get; set; } public string Version { get; set; }
private float starDifficulty = -1; public double StarDifficulty { get; set; }
public float StarDifficulty
{
get
{
return starDifficulty < 0 ? (Difficulty?.OverallDifficulty ?? 5) : starDifficulty;
}
set { starDifficulty = value; }
}
public bool Equals(BeatmapInfo other) public bool Equals(BeatmapInfo other)
{ {

View File

@ -24,7 +24,7 @@ namespace osu.Game.Database
[OneToMany(CascadeOperations = CascadeOperation.All)] [OneToMany(CascadeOperations = CascadeOperation.All)]
public List<BeatmapInfo> Beatmaps { get; set; } public List<BeatmapInfo> Beatmaps { get; set; }
public float MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty); public double MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty);
public bool DeletePending { get; set; } public bool DeletePending { get; set; }

View File

@ -20,6 +20,25 @@ namespace osu.Game.Graphics.Cursor
{ {
protected override Drawable CreateCursor() => new Cursor(); protected override Drawable CreateCursor() => new Cursor();
protected override bool OnMouseMove(InputState state)
{
if (state.Mouse.HasMainButtonPressed)
{
Vector2 offset = state.Mouse.Position - state.Mouse.PositionMouseDown ?? state.Mouse.Delta;
float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f;
// Always rotate in the direction of least distance
float diff = (degrees - ActiveCursor.Rotation) % 360;
if (diff < -180) diff += 360;
if (diff > 180) diff -= 360;
degrees = ActiveCursor.Rotation + diff;
ActiveCursor.RotateTo(degrees, 600, EasingTypes.OutQuint);
}
return base.OnMouseMove(state);
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{ {
ActiveCursor.Scale = new Vector2(1); ActiveCursor.Scale = new Vector2(1);
@ -35,7 +54,7 @@ namespace osu.Game.Graphics.Cursor
if (!state.Mouse.HasMainButtonPressed) if (!state.Mouse.HasMainButtonPressed)
{ {
((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, EasingTypes.OutQuint); ((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, EasingTypes.OutQuint);
ActiveCursor.RotateTo(0, 200, EasingTypes.OutQuint); ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), EasingTypes.OutElasticHalf);
ActiveCursor.ScaleTo(1, 500, EasingTypes.OutElastic); ActiveCursor.ScaleTo(1, 500, EasingTypes.OutElastic);
} }
@ -49,12 +68,6 @@ namespace osu.Game.Graphics.Cursor
return base.OnClick(state); return base.OnClick(state);
} }
protected override bool OnDragStart(InputState state)
{
ActiveCursor.RotateTo(-30, 600, EasingTypes.OutElastic);
return base.OnDragStart(state);
}
protected override void PopIn() protected override void PopIn()
{ {
ActiveCursor.FadeTo(1, 250, EasingTypes.OutQuint); ActiveCursor.FadeTo(1, 250, EasingTypes.OutQuint);

View File

@ -1,11 +1,11 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics.Sprites; using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics namespace osu.Game.Graphics
{ {
public class TextAwesome : SpriteText public class TextAwesome : OsuSpriteText
{ {
//public override FontFace FontFace => (int)Icon < 0xf000 ? FontFace.OsuFont : FontFace.FontAwesome; //public override FontFace FontFace => (int)Icon < 0xf000 ? FontFace.OsuFont : FontFace.FontAwesome;

View File

@ -54,6 +54,7 @@ namespace osu.Game.Graphics.UserInterface
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Margin = new MarginPadding { Right = 4 }, Margin = new MarginPadding { Right = 4 },
TextSize = 20
} }
}; };
} }

View File

@ -34,12 +34,11 @@ namespace osu.Game.Modes.Objects.Drawables
set set
{ {
if (state == value) return; if (state == value)
return;
state = value; state = value;
UpdateState(state); UpdateState(state);
if (IsLoaded)
Expire();
if (State == ArmedState.Hit) if (State == ArmedState.Hit)
PlaySample(); PlaySample();
@ -63,8 +62,6 @@ namespace osu.Game.Modes.Objects.Drawables
//force application of the state that was set before we loaded. //force application of the state that was set before we loaded.
UpdateState(State); UpdateState(State);
Expire(true);
} }
} }

View File

@ -66,12 +66,14 @@ namespace osu.Game.Modes.UI
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Icon = FontAwesome.fa_osu_mod_bg, Icon = FontAwesome.fa_osu_mod_bg,
Shadow = true, Shadow = true,
TextSize = 20
}, },
modIcon = new TextAwesome modIcon = new TextAwesome
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Colour = OsuColour.Gray(84), Colour = OsuColour.Gray(84),
TextSize = 20
}, },
}; };

View File

@ -7,6 +7,7 @@ using osu.Game.Modes.Objects;
using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Objects.Drawables;
using OpenTK; using OpenTK;
using osu.Game.Modes.Judgements; using osu.Game.Modes.Judgements;
using osu.Framework.Allocation;
namespace osu.Game.Modes.UI namespace osu.Game.Modes.UI
{ {
@ -45,10 +46,16 @@ namespace osu.Game.Modes.UI
} }
}); });
Add(HitObjects = new HitObjectContainer<DrawableHitObject<TObject, TJudgement>> HitObjects = new HitObjectContainer<DrawableHitObject<TObject, TJudgement>>
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
}); };
}
[BackgroundDependencyLoader]
private void load()
{
Add(HitObjects);
} }
/// <summary> /// <summary>

View File

@ -279,6 +279,7 @@ namespace osu.Game
//central game mode change logic. //central game mode change logic.
if (!currentScreen.ShowOverlays) if (!currentScreen.ShowOverlays)
{ {
options.State = Visibility.Hidden;
Toolbar.State = Visibility.Hidden; Toolbar.State = Visibility.Hidden;
musicController.State = Visibility.Hidden; musicController.State = Visibility.Hidden;
chat.State = Visibility.Hidden; chat.State = Visibility.Hidden;

View File

@ -65,7 +65,7 @@ namespace osu.Game.Overlays
Vector2 change = state.Mouse.Position - state.Mouse.PositionMouseDown.Value; Vector2 change = state.Mouse.Position - state.Mouse.PositionMouseDown.Value;
// Diminish the drag distance as we go further to simulate "rubber band" feeling. // Diminish the drag distance as we go further to simulate "rubber band" feeling.
change *= (float)Math.Pow(change.Length, 0.7f) / change.Length; change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length;
dragContainer.MoveTo(change); dragContainer.MoveTo(change);
return base.OnDrag(state); return base.OnDrag(state);

View File

@ -169,6 +169,7 @@ namespace osu.Game.Overlays.Notifications
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Icon = FontAwesome.fa_times_circle, Icon = FontAwesome.fa_times_circle,
TextSize = 20
} }
}; };
} }

View File

@ -53,6 +53,7 @@ namespace osu.Game.Overlays.Notifications
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Icon = icon, Icon = icon,
TextSize = 20
} }
}); });

View File

@ -32,6 +32,7 @@ namespace osu.Game.Overlays.Options
{ {
Icon = Ruleset.GetRuleset(m).Icon, Icon = Ruleset.GetRuleset(m).Icon,
Colour = Color4.Gray, Colour = Color4.Gray,
TextSize = 20
}); });
Children = new Drawable[] Children = new Drawable[]

View File

@ -5,6 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using System;
namespace osu.Game.Overlays.Options.Sections.Graphics namespace osu.Game.Overlays.Options.Sections.Graphics
{ {
@ -12,9 +13,16 @@ namespace osu.Game.Overlays.Options.Sections.Graphics
{ {
protected override string Header => "Layout"; protected override string Header => "Layout";
private OptionSlider<int> letterboxPositionX;
private OptionSlider<int> letterboxPositionY;
private Bindable<bool> letterboxing;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(FrameworkConfigManager config) private void load(FrameworkConfigManager config)
{ {
letterboxing = config.GetBindable<bool>(FrameworkConfig.Letterboxing);
Children = new Drawable[] Children = new Drawable[]
{ {
new OptionLabel { Text = "Resolution: TODO dropdown" }, new OptionLabel { Text = "Resolution: TODO dropdown" },
@ -26,19 +34,36 @@ namespace osu.Game.Overlays.Options.Sections.Graphics
new OsuCheckbox new OsuCheckbox
{ {
LabelText = "Letterboxing", LabelText = "Letterboxing",
Bindable = config.GetBindable<bool>(FrameworkConfig.Letterboxing), Bindable = letterboxing,
}, },
new OptionSlider<int> letterboxPositionX = new OptionSlider<int>
{ {
LabelText = "Horizontal position", LabelText = "Horizontal position",
Bindable = (BindableInt)config.GetBindable<int>(FrameworkConfig.LetterboxPositionX) Bindable = (BindableInt)config.GetBindable<int>(FrameworkConfig.LetterboxPositionX)
}, },
new OptionSlider<int> letterboxPositionY = new OptionSlider<int>
{ {
LabelText = "Vertical position", LabelText = "Vertical position",
Bindable = (BindableInt)config.GetBindable<int>(FrameworkConfig.LetterboxPositionY) Bindable = (BindableInt)config.GetBindable<int>(FrameworkConfig.LetterboxPositionY)
}, },
}; };
letterboxing.ValueChanged += visibilityChanged;
letterboxing.TriggerChange();
}
private void visibilityChanged(object sender, EventArgs e)
{
if (letterboxing)
{
letterboxPositionX.Show();
letterboxPositionY.Show();
}
else
{
letterboxPositionX.Hide();
letterboxPositionY.Hide();
}
} }
} }
} }

View File

@ -87,6 +87,7 @@ namespace osu.Game.Overlays.Options
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
TextSize = 20
}, },
} }
}, },

View File

@ -98,6 +98,7 @@ namespace osu.Game.Overlays.Toolbar
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
TextSize = 20
}, },
DrawableText = new OsuSpriteText DrawableText = new OsuSpriteText
{ {

View File

@ -21,7 +21,7 @@ namespace osu.Game.Screens.Menu
internal override bool ShowOverlays => false; internal override bool ShowOverlays => false;
internal override bool HasLocalCursorDisplayed => false; internal override bool HasLocalCursorDisplayed => true;
public Disclaimer() public Disclaimer()
{ {

View File

@ -27,6 +27,7 @@ namespace osu.Game.Screens.Menu
internal override bool ShowOverlays => buttons.State != MenuState.Initial; internal override bool ShowOverlays => buttons.State != MenuState.Initial;
private BackgroundScreen background; private BackgroundScreen background;
private Screen songSelect;
protected override BackgroundScreen CreateBackground() => background; protected override BackgroundScreen CreateBackground() => background;
@ -46,7 +47,7 @@ namespace osu.Game.Screens.Menu
OnChart = delegate { Push(new ChartListing()); }, OnChart = delegate { Push(new ChartListing()); },
OnDirect = delegate { Push(new OnlineListing()); }, OnDirect = delegate { Push(new OnlineListing()); },
OnEdit = delegate { Push(new Editor()); }, OnEdit = delegate { Push(new Editor()); },
OnSolo = delegate { Push(new PlaySongSelect()); }, OnSolo = delegate { Push(consumeSongSelect()); },
OnMulti = delegate { Push(new Lobby()); }, OnMulti = delegate { Push(new Lobby()); },
OnTest = delegate { Push(new TestBrowser()); }, OnTest = delegate { Push(new TestBrowser()); },
OnExit = delegate { Exit(); }, OnExit = delegate { Exit(); },
@ -62,6 +63,24 @@ namespace osu.Game.Screens.Menu
background.LoadAsync(game); background.LoadAsync(game);
buttons.OnSettings = game.ToggleOptions; buttons.OnSettings = game.ToggleOptions;
preloadSongSelect();
}
private void preloadSongSelect()
{
if (songSelect == null)
{
songSelect = new PlaySongSelect();
songSelect.LoadAsync(Game);
}
}
private Screen consumeSongSelect()
{
var s = songSelect;
songSelect = null;
return s;
} }
protected override void OnEntering(Screen last) protected override void OnEntering(Screen last)
@ -86,6 +105,9 @@ namespace osu.Game.Screens.Menu
{ {
base.OnResuming(last); base.OnResuming(last);
//we may have consumed our preloaded instance, so let's make another.
preloadSongSelect();
const float length = 300; const float length = 300;
buttons.State = MenuState.TopLevel; buttons.State = MenuState.TopLevel;

View File

@ -15,47 +15,234 @@ using OpenTK.Input;
using System.Collections; using System.Collections;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using System.Diagnostics; using System.Diagnostics;
using osu.Game.Screens.Select.Filter; using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Threading;
namespace osu.Game.Screens.Select namespace osu.Game.Screens.Select
{ {
internal class CarouselContainer : ScrollContainer, IEnumerable<BeatmapGroup> internal class BeatmapCarousel : ScrollContainer, IEnumerable<BeatmapGroup>
{ {
private Container<Panel> scrollableContent; public BeatmapInfo SelectedBeatmap => selectedPanel?.Beatmap;
private List<BeatmapGroup> groups = new List<BeatmapGroup>();
private List<Panel> panels = new List<Panel>();
public BeatmapGroup SelectedGroup { get; private set; } public Action BeatmapsChanged;
public BeatmapPanel SelectedPanel { get; private set; }
public IEnumerable<BeatmapSetInfo> Beatmaps
{
get
{
return groups.Select(g => g.BeatmapSet);
}
set
{
scrollableContent.Clear(false);
panels.Clear();
groups.Clear();
List<BeatmapGroup> newGroups = null;
Task.Run(() =>
{
newGroups = value.Select(createGroup).ToList();
criteria.Filter(newGroups);
}).ContinueWith(t =>
{
Schedule(() =>
{
foreach (var g in newGroups)
addGroup(g);
computeYPositions();
BeatmapsChanged?.Invoke();
});
});
}
}
private List<float> yPositions = new List<float>(); private List<float> yPositions = new List<float>();
public CarouselContainer() /// <summary>
{ /// Required for now unfortunately.
DistanceDecayJump = 0.01; /// </summary>
private BeatmapDatabase database;
private Container<Panel> scrollableContent;
private List<BeatmapGroup> groups = new List<BeatmapGroup>();
private List<Panel> panels = new List<Panel>();
private BeatmapGroup selectedGroup;
private BeatmapPanel selectedPanel;
public BeatmapCarousel()
{
Add(scrollableContent = new Container<Panel> Add(scrollableContent = new Container<Panel>
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}); });
} }
public void AddGroup(BeatmapGroup group) public void AddBeatmap(BeatmapSetInfo beatmapSet)
{ {
groups.Add(group); var group = createGroup(beatmapSet);
panels.Add(group.Header); //for the time being, let's completely load the difficulty panels in the background.
group.Header.UpdateClock(Clock); //this likely won't scale so well, but allows us to completely async the loading flow.
foreach (BeatmapPanel panel in group.BeatmapPanels) Schedule(delegate
{ {
panels.Add(panel); addGroup(group);
panel.UpdateClock(Clock); computeYPositions();
if (selectedGroup == null)
selectGroup(group);
});
} }
public void SelectBeatmap(BeatmapInfo beatmap, bool animated = true)
{
if (beatmap == null)
{
SelectNext();
return;
}
foreach (BeatmapGroup group in groups)
{
var panel = group.BeatmapPanels.FirstOrDefault(p => p.Beatmap.Equals(beatmap));
if (panel != null)
{
selectGroup(group, panel, animated);
return;
}
}
}
public void RemoveBeatmap(BeatmapSetInfo info) => removeGroup(groups.Find(b => b.BeatmapSet.ID == info.ID));
public Action<BeatmapGroup, BeatmapInfo> SelectionChanged;
public Action StartRequested;
public void SelectNext(int direction = 1, bool skipDifficulties = true)
{
if (groups.Count == 0)
{
selectedGroup = null;
selectedPanel = null;
return;
}
if (!skipDifficulties && selectedGroup != null)
{
int i = selectedGroup.BeatmapPanels.IndexOf(selectedPanel) + direction;
if (i >= 0 && i < selectedGroup.BeatmapPanels.Count)
{
//changing difficulty panel, not set.
selectGroup(selectedGroup, selectedGroup.BeatmapPanels[i]);
return;
}
}
int startIndex = groups.IndexOf(selectedGroup);
int index = startIndex;
do
{
index = (index + direction + groups.Count) % groups.Count;
if (groups[index].State != BeatmapGroupState.Hidden)
{
SelectBeatmap(groups[index].BeatmapPanels.First().Beatmap);
return;
}
} while (index != startIndex);
}
public void SelectRandom()
{
List<BeatmapGroup> visibleGroups = groups.Where(selectGroup => selectGroup.State != BeatmapGroupState.Hidden).ToList();
if (visibleGroups.Count < 1)
return;
BeatmapGroup group = visibleGroups[RNG.Next(visibleGroups.Count)];
BeatmapPanel panel = group?.BeatmapPanels.First();
if (panel == null)
return;
selectGroup(group, panel);
}
private FilterCriteria criteria = new FilterCriteria();
private ScheduledDelegate filterTask;
public void Filter(FilterCriteria newCriteria = null, bool debounce = true)
{
if (!IsLoaded) return;
criteria = newCriteria ?? criteria ?? new FilterCriteria();
Action perform = delegate
{
filterTask = null;
criteria.Filter(groups);
var filtered = new List<BeatmapGroup>(groups);
scrollableContent.Clear(false);
panels.Clear();
groups.Clear();
foreach (var g in filtered)
addGroup(g);
computeYPositions(); computeYPositions();
if (selectedGroup == null || selectedGroup.State == BeatmapGroupState.Hidden)
SelectNext();
};
filterTask?.Cancel();
if (debounce)
filterTask = Scheduler.AddDelayed(perform, 250);
else
perform();
} }
public void RemoveGroup(BeatmapGroup group) public IEnumerator<BeatmapGroup> GetEnumerator() => groups.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
private BeatmapGroup createGroup(BeatmapSetInfo beatmapSet)
{
database.GetChildren(beatmapSet);
beatmapSet.Beatmaps.ForEach(b => { if (b.Metadata == null) b.Metadata = beatmapSet.Metadata; });
return new BeatmapGroup(beatmapSet, database)
{
SelectionChanged = SelectionChanged,
StartRequested = b => StartRequested?.Invoke(),
State = BeatmapGroupState.Collapsed
};
}
[BackgroundDependencyLoader(permitNulls: true)]
private void load(BeatmapDatabase database)
{
this.database = database;
}
private void addGroup(BeatmapGroup group)
{
groups.Add(group);
panels.Add(group.Header);
panels.AddRange(group.BeatmapPanels);
}
private void removeGroup(BeatmapGroup group)
{ {
groups.Remove(group); groups.Remove(group);
panels.Remove(group.Header); panels.Remove(group.Header);
@ -65,18 +252,12 @@ namespace osu.Game.Screens.Select
scrollableContent.Remove(group.Header); scrollableContent.Remove(group.Header);
scrollableContent.Remove(group.BeatmapPanels); scrollableContent.Remove(group.BeatmapPanels);
if (selectedGroup == group)
SelectNext();
computeYPositions(); computeYPositions();
} }
private void movePanel(Panel panel, bool advance, bool animated, ref float currentY)
{
yPositions.Add(currentY);
panel.MoveToY(currentY, animated ? 750 : 0, EasingTypes.OutExpo);
if (advance)
currentY += panel.DrawHeight + 5;
}
/// <summary> /// <summary>
/// Computes the target Y positions for every panel in the carousel. /// Computes the target Y positions for every panel in the carousel.
/// </summary> /// </summary>
@ -99,7 +280,7 @@ namespace osu.Game.Screens.Select
foreach (BeatmapPanel panel in group.BeatmapPanels) foreach (BeatmapPanel panel in group.BeatmapPanels)
{ {
if (panel == SelectedPanel) if (panel == selectedPanel)
selectedY = currentY + panel.DrawHeight / 2 - DrawHeight / 2; selectedY = currentY + panel.DrawHeight / 2 - DrawHeight / 2;
panel.MoveToX(-50, 500, EasingTypes.OutExpo); panel.MoveToX(-50, 500, EasingTypes.OutExpo);
@ -129,105 +310,62 @@ namespace osu.Game.Screens.Select
return selectedY; return selectedY;
} }
public void SelectBeatmap(BeatmapInfo beatmap, bool animated = true) private void movePanel(Panel panel, bool advance, bool animated, ref float currentY)
{ {
foreach (BeatmapGroup group in groups) yPositions.Add(currentY);
{ panel.MoveToY(currentY, animated ? 750 : 0, EasingTypes.OutExpo);
var panel = group.BeatmapPanels.FirstOrDefault(p => p.Beatmap.Equals(beatmap));
if (panel != null) if (advance)
{ currentY += panel.DrawHeight + 5;
selectGroup(group, panel, animated);
return;
}
}
} }
private void selectGroup(BeatmapGroup group, BeatmapPanel panel, bool animated = true) private void selectGroup(BeatmapGroup group, BeatmapPanel panel = null, bool animated = true)
{ {
if (panel == null)
panel = group.BeatmapPanels.First();
Trace.Assert(group.BeatmapPanels.Contains(panel), @"Selected panel must be in provided group"); Trace.Assert(group.BeatmapPanels.Contains(panel), @"Selected panel must be in provided group");
if (SelectedGroup != null && SelectedGroup != group && SelectedGroup.State != BeatmapGroupState.Hidden) if (selectedGroup != null && selectedGroup != group && selectedGroup.State != BeatmapGroupState.Hidden)
SelectedGroup.State = BeatmapGroupState.Collapsed; selectedGroup.State = BeatmapGroupState.Collapsed;
group.State = BeatmapGroupState.Expanded; group.State = BeatmapGroupState.Expanded;
SelectedGroup = group; selectedGroup = group;
panel.State = PanelSelectedState.Selected; panel.State = PanelSelectedState.Selected;
SelectedPanel = panel; selectedPanel = panel;
float selectedY = computeYPositions(animated); float selectedY = computeYPositions(animated);
ScrollTo(selectedY, animated); ScrollTo(selectedY, animated);
} }
public void Sort(SortMode mode) protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{ {
List<BeatmapGroup> sortedGroups = new List<BeatmapGroup>(groups); int direction = 0;
switch (mode) bool skipDifficulties = false;
switch (args.Key)
{ {
case SortMode.Artist: case Key.Up:
sortedGroups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Artist, y.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase)); direction = -1;
break; break;
case SortMode.Title: case Key.Down:
sortedGroups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Title, y.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase)); direction = 1;
break; break;
case SortMode.Author: case Key.Left:
sortedGroups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Author, y.BeatmapSet.Metadata.Author, StringComparison.InvariantCultureIgnoreCase)); direction = -1;
skipDifficulties = true;
break; break;
case SortMode.Difficulty: case Key.Right:
sortedGroups.Sort((x, y) => x.BeatmapSet.MaxStarDifficulty.CompareTo(y.BeatmapSet.MaxStarDifficulty)); direction = 1;
break; skipDifficulties = true;
default:
Sort(SortMode.Artist); // Temporary
break; break;
} }
scrollableContent.Clear(false); if (direction == 0)
panels.Clear(); return base.OnKeyDown(state, args);
groups.Clear();
foreach (BeatmapGroup group in sortedGroups) SelectNext(direction, skipDifficulties);
AddGroup(group); return true;
}
/// <summary>
/// Computes the x-offset of currently visible panels. Makes the carousel appear round.
/// </summary>
/// <param name="dist">
/// Vertical distance from the center of the carousel container
/// ranging from -1 to 1.
/// </param>
/// <param name="halfHeight">Half the height of the carousel container.</param>
private static float offsetX(float dist, float halfHeight)
{
// The radius of the circle the carousel moves on.
const float circle_radius = 3;
double discriminant = Math.Max(0, circle_radius * circle_radius - dist * dist);
float x = (circle_radius - (float)Math.Sqrt(discriminant)) * halfHeight;
return 125 + x;
}
/// <summary>
/// Update a panel's x position and multiplicative alpha based on its y position and
/// the current scroll position.
/// </summary>
/// <param name="p">The panel to be updated.</param>
/// <param name="halfHeight">Half the draw height of the carousel container.</param>
private void updatePanel(Panel p, float halfHeight)
{
var height = p.IsPresent ? p.DrawHeight : 0;
float panelDrawY = p.Position.Y - Current + height / 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.)
p.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
// layer transformations on top, with a similar reasoning to the previous comment.
p.SetMultiplicativeAlpha(MathHelper.Clamp(1.75f - 1.5f * dist, 0, 1));
} }
protected override void Update() protected override void Update()
@ -276,80 +414,46 @@ namespace osu.Game.Screens.Select
updatePanel(p, halfHeight); updatePanel(p, halfHeight);
} }
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) /// <summary>
/// Computes the x-offset of currently visible panels. Makes the carousel appear round.
/// </summary>
/// <param name="dist">
/// Vertical distance from the center of the carousel container
/// ranging from -1 to 1.
/// </param>
/// <param name="halfHeight">Half the height of the carousel container.</param>
private static float offsetX(float dist, float halfHeight)
{ {
int direction = 0; // The radius of the circle the carousel moves on.
bool skipDifficulties = false; const float circle_radius = 3;
double discriminant = Math.Max(0, circle_radius * circle_radius - dist * dist);
float x = (circle_radius - (float)Math.Sqrt(discriminant)) * halfHeight;
switch (args.Key) return 125 + x;
}
/// <summary>
/// Update a panel's x position and multiplicative alpha based on its y position and
/// the current scroll position.
/// </summary>
/// <param name="p">The panel to be updated.</param>
/// <param name="halfHeight">Half the draw height of the carousel container.</param>
private void updatePanel(Panel p, float halfHeight)
{ {
case Key.Up: var height = p.IsPresent ? p.DrawHeight : 0;
direction = -1;
break;
case Key.Down:
direction = 1;
break;
case Key.Left:
direction = -1;
skipDifficulties = true;
break;
case Key.Right:
direction = 1;
skipDifficulties = true;
break;
}
if (direction == 0) float panelDrawY = p.Position.Y - Current + height / 2;
return base.OnKeyDown(state, args); float dist = Math.Abs(1f - panelDrawY / halfHeight);
SelectNext(direction, skipDifficulties); // Setting the origin position serves as an additive position on top of potential
return true; // local transformation we may want to apply (e.g. when a panel gets selected, we
} // may want to smoothly transform it leftwards.)
p.OriginPosition = new Vector2(-offsetX(dist, halfHeight), 0);
public void SelectNext(int direction = 1, bool skipDifficulties = true) // 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
if (!skipDifficulties && SelectedGroup != null) // layer transformations on top, with a similar reasoning to the previous comment.
{ p.SetMultiplicativeAlpha(MathHelper.Clamp(1.75f - 1.5f * dist, 0, 1));
int i = SelectedGroup.BeatmapPanels.IndexOf(SelectedPanel) + direction;
if (i >= 0 && i < SelectedGroup.BeatmapPanels.Count)
{
//changing difficulty panel, not set.
selectGroup(SelectedGroup, SelectedGroup.BeatmapPanels[i]);
return;
} }
} }
int startIndex = groups.IndexOf(SelectedGroup);
int index = startIndex;
do
{
index = (index + direction + groups.Count) % groups.Count;
if (groups[index].State != BeatmapGroupState.Hidden)
{
SelectBeatmap(groups[index].BeatmapPanels.First().Beatmap);
return;
}
} while (index != startIndex);
}
public void SelectRandom()
{
List<BeatmapGroup> visibleGroups = groups.Where(selectGroup => selectGroup.State != BeatmapGroupState.Hidden).ToList();
if (visibleGroups.Count < 1)
return;
BeatmapGroup group = visibleGroups[RNG.Next(visibleGroups.Count)];
BeatmapPanel panel = group?.BeatmapPanels.First();
if (panel == null)
return;
selectGroup(group, panel);
}
public IEnumerator<BeatmapGroup> GetEnumerator() => groups.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}
} }

View File

@ -237,14 +237,16 @@ namespace osu.Game.Screens.Select
Icon = FontAwesome.fa_square, Icon = FontAwesome.fa_square,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Colour = new Color4(68, 17, 136, 255), Colour = new Color4(68, 17, 136, 255),
Rotation = 45 Rotation = 45,
TextSize = 20
}, },
new TextAwesome new TextAwesome
{ {
Icon = statistic.Icon, Icon = statistic.Icon,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Colour = new Color4(255, 221, 85, 255), Colour = new Color4(255, 221, 85, 255),
Scale = new Vector2(0.8f) Scale = new Vector2(0.8f),
TextSize = 20
}, },
new OsuSpriteText new OsuSpriteText
{ {

View File

@ -5,6 +5,7 @@ using System;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
@ -15,14 +16,13 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Select.Filter; using osu.Game.Screens.Select.Filter;
using Container = osu.Framework.Graphics.Containers.Container; using Container = osu.Framework.Graphics.Containers.Container;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Modes;
namespace osu.Game.Screens.Select namespace osu.Game.Screens.Select
{ {
public class FilterControl : Container public class FilterControl : Container
{ {
public Action FilterChanged; public Action<FilterCriteria> FilterChanged;
public string Search => searchTextBox.Text;
private OsuTabControl<SortMode> sortTabs; private OsuTabControl<SortMode> sortTabs;
@ -37,7 +37,7 @@ namespace osu.Game.Screens.Select
if (sort != value) if (sort != value)
{ {
sort = value; sort = value;
FilterChanged?.Invoke(); FilterChanged?.Invoke(CreateCriteria());
} }
} }
} }
@ -51,11 +51,19 @@ namespace osu.Game.Screens.Select
if (group != value) if (group != value)
{ {
group = value; group = value;
FilterChanged?.Invoke(); FilterChanged?.Invoke(CreateCriteria());
} }
} }
} }
public FilterCriteria CreateCriteria() => new FilterCriteria
{
Group = group,
Sort = sort,
SearchText = searchTextBox.Text,
Mode = playMode
};
public Action Exit; public Action Exit;
private SearchTextBox searchTextBox; private SearchTextBox searchTextBox;
@ -88,7 +96,7 @@ namespace osu.Game.Screens.Select
OnChange = (sender, newText) => OnChange = (sender, newText) =>
{ {
if (newText) if (newText)
FilterChanged?.Invoke(); FilterChanged?.Invoke(CreateCriteria());
}, },
Exit = () => Exit?.Invoke(), Exit = () => Exit?.Invoke(),
}, },
@ -158,10 +166,15 @@ namespace osu.Game.Screens.Select
searchTextBox.HoldFocus = true; searchTextBox.HoldFocus = true;
} }
[BackgroundDependencyLoader] private readonly Bindable<PlayMode> playMode = new Bindable<PlayMode>();
private void load(OsuColour colours)
[BackgroundDependencyLoader(permitNulls:true)]
private void load(OsuColour colours, OsuGame osu)
{ {
sortTabs.AccentColour = colours.GreenLight; sortTabs.AccentColour = colours.GreenLight;
if (osu != null)
playMode.BindTo(osu.PlayMode);
} }
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;

View File

@ -0,0 +1,65 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Modes;
using osu.Game.Screens.Select.Filter;
namespace osu.Game.Screens.Select
{
public class FilterCriteria
{
public GroupMode Group;
public SortMode Sort;
public string SearchText;
public PlayMode Mode;
public void Filter(List<BeatmapGroup> groups)
{
foreach (var g in groups)
{
var set = g.BeatmapSet;
bool hasCurrentMode = set.Beatmaps.Any(bm => bm.Mode == Mode);
bool match = hasCurrentMode;
match &= string.IsNullOrEmpty(SearchText)
|| (set.Metadata.Artist ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1
|| (set.Metadata.ArtistUnicode ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1
|| (set.Metadata.Title ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1
|| (set.Metadata.TitleUnicode ?? string.Empty).IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) != -1;
switch (g.State)
{
case BeatmapGroupState.Hidden:
if (match) g.State = BeatmapGroupState.Collapsed;
break;
default:
if (!match) g.State = BeatmapGroupState.Hidden;
break;
}
}
switch (Sort)
{
default:
case SortMode.Artist:
groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Artist, y.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase));
break;
case SortMode.Title:
groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Title, y.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase));
break;
case SortMode.Author:
groups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Author, y.BeatmapSet.Metadata.Author, StringComparison.InvariantCultureIgnoreCase));
break;
case SortMode.Difficulty:
groups.Sort((x, y) => x.BeatmapSet.MaxStarDifficulty.CompareTo(y.BeatmapSet.MaxStarDifficulty));
break;
}
}
}
}

View File

@ -19,7 +19,8 @@ namespace osu.Game.Screens.Select.Options
public class BeatmapOptionsOverlay : FocusedOverlayContainer public class BeatmapOptionsOverlay : FocusedOverlayContainer
{ {
private const float transition_duration = 500; private const float transition_duration = 500;
private const float x_position = 290; private const float x_position = 0.2f;
private const float x_movement = 0.8f;
private const float height = 100; private const float height = 100;
@ -30,10 +31,10 @@ namespace osu.Game.Screens.Select.Options
{ {
base.PopIn(); base.PopIn();
if (buttonsContainer.Position.X >= DrawWidth || buttonsContainer.Alpha <= 0) FadeIn(transition_duration, EasingTypes.OutQuint);
buttonsContainer.MoveToX(-buttonsContainer.DrawWidth);
buttonsContainer.Alpha = 1; if (buttonsContainer.Position.X == 1 || Alpha == 0)
buttonsContainer.MoveToX(x_position - x_movement);
holder.ScaleTo(new Vector2(1, 1), transition_duration / 2, EasingTypes.OutQuint); holder.ScaleTo(new Vector2(1, 1), transition_duration / 2, EasingTypes.OutQuint);
@ -47,15 +48,10 @@ namespace osu.Game.Screens.Select.Options
holder.ScaleTo(new Vector2(1, 0), transition_duration / 2, EasingTypes.InSine); holder.ScaleTo(new Vector2(1, 0), transition_duration / 2, EasingTypes.InSine);
buttonsContainer.MoveToX(DrawWidth, transition_duration, EasingTypes.InSine); buttonsContainer.MoveToX(x_position + x_movement, transition_duration, EasingTypes.InSine);
buttonsContainer.TransformSpacingTo(new Vector2(200f, 0f), transition_duration, EasingTypes.InSine); buttonsContainer.TransformSpacingTo(new Vector2(200f, 0f), transition_duration, EasingTypes.InSine);
Delay(transition_duration); FadeOut(transition_duration, EasingTypes.InQuint);
Schedule(() =>
{
if (State == Visibility.Hidden)
buttonsContainer.Alpha = 0;
});
} }
public BeatmapOptionsOverlay() public BeatmapOptionsOverlay()
@ -79,6 +75,7 @@ namespace osu.Game.Screens.Select.Options
buttonsContainer = new ButtonFlow buttonsContainer = new ButtonFlow
{ {
Height = height, Height = height,
RelativePositionAxes = Axes.X,
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.X,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,

View File

@ -26,6 +26,7 @@ namespace osu.Game.Screens.Select
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Margin = new MarginPadding { Right = 10 }, Margin = new MarginPadding { Right = 10 },
TextSize = 20
} }
}); });

View File

@ -2,10 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks;
using OpenTK; using OpenTK;
using OpenTK.Input; using OpenTK.Input;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -19,7 +17,6 @@ using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Transforms; using osu.Framework.Graphics.Transforms;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables;
using osu.Game.Database; using osu.Game.Database;
@ -38,7 +35,7 @@ namespace osu.Game.Screens.Select
private BeatmapDatabase database; private BeatmapDatabase database;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap); protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap);
private CarouselContainer carousel; private BeatmapCarousel carousel;
private TrackManager trackManager; private TrackManager trackManager;
private DialogOverlay dialogOverlay; private DialogOverlay dialogOverlay;
@ -51,8 +48,6 @@ namespace osu.Game.Screens.Select
private SampleChannel sampleChangeDifficulty; private SampleChannel sampleChangeDifficulty;
private SampleChannel sampleChangeBeatmap; private SampleChannel sampleChangeBeatmap;
private List<BeatmapGroup> beatmapGroups;
protected virtual bool ShowFooter => true; protected virtual bool ShowFooter => true;
/// <summary> /// <summary>
@ -72,7 +67,6 @@ namespace osu.Game.Screens.Select
const float carousel_width = 640; const float carousel_width = 640;
const float filter_height = 100; const float filter_height = 100;
beatmapGroups = new List<BeatmapGroup>();
Add(new ParallaxContainer Add(new ParallaxContainer
{ {
Padding = new MarginPadding { Top = filter_height }, Padding = new MarginPadding { Top = filter_height },
@ -87,18 +81,20 @@ namespace osu.Game.Screens.Select
} }
} }
}); });
Add(carousel = new CarouselContainer Add(carousel = new BeatmapCarousel
{ {
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Size = new Vector2(carousel_width, 1), Size = new Vector2(carousel_width, 1),
Anchor = Anchor.CentreRight, Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight, Origin = Anchor.CentreRight,
SelectionChanged = selectionChanged,
StartRequested = raiseSelect
}); });
Add(FilterControl = new FilterControl Add(FilterControl = new FilterControl
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Height = filter_height, Height = filter_height,
FilterChanged = () => filterChanged(), FilterChanged = criteria => filterChanged(criteria),
Exit = Exit, Exit = Exit,
}); });
Add(beatmapInfoWedge = new BeatmapInfoWedge Add(beatmapInfoWedge = new BeatmapInfoWedge
@ -132,8 +128,7 @@ namespace osu.Game.Screens.Select
} }
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader(permitNulls: true)]
private void load(BeatmapDatabase beatmaps, AudioManager audio, DialogOverlay dialog, Framework.Game game, private void load(BeatmapDatabase beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours)
OsuGame osu, OsuColour colours)
{ {
if (Footer != null) if (Footer != null)
{ {
@ -161,7 +156,16 @@ namespace osu.Game.Screens.Select
initialAddSetsTask = new CancellationTokenSource(); initialAddSetsTask = new CancellationTokenSource();
Task.Factory.StartNew(() => addBeatmapSets(game, initialAddSetsTask.Token), initialAddSetsTask.Token); carousel.BeatmapsChanged = beatmapsLoaded;
carousel.Beatmaps = database.Query<BeatmapSetInfo>().Where(b => !b.DeletePending);
}
private void beatmapsLoaded()
{
if (Beatmap != null)
carousel.SelectBeatmap(Beatmap.BeatmapInfo, false);
else
carousel.SelectNext();
} }
private void raiseSelect() private void raiseSelect()
@ -173,61 +177,15 @@ namespace osu.Game.Screens.Select
} }
public void SelectRandom() => carousel.SelectRandom(); public void SelectRandom() => carousel.SelectRandom();
protected abstract void OnSelected(); protected abstract void OnSelected();
private ScheduledDelegate filterTask; private void filterChanged(FilterCriteria criteria, bool debounce = true)
private void filterChanged(bool debounce = true, bool eagerSelection = true)
{ {
filterTask?.Cancel(); carousel.Filter(criteria, debounce);
filterTask = Scheduler.AddDelayed(() =>
{
filterTask = null;
var search = FilterControl.Search;
BeatmapGroup newSelection = null;
carousel.Sort(FilterControl.Sort);
foreach (var beatmapGroup in carousel)
{
var set = beatmapGroup.BeatmapSet;
bool hasCurrentMode = set.Beatmaps.Any(bm => bm.Mode == playMode);
bool match = hasCurrentMode;
match &= string.IsNullOrEmpty(search)
|| (set.Metadata.Artist ?? "").IndexOf(search, StringComparison.InvariantCultureIgnoreCase) != -1
|| (set.Metadata.ArtistUnicode ?? "").IndexOf(search, StringComparison.InvariantCultureIgnoreCase) != -1
|| (set.Metadata.Title ?? "").IndexOf(search, StringComparison.InvariantCultureIgnoreCase) != -1
|| (set.Metadata.TitleUnicode ?? "").IndexOf(search, StringComparison.InvariantCultureIgnoreCase) != -1;
if (match)
{
if (newSelection == null || beatmapGroup.BeatmapSet.OnlineBeatmapSetID == Beatmap.BeatmapSetInfo.OnlineBeatmapSetID)
{
if (newSelection != null)
newSelection.State = BeatmapGroupState.Collapsed;
newSelection = beatmapGroup;
}
else
beatmapGroup.State = BeatmapGroupState.Collapsed;
}
else
{
beatmapGroup.State = BeatmapGroupState.Hidden;
}
} }
if (newSelection != null) private void onBeatmapSetAdded(BeatmapSetInfo s) => carousel.AddBeatmap(s);
{
if (newSelection.BeatmapPanels.Any(b => b.Beatmap.ID == Beatmap.BeatmapInfo.ID))
carousel.SelectBeatmap(Beatmap.BeatmapInfo, false);
else if (eagerSelection)
carousel.SelectBeatmap(newSelection.BeatmapSet.Beatmaps[0], false);
}
}, debounce ? 250 : 0);
}
private void onBeatmapSetAdded(BeatmapSetInfo s) => Schedule(() => addBeatmapSet(s, Game, true));
private void onBeatmapSetRemoved(BeatmapSetInfo s) => Schedule(() => removeBeatmapSet(s)); private void onBeatmapSetRemoved(BeatmapSetInfo s) => Schedule(() => removeBeatmapSet(s));
@ -288,10 +246,7 @@ namespace osu.Game.Screens.Select
initialAddSetsTask.Cancel(); initialAddSetsTask.Cancel();
} }
private void playMode_ValueChanged(object sender, EventArgs e) private void playMode_ValueChanged(object sender, EventArgs e) => carousel.Filter();
{
filterChanged(false);
}
private void changeBackground(WorkingBeatmap beatmap) private void changeBackground(WorkingBeatmap beatmap)
{ {
@ -352,63 +307,18 @@ namespace osu.Game.Screens.Select
} }
} }
private void addBeatmapSet(BeatmapSetInfo beatmapSet, Framework.Game game, bool select = false) private void selectBeatmap(BeatmapSetInfo beatmapSet = null)
{ {
beatmapSet = database.GetWithChildren<BeatmapSetInfo>(beatmapSet.ID); carousel.SelectBeatmap(beatmapSet != null ? beatmapSet.Beatmaps.First() : Beatmap?.BeatmapInfo);
beatmapSet.Beatmaps.ForEach(b =>
{
database.GetChildren(b);
if (b.Metadata == null) b.Metadata = beatmapSet.Metadata;
});
var group = new BeatmapGroup(beatmapSet, database)
{
SelectionChanged = selectionChanged,
StartRequested = b => raiseSelect()
};
//for the time being, let's completely load the difficulty panels in the background.
//this likely won't scale so well, but allows us to completely async the loading flow.
Task.WhenAll(group.BeatmapPanels.Select(panel => panel.LoadAsync(game))).ContinueWith(task => Schedule(delegate
{
beatmapGroups.Add(group);
group.State = BeatmapGroupState.Collapsed;
carousel.AddGroup(group);
filterChanged(false, false);
if (Beatmap == null || select)
carousel.SelectBeatmap(beatmapSet.Beatmaps.First());
else
carousel.SelectBeatmap(Beatmap.BeatmapInfo);
}));
} }
private void removeBeatmapSet(BeatmapSetInfo beatmapSet) private void removeBeatmapSet(BeatmapSetInfo beatmapSet)
{ {
var group = beatmapGroups.Find(b => b.BeatmapSet.ID == beatmapSet.ID); carousel.RemoveBeatmap(beatmapSet);
if (group == null) return; if (carousel.SelectedBeatmap == null)
if (carousel.SelectedGroup == group)
carousel.SelectNext();
beatmapGroups.Remove(group);
carousel.RemoveGroup(group);
if (beatmapGroups.Count == 0)
Beatmap = null; Beatmap = null;
} }
private void addBeatmapSets(Framework.Game game, CancellationToken token)
{
foreach (var beatmapSet in database.Query<BeatmapSetInfo>().Where(b => !b.DeletePending))
{
if (token.IsCancellationRequested) return;
addBeatmapSet(beatmapSet, game);
}
}
private void promptDelete() private void promptDelete()
{ {
if (Beatmap != null) if (Beatmap != null)

View File

@ -160,7 +160,7 @@ namespace osu.Game.Screens.Tournament
Text = "Control Panel", Text = "Control Panel",
TextSize = 22f, TextSize = 22f,
Font = "Exo2.0-Boldd" Font = "Exo2.0-Bold"
}, },
new FillFlowContainer new FillFlowContainer
{ {

View File

@ -75,6 +75,8 @@
<Compile Include="Beatmaps\DifficultyCalculator.cs" /> <Compile Include="Beatmaps\DifficultyCalculator.cs" />
<Compile Include="Beatmaps\IBeatmapCoverter.cs" /> <Compile Include="Beatmaps\IBeatmapCoverter.cs" />
<Compile Include="Beatmaps\IBeatmapProcessor.cs" /> <Compile Include="Beatmaps\IBeatmapProcessor.cs" />
<Compile Include="Beatmaps\Legacy\LegacyBeatmap.cs" />
<Compile Include="Beatmaps\Timing\TimeSignatures.cs" />
<Compile Include="Beatmaps\Timing\TimingInfo.cs" /> <Compile Include="Beatmaps\Timing\TimingInfo.cs" />
<Compile Include="Database\ScoreDatabase.cs" /> <Compile Include="Database\ScoreDatabase.cs" />
<Compile Include="Graphics\Backgrounds\Triangles.cs" /> <Compile Include="Graphics\Backgrounds\Triangles.cs" />
@ -197,7 +199,8 @@
<Compile Include="Screens\Play\PlayerLoader.cs" /> <Compile Include="Screens\Play\PlayerLoader.cs" />
<Compile Include="Screens\Play\SkipButton.cs" /> <Compile Include="Screens\Play\SkipButton.cs" />
<Compile Include="Modes\UI\StandardComboCounter.cs" /> <Compile Include="Modes\UI\StandardComboCounter.cs" />
<Compile Include="Screens\Select\CarouselContainer.cs" /> <Compile Include="Screens\Select\BeatmapCarousel.cs" />
<Compile Include="Screens\Select\FilterCriteria.cs" />
<Compile Include="Screens\Select\Filter\GroupMode.cs" /> <Compile Include="Screens\Select\Filter\GroupMode.cs" />
<Compile Include="Screens\Select\Filter\SortMode.cs" /> <Compile Include="Screens\Select\Filter\SortMode.cs" />
<Compile Include="Screens\Select\MatchSongSelect.cs" /> <Compile Include="Screens\Select\MatchSongSelect.cs" />