1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-16 11:13:02 +08:00

Merge branch 'master' into taiko_bash_drawable

Conflicts:
	osu.Game.Modes.Taiko/osu.Game.Modes.Taiko.csproj
This commit is contained in:
smoogipooo 2017-03-20 18:20:32 +09:00
commit 3f7cd41490
51 changed files with 843 additions and 454 deletions

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

@ -1 +1 @@
Subproject commit 4f9ed4e703777ede98737c7e2af31efa4694c395
Subproject commit f85c594c182db2b01233e29ca52639b7baa00402

View File

@ -105,6 +105,9 @@
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- 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.

View File

@ -1,7 +1,10 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Screens.Testing;
using osu.Game.Graphics;
using osu.Game.Screens.Select.Options;
namespace osu.Desktop.VisualTests.Tests
@ -16,6 +19,11 @@ namespace osu.Desktop.VisualTests.Tests
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);
AddButton(@"Toggle", overlay.ToggleVisibility);

View File

@ -144,5 +144,12 @@ namespace osu.Desktop.VisualTests.Tests
if (proxyable != null)
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>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Platform;
using osu.Framework.Screens.Testing;
using osu.Game;
using osu.Game.Screens.Backgrounds;
@ -19,5 +20,11 @@ namespace osu.Desktop.VisualTests
// we depend on some dependencies to be loaded within OsuGameBase.load().
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;
if (desktopWindow != null)
{
desktopWindow.CursorState = CursorState.Hidden;
desktopWindow.Icon = Icon.ExtractAssociatedIcon(Assembly.GetExecutingAssembly().Location);
desktopWindow.Title = Name;

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +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
namespace osu.Game.Modes.Osu.Objects
{
public enum HitObjectType
{
Circle,
Slider,
Spinner,
SliderTick
}
}

View File

@ -36,8 +36,6 @@ namespace osu.Game.Modes.Osu.Objects
public float Scale { get; set; } = 1;
public abstract HitObjectType Type { get; }
public Color4 ComboColour { get; set; }
public virtual bool NewCombo { 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 decay = Math.Pow(DECAY_BASE[(int)type], timeElapsed / 1000);
if (BaseHitObject.Type == HitObjectType.Spinner)
if (BaseHitObject is Spinner)
{
// Do nothing for spinners
}
else if (BaseHitObject.Type == HitObjectType.Slider)
else if (BaseHitObject is Slider)
{
switch (type)
{
@ -180,7 +180,7 @@ namespace osu.Game.Modes.Osu.Objects
break;
}
}
else if (BaseHitObject.Type == HitObjectType.Circle)
else if (BaseHitObject is HitCircle)
{
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 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 Duration => EndTime - StartTime;
public override HitObjectType Type => HitObjectType.Spinner;
public override bool NewCombo => true;
}
}

View File

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

View File

@ -12,7 +12,6 @@ using osu.Game.Modes.UI;
using System.Linq;
using osu.Game.Graphics.Cursor;
using osu.Game.Modes.Osu.Judgements;
using OpenTK.Graphics;
namespace osu.Game.Modes.Osu.UI
{
@ -63,7 +62,7 @@ namespace osu.Game.Modes.Osu.UI
protected override void LoadComplete()
{
base.LoadComplete();
AddInternal(new OsuCursorContainer { Colour = Color4.LightYellow });
AddInternal(new GameplayCursor());
}
public override void Add(DrawableHitObject<OsuHitObject, OsuJudgementInfo> h)

View File

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

View File

@ -1,7 +1,6 @@
// 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.Samples;
using osu.Game.Beatmaps.Timing;
using osu.Game.Database;
using osu.Game.Modes.Objects;
@ -40,23 +39,6 @@ namespace osu.Game.Modes.Taiko.Objects
/// </summary>
public bool Kiai;
/// <summary>
/// The type of HitObject.
/// </summary>
public virtual TaikoHitType Type
{
get
{
SampleType st = Sample?.Type ?? SampleType.None;
return
// Centre/Rim
((st & ~(SampleType.Finish | SampleType.Normal)) == 0 ? TaikoHitType.CentreHit : TaikoHitType.RimHit)
// Finisher
| ((st & SampleType.Finish) > 0 ? TaikoHitType.Finisher : TaikoHitType.None);
}
}
public override void ApplyDefaults(TimingInfo timing, BeatmapDifficulty difficulty)
{
base.ApplyDefaults(timing, difficulty);

View File

@ -1,21 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
namespace osu.Game.Modes.Taiko.Objects
{
[Flags]
public enum TaikoHitType
{
None = 0,
CentreHit = 1 << 0,
RimHit = 1 << 1,
DrumRoll = 1 << 2,
DrumRollTick = 1 << 3,
Bash = 1 << 4,
Finisher = 1 << 5,
Hit = CentreHit | RimHit
}
}

View File

@ -54,7 +54,6 @@
<Compile Include="Objects\Bash.cs" />
<Compile Include="Objects\Drawable\DrawableBash.cs" />
<Compile Include="Objects\Drawable\DrawableTaikoHitObject.cs" />
<Compile Include="Objects\TaikoHitType.cs" />
<Compile Include="TaikoDifficultyCalculator.cs" />
<Compile Include="Objects\TaikoHitObject.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />

View File

@ -55,5 +55,14 @@ namespace osu.Game.Beatmaps
/// </summary>
/// <returns>The star difficulty.</returns>
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
{
internal class BeatmapGroup : IStateful<BeatmapGroupState>
public class BeatmapGroup : IStateful<BeatmapGroupState>
{
public BeatmapPanel SelectedPanel;
@ -63,8 +63,6 @@ namespace osu.Game.Beatmaps.Drawables
{
BeatmapSet = beatmapSet;
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)
{

View File

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

View File

@ -17,7 +17,7 @@ using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Drawables
{
internal class BeatmapSetHeader : Panel
public class BeatmapSetHeader : Panel
{
public Action<BeatmapSetHeader> GainedSelection;
private SpriteText title, artist;
@ -32,10 +32,6 @@ namespace osu.Game.Beatmaps.Drawables
Children = new Drawable[]
{
new PanelBackground(beatmap)
{
RelativeSizeAxes = Axes.Both,
},
new FillFlowContainer
{
Direction = FillDirection.Vertical,
@ -74,13 +70,23 @@ namespace osu.Game.Beatmaps.Drawables
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
private void load(OsuConfigManager config, OsuGameBase game)
{
this.config = config;
preferUnicode = config.GetBindable<bool>(OsuConfig.ShowUnicode);
preferUnicode.ValueChanged += preferUnicode_changed;
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)
@ -98,16 +104,18 @@ namespace osu.Game.Beatmaps.Drawables
private class PanelBackground : BufferedContainer
{
private readonly WorkingBeatmap working;
public PanelBackground(WorkingBeatmap working)
{
this.working = working;
CacheDrawnFrameBuffer = true;
Children = new[]
Children = new Drawable[]
{
new BeatmapBackgroundSprite(working)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
FillMode = FillMode.Fill,
},
new FillFlowContainer
{
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)

View File

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

View File

@ -10,6 +10,7 @@ using osu.Game.Beatmaps.Samples;
using osu.Game.Beatmaps.Timing;
using osu.Game.Modes;
using osu.Game.Modes.Objects;
using osu.Game.Beatmaps.Legacy;
namespace osu.Game.Beatmaps.Formats
{
@ -244,6 +245,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)
{
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

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

View File

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

View File

@ -24,7 +24,7 @@ namespace osu.Game.Database
[OneToMany(CascadeOperations = CascadeOperation.All)]
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; }

View File

@ -0,0 +1,131 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Input;
using osu.Game.Configuration;
using System;
namespace osu.Game.Graphics.Cursor
{
public class GameplayCursor : CursorContainer
{
protected override Drawable CreateCursor() => new OsuCursor();
public GameplayCursor()
{
Add(new CursorTrail { Depth = 1 });
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
ActiveCursor.Scale = new Vector2(1);
ActiveCursor.ScaleTo(1.2f, 100, EasingTypes.OutQuad);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
if (!state.Mouse.HasMainButtonPressed)
ActiveCursor.ScaleTo(1, 200, EasingTypes.OutQuad);
return base.OnMouseUp(state, args);
}
public class OsuCursor : Container
{
private Container cursorContainer;
private Bindable<double> cursorScale;
public OsuCursor()
{
Origin = Anchor.Centre;
Size = new Vector2(42);
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
cursorScale = config.GetBindable<double>(OsuConfig.CursorSize);
Children = new Drawable[]
{
cursorContainer = new CircularContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Scale = new Vector2((float)cursorScale),
Masking = true,
BorderThickness = Size.X / 6,
BorderColour = Color4.White,
EdgeEffect = new EdgeEffect
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Pink.Opacity(0.5f),
Radius = 5,
},
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true,
},
new CircularContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = Size.X / 3,
BorderColour = Color4.White.Opacity(0.5f),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true,
},
},
},
new CircularContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Scale = new Vector2(0.1f),
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
},
},
}
},
};
cursorScale.ValueChanged += scaleChanged;
}
private void scaleChanged(object sender, EventArgs e)
{
cursorContainer.Scale = new Vector2((float)cursorScale);
}
}
}
}

View File

@ -0,0 +1,134 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Configuration;
using System;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Transforms;
namespace osu.Game.Graphics.Cursor
{
public class MenuCursor : CursorContainer
{
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)
{
ActiveCursor.Scale = new Vector2(1);
ActiveCursor.ScaleTo(0.90f, 800, EasingTypes.OutQuint);
((Cursor)ActiveCursor).AdditiveLayer.Alpha = 0;
((Cursor)ActiveCursor).AdditiveLayer.FadeInFromZero(800, EasingTypes.OutQuint);
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
if (!state.Mouse.HasMainButtonPressed)
{
((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, EasingTypes.OutQuint);
ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), EasingTypes.OutElasticHalf);
ActiveCursor.ScaleTo(1, 500, EasingTypes.OutElastic);
}
return base.OnMouseUp(state, args);
}
protected override bool OnClick(InputState state)
{
((Cursor)ActiveCursor).AdditiveLayer.FadeOutFromOne(500, EasingTypes.OutQuint);
return base.OnClick(state);
}
protected override void PopIn()
{
ActiveCursor.FadeTo(1, 250, EasingTypes.OutQuint);
ActiveCursor.ScaleTo(1, 1000, EasingTypes.OutElastic);
}
protected override void PopOut()
{
ActiveCursor.FadeTo(0, 1400, EasingTypes.OutQuint);
ActiveCursor.ScaleTo(1.1f, 100, EasingTypes.Out);
ActiveCursor.Delay(100);
ActiveCursor.ScaleTo(0, 500, EasingTypes.In);
}
public class Cursor : Container
{
private Container cursorContainer;
private Bindable<double> cursorScale;
public Sprite AdditiveLayer;
public Cursor()
{
Size = new Vector2(42);
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, TextureStore textures, OsuColour colour)
{
cursorScale = config.GetBindable<double>(OsuConfig.CursorSize);
Children = new Drawable[]
{
cursorContainer = new Container
{
Size = new Vector2(32),
Children = new Drawable[]
{
new Sprite
{
FillMode = FillMode.Fit,
Texture = textures.Get(@"Cursor/menu-cursor"),
},
AdditiveLayer = new Sprite
{
FillMode = FillMode.Fit,
BlendingMode = BlendingMode.Additive,
Colour = colour.Pink,
Alpha = 0,
Texture = textures.Get(@"Cursor/menu-cursor-additive"),
},
}
}
};
cursorScale.ValueChanged += scaleChanged;
}
private void scaleChanged(object sender, EventArgs e)
{
cursorContainer.Scale = new Vector2((float)cursorScale);
}
}
}
}

View File

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

View File

@ -220,7 +220,7 @@ namespace osu.Game
}
};
Cursor.Alpha = 0;
Cursor.State = Visibility.Hidden;
}
private bool globalHotkeyPressed(InputState state, KeyDownEventArgs args)
@ -264,10 +264,20 @@ namespace osu.Game
private Container overlayContent;
private OsuScreen currentScreen;
private void screenChanged(Screen newScreen)
{
currentScreen = newScreen as OsuScreen;
if (currentScreen == null)
{
Exit();
return;
}
//central game mode change logic.
if ((newScreen as OsuScreen)?.ShowOverlays != true)
if (!currentScreen.ShowOverlays)
{
Toolbar.State = Visibility.Hidden;
musicController.State = Visibility.Hidden;
@ -278,13 +288,7 @@ namespace osu.Game
Toolbar.State = Visibility.Visible;
}
if (newScreen is MainMenu)
Cursor.FadeIn(100);
ScreenChanged?.Invoke(newScreen);
if (newScreen == null)
Exit();
}
protected override bool OnExiting()
@ -308,6 +312,8 @@ namespace osu.Game
if (intro?.ChildScreen != null)
intro.ChildScreen.Padding = new MarginPadding { Top = Toolbar.Position.Y + Toolbar.DrawHeight };
Cursor.State = currentScreen == null || currentScreen.HasLocalCursorDisplayed ? Visibility.Hidden : Visibility.Visible;
}
private void screenAdded(Screen newScreen)

View File

@ -8,7 +8,6 @@ using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.IO.Stores;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
@ -38,7 +37,7 @@ namespace osu.Game
private RatioAdjust ratioContainer;
protected CursorContainer Cursor;
protected MenuCursor Cursor;
public readonly Bindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();
@ -137,7 +136,7 @@ namespace osu.Game
{
Children = new[]
{
Cursor = new OsuCursorContainer { Depth = float.MinValue }
Cursor = new MenuCursor { Depth = float.MinValue }
}
});
}

View File

@ -65,7 +65,7 @@ namespace osu.Game.Overlays
Vector2 change = state.Mouse.Position - state.Mouse.PositionMouseDown.Value;
// 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);
return base.OnDrag(state);

View File

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

View File

@ -28,6 +28,8 @@ namespace osu.Game.Screens.Menu
private SampleChannel seeya;
private Track bgm;
internal override bool HasLocalCursorDisplayed => true;
internal override bool ShowOverlays => false;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenEmpty();

View File

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

View File

@ -24,6 +24,8 @@ namespace osu.Game.Screens
protected new OsuGameBase Game => base.Game as OsuGameBase;
internal virtual bool HasLocalCursorDisplayed => false;
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
public WorkingBeatmap Beatmap

View File

@ -78,6 +78,8 @@ namespace osu.Game.Screens.Play
// Don't let mouse down events through the overlay or people can click circles while paused.
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
protected override bool OnMouseMove(InputState state) => true;
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (args.Key == Key.Escape)

View File

@ -32,6 +32,10 @@ namespace osu.Game.Screens.Play
internal override bool ShowOverlays => false;
internal override bool HasLocalCursorDisplayed => !hasReplayLoaded && !IsPaused;
private bool hasReplayLoaded => hitRenderer.InputManager.ReplayInputHandler != null;
public BeatmapInfo BeatmapInfo;
public bool IsPaused { get; private set; }
@ -304,7 +308,7 @@ namespace osu.Game.Screens.Play
{
if (pauseOverlay == null) return false;
if (hitRenderer.InputManager.ReplayInputHandler != null)
if (hasReplayLoaded)
return false;
if (pauseOverlay.State != Visibility.Visible && !canPause) return true;

View File

@ -15,47 +15,234 @@ using OpenTK.Input;
using System.Collections;
using osu.Framework.MathUtils;
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
{
internal class CarouselContainer : ScrollContainer, IEnumerable<BeatmapGroup>
internal class BeatmapCarousel : ScrollContainer, IEnumerable<BeatmapGroup>
{
private Container<Panel> scrollableContent;
private List<BeatmapGroup> groups = new List<BeatmapGroup>();
private List<Panel> panels = new List<Panel>();
public BeatmapInfo SelectedBeatmap => selectedPanel?.Beatmap;
public BeatmapGroup SelectedGroup { get; private set; }
public BeatmapPanel SelectedPanel { get; private set; }
public Action BeatmapsChanged;
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>();
public CarouselContainer()
{
DistanceDecayJump = 0.01;
/// <summary>
/// Required for now unfortunately.
/// </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>
{
RelativeSizeAxes = Axes.X,
});
}
public void AddGroup(BeatmapGroup group)
public void AddBeatmap(BeatmapSetInfo beatmapSet)
{
groups.Add(group);
var group = createGroup(beatmapSet);
panels.Add(group.Header);
group.Header.UpdateClock(Clock);
foreach (BeatmapPanel panel in group.BeatmapPanels)
//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.
Schedule(delegate
{
panels.Add(panel);
panel.UpdateClock(Clock);
}
computeYPositions();
addGroup(group);
computeYPositions();
if (selectedGroup == null)
selectGroup(group);
});
}
public void RemoveGroup(BeatmapGroup 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();
if (selectedGroup == null || selectedGroup.State == BeatmapGroupState.Hidden)
SelectNext();
};
filterTask?.Cancel();
if (debounce)
filterTask = Scheduler.AddDelayed(perform, 250);
else
perform();
}
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);
panels.Remove(group.Header);
@ -65,18 +252,12 @@ namespace osu.Game.Screens.Select
scrollableContent.Remove(group.Header);
scrollableContent.Remove(group.BeatmapPanels);
if (selectedGroup == group)
SelectNext();
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>
/// Computes the target Y positions for every panel in the carousel.
/// </summary>
@ -99,7 +280,7 @@ namespace osu.Game.Screens.Select
foreach (BeatmapPanel panel in group.BeatmapPanels)
{
if (panel == SelectedPanel)
if (panel == selectedPanel)
selectedY = currentY + panel.DrawHeight / 2 - DrawHeight / 2;
panel.MoveToX(-50, 500, EasingTypes.OutExpo);
@ -129,105 +310,62 @@ namespace osu.Game.Screens.Select
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)
{
var panel = group.BeatmapPanels.FirstOrDefault(p => p.Beatmap.Equals(beatmap));
if (panel != null)
{
selectGroup(group, panel, animated);
return;
}
}
yPositions.Add(currentY);
panel.MoveToY(currentY, animated ? 750 : 0, EasingTypes.OutExpo);
if (advance)
currentY += panel.DrawHeight + 5;
}
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");
if (SelectedGroup != null && SelectedGroup != group && SelectedGroup.State != BeatmapGroupState.Hidden)
SelectedGroup.State = BeatmapGroupState.Collapsed;
if (selectedGroup != null && selectedGroup != group && selectedGroup.State != BeatmapGroupState.Hidden)
selectedGroup.State = BeatmapGroupState.Collapsed;
group.State = BeatmapGroupState.Expanded;
SelectedGroup = group;
selectedGroup = group;
panel.State = PanelSelectedState.Selected;
SelectedPanel = panel;
selectedPanel = panel;
float selectedY = computeYPositions(animated);
ScrollTo(selectedY, animated);
}
public void Sort(SortMode mode)
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
List<BeatmapGroup> sortedGroups = new List<BeatmapGroup>(groups);
switch (mode)
int direction = 0;
bool skipDifficulties = false;
switch (args.Key)
{
case SortMode.Artist:
sortedGroups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Artist, y.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase));
case Key.Up:
direction = -1;
break;
case SortMode.Title:
sortedGroups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Title, y.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase));
case Key.Down:
direction = 1;
break;
case SortMode.Author:
sortedGroups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Author, y.BeatmapSet.Metadata.Author, StringComparison.InvariantCultureIgnoreCase));
case Key.Left:
direction = -1;
skipDifficulties = true;
break;
case SortMode.Difficulty:
sortedGroups.Sort((x, y) => x.BeatmapSet.MaxStarDifficulty.CompareTo(y.BeatmapSet.MaxStarDifficulty));
break;
default:
Sort(SortMode.Artist); // Temporary
case Key.Right:
direction = 1;
skipDifficulties = true;
break;
}
scrollableContent.Clear(false);
panels.Clear();
groups.Clear();
if (direction == 0)
return base.OnKeyDown(state, args);
foreach (BeatmapGroup group in sortedGroups)
AddGroup(group);
}
/// <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));
SelectNext(direction, skipDifficulties);
return true;
}
protected override void Update()
@ -276,80 +414,46 @@ namespace osu.Game.Screens.Select
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;
bool skipDifficulties = false;
// 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;
switch (args.Key)
{
case Key.Up:
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)
return base.OnKeyDown(state, args);
SelectNext(direction, skipDifficulties);
return true;
return 125 + x;
}
public void SelectNext(int direction = 1, bool skipDifficulties = true)
/// <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)
{
if (!skipDifficulties && SelectedGroup != null)
{
int i = SelectedGroup.BeatmapPanels.IndexOf(SelectedPanel) + direction;
var height = p.IsPresent ? p.DrawHeight : 0;
if (i >= 0 && i < SelectedGroup.BeatmapPanels.Count)
{
//changing difficulty panel, not set.
selectGroup(SelectedGroup, SelectedGroup.BeatmapPanels[i]);
return;
}
}
float panelDrawY = p.Position.Y - Current + height / 2;
float dist = Math.Abs(1f - panelDrawY / halfHeight);
int startIndex = groups.IndexOf(SelectedGroup);
int index = startIndex;
// 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);
do
{
index = (index + direction + groups.Count) % groups.Count;
if (groups[index].State != BeatmapGroupState.Hidden)
{
SelectBeatmap(groups[index].BeatmapPanels.First().Beatmap);
return;
}
} while (index != startIndex);
// 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));
}
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

@ -5,6 +5,7 @@ using System;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
@ -15,14 +16,13 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Select.Filter;
using Container = osu.Framework.Graphics.Containers.Container;
using osu.Framework.Input;
using osu.Game.Modes;
namespace osu.Game.Screens.Select
{
public class FilterControl : Container
{
public Action FilterChanged;
public string Search => searchTextBox.Text;
public Action<FilterCriteria> FilterChanged;
private OsuTabControl<SortMode> sortTabs;
@ -30,16 +30,16 @@ namespace osu.Game.Screens.Select
private SortMode sort = SortMode.Title;
public SortMode Sort
{
get { return sort; }
{
get { return sort; }
set
{
if (sort != value)
{
sort = value;
FilterChanged?.Invoke();
FilterChanged?.Invoke(CreateCriteria());
}
}
}
}
private GroupMode group = GroupMode.All;
@ -51,11 +51,19 @@ namespace osu.Game.Screens.Select
if (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;
private SearchTextBox searchTextBox;
@ -88,7 +96,7 @@ namespace osu.Game.Screens.Select
OnChange = (sender, newText) =>
{
if (newText)
FilterChanged?.Invoke();
FilterChanged?.Invoke(CreateCriteria());
},
Exit = () => Exit?.Invoke(),
},
@ -152,16 +160,21 @@ namespace osu.Game.Screens.Select
searchTextBox.HoldFocus = false;
searchTextBox.TriggerFocusLost();
}
public void Activate()
{
searchTextBox.HoldFocus = true;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private readonly Bindable<PlayMode> playMode = new Bindable<PlayMode>();
[BackgroundDependencyLoader(permitNulls:true)]
private void load(OsuColour colours, OsuGame osu)
{
sortTabs.AccentColour = colours.GreenLight;
if (osu != null)
playMode.BindTo(osu.PlayMode);
}
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
{
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;
@ -30,10 +31,10 @@ namespace osu.Game.Screens.Select.Options
{
base.PopIn();
if (buttonsContainer.Position.X >= DrawWidth || buttonsContainer.Alpha <= 0)
buttonsContainer.MoveToX(-buttonsContainer.DrawWidth);
FadeIn(transition_duration, EasingTypes.OutQuint);
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);
@ -47,15 +48,10 @@ namespace osu.Game.Screens.Select.Options
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);
Delay(transition_duration);
Schedule(() =>
{
if (State == Visibility.Hidden)
buttonsContainer.Alpha = 0;
});
FadeOut(transition_duration, EasingTypes.InQuint);
}
public BeatmapOptionsOverlay()
@ -79,6 +75,7 @@ namespace osu.Game.Screens.Select.Options
buttonsContainer = new ButtonFlow
{
Height = height,
RelativePositionAxes = Axes.X,
AutoSizeAxes = Axes.X,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,

View File

@ -2,10 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using OpenTK;
using OpenTK.Input;
using osu.Framework.Allocation;
@ -19,7 +17,6 @@ using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Input;
using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Database;
@ -38,7 +35,7 @@ namespace osu.Game.Screens.Select
private BeatmapDatabase database;
protected override BackgroundScreen CreateBackground() => new BackgroundScreenBeatmap(Beatmap);
private CarouselContainer carousel;
private BeatmapCarousel carousel;
private TrackManager trackManager;
private DialogOverlay dialogOverlay;
@ -51,8 +48,6 @@ namespace osu.Game.Screens.Select
private SampleChannel sampleChangeDifficulty;
private SampleChannel sampleChangeBeatmap;
private List<BeatmapGroup> beatmapGroups;
protected virtual bool ShowFooter => true;
/// <summary>
@ -72,7 +67,6 @@ namespace osu.Game.Screens.Select
const float carousel_width = 640;
const float filter_height = 100;
beatmapGroups = new List<BeatmapGroup>();
Add(new ParallaxContainer
{
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,
Size = new Vector2(carousel_width, 1),
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
SelectionChanged = selectionChanged,
StartRequested = raiseSelect
});
Add(FilterControl = new FilterControl
{
RelativeSizeAxes = Axes.X,
Height = filter_height,
FilterChanged = () => filterChanged(),
FilterChanged = criteria => filterChanged(criteria),
Exit = Exit,
});
Add(beatmapInfoWedge = new BeatmapInfoWedge
@ -132,8 +128,7 @@ namespace osu.Game.Screens.Select
}
[BackgroundDependencyLoader(permitNulls: true)]
private void load(BeatmapDatabase beatmaps, AudioManager audio, DialogOverlay dialog, Framework.Game game,
OsuGame osu, OsuColour colours)
private void load(BeatmapDatabase beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours)
{
if (Footer != null)
{
@ -161,7 +156,16 @@ namespace osu.Game.Screens.Select
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()
@ -173,61 +177,15 @@ namespace osu.Game.Screens.Select
}
public void SelectRandom() => carousel.SelectRandom();
protected abstract void OnSelected();
private ScheduledDelegate filterTask;
private void filterChanged(bool debounce = true, bool eagerSelection = true)
private void filterChanged(FilterCriteria criteria, bool debounce = true)
{
filterTask?.Cancel();
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)
{
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);
carousel.Filter(criteria, debounce);
}
private void onBeatmapSetAdded(BeatmapSetInfo s) => Schedule(() => addBeatmapSet(s, Game, true));
private void onBeatmapSetAdded(BeatmapSetInfo s) => carousel.AddBeatmap(s);
private void onBeatmapSetRemoved(BeatmapSetInfo s) => Schedule(() => removeBeatmapSet(s));
@ -288,10 +246,7 @@ namespace osu.Game.Screens.Select
initialAddSetsTask.Cancel();
}
private void playMode_ValueChanged(object sender, EventArgs e)
{
filterChanged(false);
}
private void playMode_ValueChanged(object sender, EventArgs e) => carousel.Filter();
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);
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);
}));
carousel.SelectBeatmap(beatmapSet != null ? beatmapSet.Beatmaps.First() : Beatmap?.BeatmapInfo);
}
private void removeBeatmapSet(BeatmapSetInfo beatmapSet)
{
var group = beatmapGroups.Find(b => b.BeatmapSet.ID == beatmapSet.ID);
if (group == null) return;
if (carousel.SelectedGroup == group)
carousel.SelectNext();
beatmapGroups.Remove(group);
carousel.RemoveGroup(group);
if (beatmapGroups.Count == 0)
carousel.RemoveBeatmap(beatmapSet);
if (carousel.SelectedBeatmap == 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()
{
if (Beatmap != null)

View File

@ -75,10 +75,12 @@
<Compile Include="Beatmaps\DifficultyCalculator.cs" />
<Compile Include="Beatmaps\IBeatmapCoverter.cs" />
<Compile Include="Beatmaps\IBeatmapProcessor.cs" />
<Compile Include="Beatmaps\Legacy\LegacyBeatmap.cs" />
<Compile Include="Beatmaps\Timing\TimingInfo.cs" />
<Compile Include="Database\ScoreDatabase.cs" />
<Compile Include="Graphics\Backgrounds\Triangles.cs" />
<Compile Include="Graphics\Cursor\CursorTrail.cs" />
<Compile Include="Graphics\Cursor\GameplayCursor.cs" />
<Compile Include="Graphics\Sprites\OsuSpriteText.cs" />
<Compile Include="Graphics\UserInterface\BackButton.cs" />
<Compile Include="Graphics\UserInterface\FocusedTextBox.cs" />
@ -196,7 +198,8 @@
<Compile Include="Screens\Play\PlayerLoader.cs" />
<Compile Include="Screens\Play\SkipButton.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\SortMode.cs" />
<Compile Include="Screens\Select\MatchSongSelect.cs" />
@ -221,7 +224,7 @@
<Compile Include="Input\GlobalHotkeys.cs" />
<Compile Include="Graphics\Backgrounds\Background.cs" />
<Compile Include="Graphics\Containers\ParallaxContainer.cs" />
<Compile Include="Graphics\Cursor\OsuCursorContainer.cs" />
<Compile Include="Graphics\Cursor\MenuCursor.cs" />
<Compile Include="Graphics\Processing\RatioAdjust.cs" />
<Compile Include="Graphics\TextAwesome.cs" />
<Compile Include="Screens\Play\KeyCounter.cs" />