Merge branch 'master' into ranks-section

This commit is contained in:
Dean Herbert
2017-09-01 18:46:53 +09:00
committed by GitHub
91 changed files with 1978 additions and 1194 deletions

View File

@ -14,7 +14,7 @@ namespace osu.Desktop.Tests.Visual
[Test] [Test]
public override void RunTest() public override void RunTest()
{ {
using (var host = new HeadlessGameHost()) using (var host = new HeadlessGameHost(realtime: false))
host.Run(new OsuTestCaseTestRunner(this)); host.Run(new OsuTestCaseTestRunner(this));
} }

View File

@ -69,28 +69,28 @@ namespace osu.Desktop.Tests.Visual
private class MyContextMenuContainer : Container, IHasContextMenu private class MyContextMenuContainer : Container, IHasContextMenu
{ {
public ContextMenuItem[] ContextMenuItems => new ContextMenuItem[] public MenuItem[] ContextMenuItems => new MenuItem[]
{ {
new OsuContextMenuItem(@"Some option"), new OsuMenuItem(@"Some option"),
new OsuContextMenuItem(@"Highlighted option", MenuItemType.Highlighted), new OsuMenuItem(@"Highlighted option", MenuItemType.Highlighted),
new OsuContextMenuItem(@"Another option"), new OsuMenuItem(@"Another option"),
new OsuContextMenuItem(@"Choose me please"), new OsuMenuItem(@"Choose me please"),
new OsuContextMenuItem(@"And me too"), new OsuMenuItem(@"And me too"),
new OsuContextMenuItem(@"Trying to fill"), new OsuMenuItem(@"Trying to fill"),
new OsuContextMenuItem(@"Destructive option", MenuItemType.Destructive), new OsuMenuItem(@"Destructive option", MenuItemType.Destructive),
}; };
} }
private class AnotherContextMenuContainer : Container, IHasContextMenu private class AnotherContextMenuContainer : Container, IHasContextMenu
{ {
public ContextMenuItem[] ContextMenuItems => new ContextMenuItem[] public MenuItem[] ContextMenuItems => new MenuItem[]
{ {
new OsuContextMenuItem(@"Simple option"), new OsuMenuItem(@"Simple option"),
new OsuContextMenuItem(@"Simple very very long option"), new OsuMenuItem(@"Simple very very long option"),
new OsuContextMenuItem(@"Change width", MenuItemType.Highlighted) { Action = () => this.ResizeWidthTo(Width * 2, 100, Easing.OutQuint) }, new OsuMenuItem(@"Change width", MenuItemType.Highlighted, () => this.ResizeWidthTo(Width * 2, 100, Easing.OutQuint)),
new OsuContextMenuItem(@"Change height", MenuItemType.Highlighted) { Action = () => this.ResizeHeightTo(Height * 2, 100, Easing.OutQuint) }, new OsuMenuItem(@"Change height", MenuItemType.Highlighted, () => this.ResizeHeightTo(Height * 2, 100, Easing.OutQuint)),
new OsuContextMenuItem(@"Change width back", MenuItemType.Destructive) { Action = () => this.ResizeWidthTo(Width / 2, 100, Easing.OutQuint) }, new OsuMenuItem(@"Change width back", MenuItemType.Destructive, () => this.ResizeWidthTo(Width / 2, 100, Easing.OutQuint)),
new OsuContextMenuItem(@"Change height back", MenuItemType.Destructive) { Action = () => this.ResizeHeightTo(Height / 2, 100, Easing.OutQuint) }, new OsuMenuItem(@"Change height back", MenuItemType.Destructive, () => this.ResizeHeightTo(Height / 2, 100, Easing.OutQuint)),
}; };
} }
} }

View File

@ -3,6 +3,7 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using OpenTK; using OpenTK;
@ -40,8 +41,8 @@ namespace osu.Desktop.Tests.Visual
RelativeChildSize = new Vector2(1, 10000), RelativeChildSize = new Vector2(1, 10000),
Children = new[] Children = new[]
{ {
new DrawableNote(new Note { StartTime = 5000 }) { AccentColour = Color4.Red }, new DrawableNote(new Note { StartTime = 5000 }, ManiaAction.Key1) { AccentColour = Color4.Red },
new DrawableNote(new Note { StartTime = 6000 }) { AccentColour = Color4.Red } new DrawableNote(new Note { StartTime = 6000 }, ManiaAction.Key1) { AccentColour = Color4.Red }
} }
} }
} }
@ -66,7 +67,7 @@ namespace osu.Desktop.Tests.Visual
{ {
StartTime = 5000, StartTime = 5000,
Duration = 1000 Duration = 1000
}) { AccentColour = Color4.Red } }, ManiaAction.Key1) { AccentColour = Color4.Red }
} }
} }
} }

View File

@ -1,111 +1,34 @@
// 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 System;
using System.Linq; using System.Linq;
using osu.Framework.Configuration; using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Timing; using osu.Game.Rulesets.Mania.Timing;
using osu.Game.Rulesets.Mania.UI; using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.Timing;
using OpenTK; using osu.Game.Rulesets;
using OpenTK.Input;
namespace osu.Desktop.Tests.Visual namespace osu.Desktop.Tests.Visual
{ {
internal class TestCaseManiaPlayfield : OsuTestCase internal class TestCaseManiaPlayfield : OsuTestCase
{ {
private const double start_time = 500;
private const double duration = 500;
public override string Description => @"Mania playfield"; public override string Description => @"Mania playfield";
protected override double TimePerAction => 200; protected override double TimePerAction => 200;
private RulesetInfo maniaRuleset;
public TestCaseManiaPlayfield() public TestCaseManiaPlayfield()
{ {
Action<int, SpecialColumnPosition> createPlayfield = (cols, pos) =>
{
Clear();
Add(new ManiaPlayfield(cols)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
SpecialColumnPosition = pos,
Scale = new Vector2(1, -1)
});
};
const double start_time = 500;
const double duration = 500;
Func<double, bool, SpeedAdjustmentContainer> createTimingChange = (time, gravity) => new ManiaSpeedAdjustmentContainer(new MultiplierControlPoint(time)
{
TimingPoint = { BeatLength = 1000 }
}, gravity ? ScrollingAlgorithm.Gravity : ScrollingAlgorithm.Basic);
Action<bool> createPlayfieldWithNotes = gravity =>
{
Clear();
var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
ManiaPlayfield playField;
Add(playField = new ManiaPlayfield(4)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(1, -1),
Clock = new FramedClock(rateAdjustClock)
});
if (!gravity)
playField.Columns.ForEach(c => c.Add(createTimingChange(0, false)));
for (double t = start_time; t <= start_time + duration; t += 100)
{
if (gravity)
playField.Columns.ElementAt(0).Add(createTimingChange(t, true));
playField.Add(new DrawableNote(new Note
{
StartTime = t,
Column = 0
}, new Bindable<Key>(Key.D)));
if (gravity)
playField.Columns.ElementAt(3).Add(createTimingChange(t, true));
playField.Add(new DrawableNote(new Note
{
StartTime = t,
Column = 3
}, new Bindable<Key>(Key.K)));
}
if (gravity)
playField.Columns.ElementAt(1).Add(createTimingChange(start_time, true));
playField.Add(new DrawableHoldNote(new HoldNote
{
StartTime = start_time,
Duration = duration,
Column = 1
}, new Bindable<Key>(Key.F)));
if (gravity)
playField.Columns.ElementAt(2).Add(createTimingChange(start_time, true));
playField.Add(new DrawableHoldNote(new HoldNote
{
StartTime = start_time,
Duration = duration,
Column = 2
}, new Bindable<Key>(Key.J)));
};
AddStep("1 column", () => createPlayfield(1, SpecialColumnPosition.Normal)); AddStep("1 column", () => createPlayfield(1, SpecialColumnPosition.Normal));
AddStep("4 columns", () => createPlayfield(4, SpecialColumnPosition.Normal)); AddStep("4 columns", () => createPlayfield(4, SpecialColumnPosition.Normal));
AddStep("Left special style", () => createPlayfield(4, SpecialColumnPosition.Left)); AddStep("Left special style", () => createPlayfield(4, SpecialColumnPosition.Left));
@ -114,29 +37,105 @@ namespace osu.Desktop.Tests.Visual
AddStep("8 columns", () => createPlayfield(8, SpecialColumnPosition.Normal)); AddStep("8 columns", () => createPlayfield(8, SpecialColumnPosition.Normal));
AddStep("Left special style", () => createPlayfield(8, SpecialColumnPosition.Left)); AddStep("Left special style", () => createPlayfield(8, SpecialColumnPosition.Left));
AddStep("Right special style", () => createPlayfield(8, SpecialColumnPosition.Right)); AddStep("Right special style", () => createPlayfield(8, SpecialColumnPosition.Right));
AddStep("Reversed", () => createPlayfield(4, SpecialColumnPosition.Normal, true));
AddStep("Notes with input", () => createPlayfieldWithNotes(false)); AddStep("Notes with input", () => createPlayfieldWithNotes(false));
AddWaitStep((int)Math.Ceiling((start_time + duration) / TimePerAction)); AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(false, true));
AddStep("Notes with gravity", () => createPlayfieldWithNotes(true)); AddStep("Notes with gravity", () => createPlayfieldWithNotes(true));
AddWaitStep((int)Math.Ceiling((start_time + duration) / TimePerAction)); AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true, true));
} }
private void triggerKeyDown(Column column) [BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{ {
column.TriggerOnKeyDown(new InputState(), new KeyDownEventArgs maniaRuleset = rulesets.GetRuleset(3);
{
Key = column.Key,
Repeat = false
});
} }
private void triggerKeyUp(Column column) private SpeedAdjustmentContainer createTimingChange(double time, bool gravity) => new ManiaSpeedAdjustmentContainer(new MultiplierControlPoint(time)
{ {
column.TriggerOnKeyUp(new InputState(), new KeyUpEventArgs TimingPoint = { BeatLength = 1000 }
}, gravity ? ScrollingAlgorithm.Gravity : ScrollingAlgorithm.Basic);
private void createPlayfield(int cols, SpecialColumnPosition specialPos, bool inverted = false)
{ {
Key = column.Key Clear();
var inputManager = new ManiaInputManager(maniaRuleset, cols) { RelativeSizeAxes = Axes.Both };
Add(inputManager);
ManiaPlayfield playfield;
inputManager.Add(playfield = new ManiaPlayfield(cols)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
SpecialColumnPosition = specialPos
}); });
playfield.Inverted.Value = inverted;
}
private void createPlayfieldWithNotes(bool gravity, bool inverted = false)
{
Clear();
var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
var inputManager = new ManiaInputManager(maniaRuleset, 4) { RelativeSizeAxes = Axes.Both };
Add(inputManager);
ManiaPlayfield playfield;
inputManager.Add(playfield = new ManiaPlayfield(4)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Clock = new FramedClock(rateAdjustClock)
});
playfield.Inverted.Value = inverted;
if (!gravity)
playfield.Columns.ForEach(c => c.Add(createTimingChange(0, false)));
for (double t = start_time; t <= start_time + duration; t += 100)
{
if (gravity)
playfield.Columns.ElementAt(0).Add(createTimingChange(t, true));
playfield.Add(new DrawableNote(new Note
{
StartTime = t,
Column = 0
}, ManiaAction.Key1));
if (gravity)
playfield.Columns.ElementAt(3).Add(createTimingChange(t, true));
playfield.Add(new DrawableNote(new Note
{
StartTime = t,
Column = 3
}, ManiaAction.Key4));
}
if (gravity)
playfield.Columns.ElementAt(1).Add(createTimingChange(start_time, true));
playfield.Add(new DrawableHoldNote(new HoldNote
{
StartTime = start_time,
Duration = duration,
Column = 1
}, ManiaAction.Key2));
if (gravity)
playfield.Columns.ElementAt(2).Add(createTimingChange(start_time, true));
playfield.Add(new DrawableHoldNote(new HoldNote
{
StartTime = start_time,
Duration = duration,
Column = 2
}, ManiaAction.Key3));
} }
} }
} }

View File

@ -64,8 +64,8 @@ namespace osu.Desktop.Tests.Visual
AddStep("Reverse direction", () => AddStep("Reverse direction", () =>
{ {
horizontalRulesetContainer.Playfield.Reversed.Toggle(); horizontalRulesetContainer.Playfield.Reverse();
verticalRulesetContainer.Playfield.Reversed.Toggle(); verticalRulesetContainer.Playfield.Reverse();
}); });
} }
@ -115,18 +115,18 @@ namespace osu.Desktop.Tests.Visual
Assert.AreEqual(1, speedAdjustments[3].ControlPoint.Multiplier); Assert.AreEqual(1, speedAdjustments[3].ControlPoint.Multiplier);
// Check insertion of hit objects // Check insertion of hit objects
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[0].Contains(hitObjects[0])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[4].Contains(hitObjects[0]));
Assert.IsTrue(speedAdjustments[0].Contains(hitObjects[1])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[3].Contains(hitObjects[1]));
Assert.IsTrue(speedAdjustments[1].Contains(hitObjects[2])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[2].Contains(hitObjects[2]));
Assert.IsTrue(speedAdjustments[2].Contains(hitObjects[3])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[1].Contains(hitObjects[3]));
Assert.IsTrue(speedAdjustments[3].Contains(hitObjects[4])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[0].Contains(hitObjects[4]));
Assert.IsTrue(speedAdjustments[3].Contains(hitObjects[5])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[0].Contains(hitObjects[5]));
hitObjectContainer.RemoveSpeedAdjustment(speedAdjustments[1]); hitObjectContainer.RemoveSpeedAdjustment(hitObjectContainer.SpeedAdjustments[3]);
// The hit object contained in this speed adjustment should be resorted into the previous one // The hit object contained in this speed adjustment should be resorted into the one occuring before it
Assert.IsTrue(speedAdjustments[0].Contains(hitObjects[2])); Assert.IsTrue(hitObjectContainer.SpeedAdjustments[3].Contains(hitObjects[1]));
} }
private class TestRulesetContainer : ScrollingRulesetContainer<TestPlayfield, TestHitObject, TestJudgement> private class TestRulesetContainer : ScrollingRulesetContainer<TestPlayfield, TestHitObject, TestJudgement>
@ -210,6 +210,8 @@ namespace osu.Desktop.Tests.Visual
content = new Container { RelativeSizeAxes = Axes.Both } content = new Container { RelativeSizeAxes = Axes.Both }
}; };
} }
public void Reverse() => Reversed.Toggle();
} }

View File

@ -20,16 +20,11 @@ namespace osu.Desktop
{ {
internal class OsuGameDesktop : OsuGame internal class OsuGameDesktop : OsuGame
{ {
private readonly VersionManager versionManager; private VersionManager versionManager;
public OsuGameDesktop(string[] args = null) public OsuGameDesktop(string[] args = null)
: base(args) : base(args)
{ {
versionManager = new VersionManager
{
Depth = int.MinValue,
State = Visibility.Hidden
};
} }
public override Storage GetStorageForStableInstall() public override Storage GetStorageForStableInstall()
@ -88,11 +83,15 @@ namespace osu.Desktop
{ {
base.LoadComplete(); base.LoadComplete();
LoadComponentAsync(versionManager, Add); LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue });
ScreenChanged += s => ScreenChanged += s =>
{ {
if (!versionManager.IsPresent && s is Intro) if (s is Intro && s.ChildScreen == null)
{
Add(versionManager);
versionManager.State = Visibility.Visible; versionManager.State = Visibility.Visible;
}
}; };
} }

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 System; using System;
using System.Diagnostics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
@ -19,6 +20,7 @@ using OpenTK.Graphics;
using System.Net.Http; using System.Net.Http;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game; using osu.Game;
using osu.Game.Configuration;
namespace osu.Desktop.Overlays namespace osu.Desktop.Overlays
{ {
@ -26,17 +28,22 @@ namespace osu.Desktop.Overlays
{ {
private UpdateManager updateManager; private UpdateManager updateManager;
private NotificationOverlay notificationOverlay; private NotificationOverlay notificationOverlay;
private OsuConfigManager config;
private OsuGameBase game;
public override bool HandleInput => false; public override bool HandleInput => false;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game) private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config)
{ {
notificationOverlay = notification; notificationOverlay = notification;
this.config = config;
this.game = game;
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Anchor = Anchor.BottomCentre; Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre; Origin = Anchor.BottomCentre;
Alpha = 0; Alpha = 0;
Children = new Drawable[] Children = new Drawable[]
@ -91,6 +98,42 @@ namespace osu.Desktop.Overlays
checkForUpdateAsync(); checkForUpdateAsync();
} }
protected override void LoadComplete()
{
base.LoadComplete();
var version = game.Version;
var lastVersion = config.Get<string>(OsuSetting.Version);
if (game.IsDeployedBuild && version != lastVersion)
{
config.Set(OsuSetting.Version, version);
// only show a notification if we've previously saved a version to the config file (ie. not the first run).
if (!string.IsNullOrEmpty(lastVersion))
Scheduler.AddDelayed(() => notificationOverlay.Post(new UpdateCompleteNotification(version)), 5000);
}
}
private class UpdateCompleteNotification : SimpleNotification
{
public UpdateCompleteNotification(string version)
{
Text = $"You are now running osu!lazer {version}.\nClick to see what's new!";
Icon = FontAwesome.fa_check_square;
Activated = delegate
{
Process.Start($"https://github.com/ppy/osu/releases/tag/v{version}");
return true;
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
IconBackgound.Colour = colours.BlueDark;
}
}
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);

View File

@ -28,12 +28,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private Pattern lastPattern = new Pattern(); private Pattern lastPattern = new Pattern();
private FastRandom random; private FastRandom random;
private Beatmap beatmap; private Beatmap beatmap;
private bool isForCurrentRuleset;
protected override Beatmap<ManiaHitObject> ConvertBeatmap(Beatmap original, bool isForCurrentRuleset) private readonly int availableColumns;
private readonly bool isForCurrentRuleset;
public ManiaBeatmapConverter(bool isForCurrentRuleset, int availableColumns)
{ {
this.isForCurrentRuleset = isForCurrentRuleset; if (availableColumns <= 0) throw new ArgumentOutOfRangeException(nameof(availableColumns));
this.isForCurrentRuleset = isForCurrentRuleset;
this.availableColumns = availableColumns;
}
protected override Beatmap<ManiaHitObject> ConvertBeatmap(Beatmap original)
{
beatmap = original; beatmap = original;
BeatmapDifficulty difficulty = original.BeatmapInfo.Difficulty; BeatmapDifficulty difficulty = original.BeatmapInfo.Difficulty;
@ -41,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate); int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
random = new FastRandom(seed); random = new FastRandom(seed);
return base.ConvertBeatmap(original, isForCurrentRuleset); return base.ConvertBeatmap(original);
} }
protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, Beatmap beatmap) protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, Beatmap beatmap)
@ -89,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// <returns>The hit objects generated.</returns> /// <returns>The hit objects generated.</returns>
private IEnumerable<ManiaHitObject> generateSpecific(HitObject original) private IEnumerable<ManiaHitObject> generateSpecific(HitObject original)
{ {
var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern); var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, availableColumns, lastPattern);
Pattern newPattern = generator.Generate(); Pattern newPattern = generator.Generate();
lastPattern = newPattern; lastPattern = newPattern;
@ -113,14 +121,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
Patterns.PatternGenerator conversion = null; Patterns.PatternGenerator conversion = null;
if (distanceData != null) if (distanceData != null)
conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern); conversion = new DistanceObjectPatternGenerator(random, original, beatmap, availableColumns, lastPattern);
else if (endTimeData != null) else if (endTimeData != null)
conversion = new EndTimeObjectPatternGenerator(random, original, beatmap); conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, availableColumns);
else if (positionData != null) else if (positionData != null)
{ {
computeDensity(original.StartTime); computeDensity(original.StartTime);
conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair); conversion = new HitObjectPatternGenerator(random, original, beatmap, availableColumns, lastPattern, lastTime, lastPosition, density, lastStair);
recordNote(original.StartTime, positionData.Position); recordNote(original.StartTime, positionData.Position);
} }
@ -142,8 +150,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// </summary> /// </summary>
private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator
{ {
public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern) public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern)
: base(random, hitObject, beatmap, previousPattern) : base(random, hitObject, beatmap, availableColumns, previousPattern)
{ {
} }

View File

@ -29,8 +29,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private PatternType convertType; private PatternType convertType;
public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern) public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern)
: base(random, hitObject, beatmap, previousPattern) : base(random, hitObject, beatmap, availableColumns, previousPattern)
{ {
convertType = PatternType.None; convertType = PatternType.None;
if (Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) if (Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode)

View File

@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
private readonly double endTime; private readonly double endTime;
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap) public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns)
: base(random, hitObject, beatmap, new Pattern()) : base(random, hitObject, beatmap, availableColumns, new Pattern())
{ {
var endtimeData = HitObject as IHasEndTime; var endtimeData = HitObject as IHasEndTime;

View File

@ -20,9 +20,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private readonly PatternType convertType; private readonly PatternType convertType;
public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair) public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair)
: base(random, hitObject, beatmap, previousPattern) : base(random, hitObject, beatmap, availableColumns, previousPattern)
{ {
if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime));
if (density < 0) throw new ArgumentOutOfRangeException(nameof(density));
StairType = lastStair; StairType = lastStair;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime); TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);

View File

@ -25,11 +25,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary> /// </summary>
protected readonly FastRandom Random; protected readonly FastRandom Random;
protected PatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern) protected PatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern)
: base(hitObject, beatmap, previousPattern) : base(hitObject, beatmap, availableColumns, previousPattern)
{ {
Random = random; if (random == null) throw new ArgumentNullException(nameof(random));
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
if (availableColumns <= 0) throw new ArgumentOutOfRangeException(nameof(availableColumns));
if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern));
Random = random;
RandomStart = AvailableColumns == 8 ? 1 : 0; RandomStart = AvailableColumns == 8 ? 1 : 0;
} }
@ -62,6 +66,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <returns>The amount of notes to be generated.</returns> /// <returns>The amount of notes to be generated.</returns>
protected int GetRandomNoteCount(double p2, double p3, double p4 = 0, double p5 = 0, double p6 = 0) protected int GetRandomNoteCount(double p2, double p3, double p4 = 0, double p5 = 0, double p6 = 0)
{ {
if (p2 < 0 || p2 > 1) throw new ArgumentOutOfRangeException(nameof(p2));
if (p3 < 0 || p3 > 1) throw new ArgumentOutOfRangeException(nameof(p3));
if (p4 < 0 || p4 > 1) throw new ArgumentOutOfRangeException(nameof(p4));
if (p5 < 0 || p5 > 1) throw new ArgumentOutOfRangeException(nameof(p5));
if (p6 < 0 || p6 > 1) throw new ArgumentOutOfRangeException(nameof(p6));
double val = Random.NextDouble(); double val = Random.NextDouble();
if (val >= 1 - p6) if (val >= 1 - p6)
return 6; return 6;

View File

@ -32,13 +32,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
/// </summary> /// </summary>
protected readonly Beatmap Beatmap; protected readonly Beatmap Beatmap;
protected PatternGenerator(HitObject hitObject, Beatmap beatmap, Pattern previousPattern) protected PatternGenerator(HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern)
{ {
PreviousPattern = previousPattern; if (hitObject == null) throw new ArgumentNullException(nameof(hitObject));
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
if (availableColumns <= 0) throw new ArgumentOutOfRangeException(nameof(availableColumns));
if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern));
HitObject = hitObject; HitObject = hitObject;
Beatmap = beatmap; Beatmap = beatmap;
AvailableColumns = availableColumns;
AvailableColumns = (int)Math.Round(beatmap.BeatmapInfo.Difficulty.CircleSize); PreviousPattern = previousPattern;
} }
/// <summary> /// <summary>

View File

@ -6,6 +6,7 @@ using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using System.Collections.Generic; using System.Collections.Generic;
using System;
namespace osu.Game.Rulesets.Mania namespace osu.Game.Rulesets.Mania
{ {
@ -21,6 +22,6 @@ namespace osu.Game.Rulesets.Mania
return 0; return 0;
} }
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter(); protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.Difficulty.CircleSize)));
} }
} }

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 System.ComponentModel;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -8,14 +9,33 @@ namespace osu.Game.Rulesets.Mania
{ {
public class ManiaInputManager : RulesetInputManager<ManiaAction> public class ManiaInputManager : RulesetInputManager<ManiaAction>
{ {
public ManiaInputManager(RulesetInfo ruleset) public ManiaInputManager(RulesetInfo ruleset, int variant)
: base(ruleset, 0, SimultaneousBindingMode.Unique) : base(ruleset, variant, SimultaneousBindingMode.Unique)
{ {
} }
} }
public enum ManiaAction public enum ManiaAction
{ {
// placeholder [Description("Special")]
Special,
[Description("Key 1")]
Key1 = 10,
[Description("Key 2")]
Key2,
[Description("Key 3")]
Key3,
[Description("Key 4")]
Key4,
[Description("Key 5")]
Key5,
[Description("Key 6")]
Key6,
[Description("Key 7")]
Key7,
[Description("Key 8")]
Key8,
[Description("Key 9")]
Key9
} }
} }

View File

@ -8,6 +8,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Scoring; using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -120,5 +121,43 @@ namespace osu.Game.Rulesets.Mania
: base(rulesetInfo) : base(rulesetInfo)
{ {
} }
public override IEnumerable<int> AvailableVariants => new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0)
{
var leftKeys = new[]
{
InputKey.A,
InputKey.S,
InputKey.D,
InputKey.F
};
var rightKeys = new[]
{
InputKey.J,
InputKey.K,
InputKey.L,
InputKey.Semicolon
};
ManiaAction currentKey = ManiaAction.Key1;
var bindings = new List<KeyBinding>();
for (int i = leftKeys.Length - variant / 2; i < leftKeys.Length; i++)
bindings.Add(new KeyBinding(leftKeys[i], currentKey++));
for (int i = 0; i < variant / 2; i++)
bindings.Add(new KeyBinding(rightKeys[i], currentKey++));
if (variant % 2 == 1)
bindings.Add(new KeyBinding(InputKey.Space, ManiaAction.Special));
return bindings;
}
public override string GetVariantName(int variant) => $"{variant}K";
} }
} }

View File

@ -5,20 +5,18 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Configuration;
using OpenTK.Input;
using osu.Framework.Input;
using OpenTK; using OpenTK;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Input.Bindings;
namespace osu.Game.Rulesets.Mania.Objects.Drawables namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
/// <summary> /// <summary>
/// Visualises a <see cref="HoldNote"/> hit object. /// Visualises a <see cref="HoldNote"/> hit object.
/// </summary> /// </summary>
public class DrawableHoldNote : DrawableManiaHitObject<HoldNote> public class DrawableHoldNote : DrawableManiaHitObject<HoldNote>, IKeyBindingHandler<ManiaAction>
{ {
private readonly DrawableNote head; private readonly DrawableNote head;
private readonly DrawableNote tail; private readonly DrawableNote tail;
@ -36,8 +34,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary> /// </summary>
private bool hasBroken; private bool hasBroken;
public DrawableHoldNote(HoldNote hitObject, Bindable<Key> key = null) public DrawableHoldNote(HoldNote hitObject, ManiaAction action)
: base(hitObject, key) : base(hitObject, action)
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Height = (float)HitObject.Duration; Height = (float)HitObject.Duration;
@ -58,12 +56,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
RelativeChildOffset = new Vector2(0, (float)HitObject.StartTime), RelativeChildOffset = new Vector2(0, (float)HitObject.StartTime),
RelativeChildSize = new Vector2(1, (float)HitObject.Duration) RelativeChildSize = new Vector2(1, (float)HitObject.Duration)
}, },
head = new DrawableHeadNote(this, key) head = new DrawableHeadNote(this, action)
{ {
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre Origin = Anchor.TopCentre
}, },
tail = new DrawableTailNote(this, key) tail = new DrawableTailNote(this, action)
{ {
Anchor = Anchor.BottomCentre, Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre Origin = Anchor.TopCentre
@ -106,16 +104,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
} }
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) public bool OnPressed(ManiaAction action)
{ {
// Make sure the keypress happened within the body of the hold note // Make sure the action happened within the body of the hold note
if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime) if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime)
return false; return false;
if (args.Key != Key) if (action != Action)
return false;
if (args.Repeat)
return false; return false;
// The user has pressed during the body of the hold note, after the head note and its hit windows have passed // The user has pressed during the body of the hold note, after the head note and its hit windows have passed
@ -126,13 +121,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
return true; return true;
} }
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) public bool OnReleased(ManiaAction action)
{ {
// Make sure that the user started holding the key during the hold note // Make sure that the user started holding the key during the hold note
if (!holdStartTime.HasValue) if (!holdStartTime.HasValue)
return false; return false;
if (args.Key != Key) if (action != Action)
return false; return false;
holdStartTime = null; holdStartTime = null;
@ -151,8 +146,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
private readonly DrawableHoldNote holdNote; private readonly DrawableHoldNote holdNote;
public DrawableHeadNote(DrawableHoldNote holdNote, Bindable<Key> key = null) public DrawableHeadNote(DrawableHoldNote holdNote, ManiaAction action)
: base(holdNote.HitObject.Head, key) : base(holdNote.HitObject.Head, action)
{ {
this.holdNote = holdNote; this.holdNote = holdNote;
@ -160,9 +155,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Y = 0; Y = 0;
} }
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) public override bool OnPressed(ManiaAction action)
{ {
if (!base.OnKeyDown(state, args)) if (!base.OnPressed(action))
return false; return false;
// We only want to trigger a holding state from the head if the head has received a judgement // We only want to trigger a holding state from the head if the head has received a judgement
@ -188,8 +183,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{ {
private readonly DrawableHoldNote holdNote; private readonly DrawableHoldNote holdNote;
public DrawableTailNote(DrawableHoldNote holdNote, Bindable<Key> key = null) public DrawableTailNote(DrawableHoldNote holdNote, ManiaAction action)
: base(holdNote.HitObject.Tail, key) : base(holdNote.HitObject.Tail, action)
{ {
this.holdNote = holdNote; this.holdNote = holdNote;
@ -210,7 +205,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
tailJudgement.HasBroken = holdNote.hasBroken; tailJudgement.HasBroken = holdNote.hasBroken;
} }
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down
public override bool OnReleased(ManiaAction action)
{ {
// Make sure that the user started holding the key during the hold note // Make sure that the user started holding the key during the hold note
if (!holdNote.holdStartTime.HasValue) if (!holdNote.holdStartTime.HasValue)
@ -219,7 +216,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (Judgement.Result != HitResult.None) if (Judgement.Result != HitResult.None)
return false; return false;
if (args.Key != Key) if (action != Action)
return false; return false;
UpdateJudgement(true); UpdateJudgement(true);
@ -227,12 +224,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
// Handled by the hold note, which will set holding = false // Handled by the hold note, which will set holding = false
return false; return false;
} }
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
// Tail doesn't handle key down
return false;
}
} }
} }
} }

View File

@ -2,8 +2,6 @@
// 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.Graphics;
using OpenTK.Input;
using osu.Framework.Configuration;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@ -15,17 +13,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// <summary> /// <summary>
/// The key that will trigger input for this hit object. /// The key that will trigger input for this hit object.
/// </summary> /// </summary>
protected Bindable<Key> Key { get; private set; } = new Bindable<Key>(); protected ManiaAction Action { get; }
public new TObject HitObject; public new TObject HitObject;
protected DrawableManiaHitObject(TObject hitObject, Bindable<Key> key = null) protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null)
: base(hitObject) : base(hitObject)
{ {
HitObject = hitObject; HitObject = hitObject;
if (key != null) if (action != null)
Key.BindTo(key); Action = action.Value;
} }
public override Color4 AccentColour public override Color4 AccentColour

View File

@ -3,10 +3,8 @@
using System; using System;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces; using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
@ -16,12 +14,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// <summary> /// <summary>
/// Visualises a <see cref="Note"/> hit object. /// Visualises a <see cref="Note"/> hit object.
/// </summary> /// </summary>
public class DrawableNote : DrawableManiaHitObject<Note> public class DrawableNote : DrawableManiaHitObject<Note>, IKeyBindingHandler<ManiaAction>
{ {
private readonly NotePiece headPiece; private readonly NotePiece headPiece;
public DrawableNote(Note hitObject, Bindable<Key> key = null) public DrawableNote(Note hitObject, ManiaAction action)
: base(hitObject, key) : base(hitObject, action)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = 100; Height = 100;
@ -81,18 +79,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
} }
} }
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) public virtual bool OnPressed(ManiaAction action)
{ {
if (Judgement.Result != HitResult.None) if (action != Action)
return false;
if (args.Key != Key)
return false;
if (args.Repeat)
return false; return false;
return UpdateJudgement(true); return UpdateJudgement(true);
} }
public virtual bool OnReleased(ManiaAction action) => false;
} }
} }

View File

@ -3,17 +3,15 @@
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Input;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using System; using System;
using osu.Framework.Configuration; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
@ -32,10 +30,7 @@ namespace osu.Game.Rulesets.Mania.UI
private const float column_width = 45; private const float column_width = 45;
private const float special_column_width = 70; private const float special_column_width = 70;
/// <summary> public ManiaAction Action;
/// The key that will trigger input actions for this column and hit objects contained inside it.
/// </summary>
public Bindable<Key> Key = new Bindable<Key>();
private readonly Box background; private readonly Box background;
private readonly Container hitTargetBar; private readonly Container hitTargetBar;
@ -101,8 +96,8 @@ namespace osu.Game.Rulesets.Mania.UI
// For column lighting, we need to capture input events before the notes // For column lighting, we need to capture input events before the notes
new InputTarget new InputTarget
{ {
KeyDown = onKeyDown, Pressed = onPressed,
KeyUp = onKeyUp Released = onReleased
} }
} }
}, },
@ -199,12 +194,9 @@ namespace osu.Game.Rulesets.Mania.UI
HitObjects.Add(hitObject); HitObjects.Add(hitObject);
} }
private bool onKeyDown(InputState state, KeyDownEventArgs args) private bool onPressed(ManiaAction action)
{ {
if (args.Repeat) if (action == Action)
return false;
if (args.Key == Key)
{ {
background.FadeTo(background.Alpha + 0.2f, 50, Easing.OutQuint); background.FadeTo(background.Alpha + 0.2f, 50, Easing.OutQuint);
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint); keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint);
@ -213,9 +205,9 @@ namespace osu.Game.Rulesets.Mania.UI
return false; return false;
} }
private bool onKeyUp(InputState state, KeyUpEventArgs args) private bool onReleased(ManiaAction action)
{ {
if (args.Key == Key) if (action == Action)
{ {
background.FadeTo(0.2f, 800, Easing.OutQuart); background.FadeTo(0.2f, 800, Easing.OutQuart);
keyIcon.ScaleTo(1f, 400, Easing.OutQuart); keyIcon.ScaleTo(1f, 400, Easing.OutQuart);
@ -227,10 +219,10 @@ namespace osu.Game.Rulesets.Mania.UI
/// <summary> /// <summary>
/// This is a simple container which delegates various input events that have to be captured before the notes. /// This is a simple container which delegates various input events that have to be captured before the notes.
/// </summary> /// </summary>
private class InputTarget : Container private class InputTarget : Container, IKeyBindingHandler<ManiaAction>
{ {
public Func<InputState, KeyDownEventArgs, bool> KeyDown; public Func<ManiaAction, bool> Pressed;
public Func<InputState, KeyUpEventArgs, bool> KeyUp; public Func<ManiaAction, bool> Released;
public InputTarget() public InputTarget()
{ {
@ -239,8 +231,8 @@ namespace osu.Game.Rulesets.Mania.UI
Alpha = 0; Alpha = 0;
} }
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => KeyDown?.Invoke(state, args) ?? false; public bool OnPressed(ManiaAction action) => Pressed?.Invoke(action) ?? false;
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => KeyUp?.Invoke(state, args) ?? false; public bool OnReleased(ManiaAction action) => Released?.Invoke(action) ?? false;
} }
} }
} }

View File

@ -14,6 +14,7 @@ using osu.Framework.Allocation;
using OpenTK.Input; using OpenTK.Input;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Configuration;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -45,6 +46,11 @@ namespace osu.Game.Rulesets.Mania.UI
} }
} }
/// <summary>
/// Whether this playfield should be inverted. This flips everything inside the playfield.
/// </summary>
public readonly Bindable<bool> Inverted = new Bindable<bool>(true);
private readonly FlowContainer<Column> columns; private readonly FlowContainer<Column> columns;
public IEnumerable<Column> Columns => columns.Children; public IEnumerable<Column> Columns => columns.Children;
@ -64,6 +70,8 @@ namespace osu.Game.Rulesets.Mania.UI
if (columnCount <= 0) if (columnCount <= 0)
throw new ArgumentException("Can't have zero or fewer columns."); throw new ArgumentException("Can't have zero or fewer columns.");
Inverted.Value = true;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Container new Container
@ -122,14 +130,26 @@ namespace osu.Game.Rulesets.Mania.UI
} }
}; };
var currentAction = ManiaAction.Key1;
for (int i = 0; i < columnCount; i++) for (int i = 0; i < columnCount; i++)
{ {
var c = new Column(); var c = new Column();
c.VisibleTimeRange.BindTo(VisibleTimeRange); c.VisibleTimeRange.BindTo(VisibleTimeRange);
c.IsSpecial = isSpecialColumn(i);
c.Action = c.IsSpecial ? ManiaAction.Special : currentAction++;
columns.Add(c); columns.Add(c);
AddNested(c); AddNested(c);
} }
Inverted.ValueChanged += invertedChanged;
Inverted.TriggerChange();
}
private void invertedChanged(bool newValue)
{
Scale = new Vector2(1, newValue ? -1 : 1);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -144,15 +164,11 @@ namespace osu.Game.Rulesets.Mania.UI
specialColumnColour = colours.BlueDark; specialColumnColour = colours.BlueDark;
// Set the special column + colour + key // Set the special column + colour + key
for (int i = 0; i < columnCount; i++) foreach (var column in Columns)
{ {
Column column = Columns.ElementAt(i);
column.IsSpecial = isSpecialColumn(i);
if (!column.IsSpecial) if (!column.IsSpecial)
continue; continue;
column.Key.Value = Key.Space;
column.AccentColour = specialColumnColour; column.AccentColour = specialColumnColour;
} }
@ -166,21 +182,6 @@ namespace osu.Game.Rulesets.Mania.UI
nonSpecialColumns[i].AccentColour = colour; nonSpecialColumns[i].AccentColour = colour;
nonSpecialColumns[nonSpecialColumns.Count - 1 - i].AccentColour = colour; nonSpecialColumns[nonSpecialColumns.Count - 1 - i].AccentColour = colour;
} }
// We'll set the keys for non-special columns in another separate loop because it's not mirrored like the above colours
// Todo: This needs to go when we get to bindings and use Button1, ..., ButtonN instead
for (int i = 0; i < nonSpecialColumns.Count; i++)
{
Column column = nonSpecialColumns[i];
int keyOffset = default_keys.Length / 2 - nonSpecialColumns.Count / 2 + i;
if (keyOffset >= 0 && keyOffset < default_keys.Length)
column.Key.Value = default_keys[keyOffset];
else
// There is no default key defined for this column. Let's set this to Unknown for now
// however note that this will be gone after bindings are in place
column.Key.Value = Key.Unknown;
}
} }
/// <summary> /// <summary>

View File

@ -5,9 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using OpenTK; using OpenTK;
using OpenTK.Input;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input; using osu.Framework.Input;
@ -33,9 +31,10 @@ namespace osu.Game.Rulesets.Mania.UI
public class ManiaRulesetContainer : ScrollingRulesetContainer<ManiaPlayfield, ManiaHitObject, ManiaJudgement> public class ManiaRulesetContainer : ScrollingRulesetContainer<ManiaPlayfield, ManiaHitObject, ManiaJudgement>
{ {
/// <summary> /// <summary>
/// Preferred column count. This will only have an effect during the initialization of the play field. /// The number of columns which the <see cref="ManiaPlayfield"/> should display, and which
/// the beatmap converter will attempt to convert beatmaps to use.
/// </summary> /// </summary>
public int PreferredColumns; private int availableColumns;
public IEnumerable<DrawableBarLine> BarLines; public IEnumerable<DrawableBarLine> BarLines;
@ -76,38 +75,47 @@ namespace osu.Game.Rulesets.Mania.UI
BarLines.ForEach(Playfield.Add); BarLines.ForEach(Playfield.Add);
} }
protected override void ApplyBeatmap() protected sealed override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield() => new ManiaPlayfield(availableColumns)
{
base.ApplyBeatmap();
PreferredColumns = (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.Difficulty.CircleSize));
}
protected sealed override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield() => new ManiaPlayfield(PreferredColumns)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
// Invert by default for now (should be moved to config/skin later)
Scale = new Vector2(1, -1)
}; };
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo); public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, availableColumns);
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter(); protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter()
{
if (IsForCurrentRuleset)
availableColumns = (int)Math.Max(1, Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize));
else
{
float percentSliderOrSpinner = (float)WorkingBeatmap.Beatmap.HitObjects.Count(h => h is IHasEndTime) / WorkingBeatmap.Beatmap.HitObjects.Count;
if (percentSliderOrSpinner < 0.2)
availableColumns = 7;
else if (percentSliderOrSpinner < 0.3 || Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize) >= 5)
availableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 5 ? 7 : 6;
else if (percentSliderOrSpinner > 0.6)
availableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 4 ? 5 : 4;
else
availableColumns = Math.Max(4, Math.Min((int)Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) + 1, 7));
}
return new ManiaBeatmapConverter(IsForCurrentRuleset, availableColumns);
}
protected override DrawableHitObject<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h) protected override DrawableHitObject<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h)
{ {
Bindable<Key> key = Playfield.Columns.ElementAt(h.Column).Key; ManiaAction action = Playfield.Columns.ElementAt(h.Column).Action;
var holdNote = h as HoldNote; var holdNote = h as HoldNote;
if (holdNote != null) if (holdNote != null)
return new DrawableHoldNote(holdNote, key); return new DrawableHoldNote(holdNote, action);
var note = h as Note; var note = h as Note;
if (note != null) if (note != null)
return new DrawableNote(note, key); return new DrawableNote(note, action);
return null; return null;
} }

View File

@ -7,8 +7,13 @@ using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Mods namespace osu.Game.Rulesets.Osu.Mods
{ {
@ -28,10 +33,23 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;
} }
public class OsuModHardRock : ModHardRock public class OsuModHardRock : ModHardRock, IApplicableMod<OsuHitObject>
{ {
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;
public override bool Ranked => true; public override bool Ranked => true;
public void ApplyToRulesetContainer(RulesetContainer<OsuHitObject> rulesetContainer)
{
rulesetContainer.Objects.OfType<OsuHitObject>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Y));
rulesetContainer.Objects.OfType<Slider>().ForEach(s =>
{
var newControlPoints = new List<Vector2>();
s.ControlPoints.ForEach(c => newControlPoints.Add(new Vector2(c.X, OsuPlayfield.BASE_SIZE.Y - c.Y)));
s.ControlPoints = newControlPoints;
s.Curve?.Calculate(); // Recalculate the slider curve
});
}
} }
public class OsuModSuddenDeath : ModSuddenDeath public class OsuModSuddenDeath : ModSuddenDeath

View File

@ -0,0 +1,35 @@
// 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.Collections.Generic;
using osu.Framework.Input;
using osu.Game.Rulesets.Replays;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Replays
{
public class OsuReplayInputHandler : FramedReplayInputHandler
{
public OsuReplayInputHandler(Replay replay)
: base(replay)
{
}
public override List<InputState> GetPendingStates()
{
List<OsuAction> actions = new List<OsuAction>();
if (CurrentFrame?.MouseLeft ?? false) actions.Add(OsuAction.LeftButton);
if (CurrentFrame?.MouseRight ?? false) actions.Add(OsuAction.RightButton);
return new List<InputState>
{
new ReplayState<OsuAction>
{
Mouse = new ReplayMouseState(ToScreenSpace(Position ?? Vector2.Zero)),
PressedActions = actions
}
};
}
}
}

View File

@ -10,9 +10,11 @@ using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Osu.UI namespace osu.Game.Rulesets.Osu.UI
{ {
@ -49,6 +51,8 @@ namespace osu.Game.Rulesets.Osu.UI
return null; return null;
} }
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay);
protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f); protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f);
} }
} }

View File

@ -78,6 +78,7 @@
<Compile Include="OsuDifficulty\Skills\Speed.cs" /> <Compile Include="OsuDifficulty\Skills\Speed.cs" />
<Compile Include="OsuDifficulty\Utils\History.cs" /> <Compile Include="OsuDifficulty\Utils\History.cs" />
<Compile Include="OsuInputManager.cs" /> <Compile Include="OsuInputManager.cs" />
<Compile Include="Replays\OsuReplayInputHandler.cs" />
<Compile Include="UI\Cursor\CursorTrail.cs" /> <Compile Include="UI\Cursor\CursorTrail.cs" />
<Compile Include="UI\Cursor\GameplayCursor.cs" /> <Compile Include="UI\Cursor\GameplayCursor.cs" />
<Compile Include="UI\OsuSettings.cs" /> <Compile Include="UI\OsuSettings.cs" />

View File

@ -39,19 +39,22 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
/// </summary> /// </summary>
private const float taiko_base_distance = 100; private const float taiko_base_distance = 100;
private bool isForCurrentRuleset; private readonly bool isForCurrentRuleset;
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(HitObject) }; protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(HitObject) };
protected override Beatmap<TaikoHitObject> ConvertBeatmap(Beatmap original, bool isForCurrentRuleset) public TaikoBeatmapConverter(bool isForCurrentRuleset)
{ {
this.isForCurrentRuleset = isForCurrentRuleset; this.isForCurrentRuleset = isForCurrentRuleset;
}
protected override Beatmap<TaikoHitObject> ConvertBeatmap(Beatmap original)
{
// Rewrite the beatmap info to add the slider velocity multiplier // Rewrite the beatmap info to add the slider velocity multiplier
BeatmapInfo info = original.BeatmapInfo.DeepClone(); BeatmapInfo info = original.BeatmapInfo.DeepClone();
info.Difficulty.SliderMultiplier *= legacy_velocity_multiplier; info.Difficulty.SliderMultiplier *= legacy_velocity_multiplier;
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original, isForCurrentRuleset); Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original);
// Post processing step to transform hit objects with the same start time into strong hits // Post processing step to transform hit objects with the same start time into strong hits
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x => converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>

View File

@ -28,14 +28,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
FillMode = FillMode.Fit; FillMode = FillMode.Fit;
} }
protected override void LoadComplete()
{
base.LoadComplete();
// We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize
Width *= Parent.RelativeChildSize.X;
}
protected override void CheckJudgement(bool userTriggered) protected override void CheckJudgement(bool userTriggered)
{ {
if (!userTriggered) if (!userTriggered)
@ -71,6 +63,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return UpdateJudgement(true); return UpdateJudgement(true);
} }
protected override void Update()
{
base.Update();
Size = BaseSize * Parent.RelativeChildSize;
}
protected override void UpdateState(ArmedState state) protected override void UpdateState(ArmedState state)
{ {
var circlePiece = MainPiece as CirclePiece; var circlePiece = MainPiece as CirclePiece;

View File

@ -199,6 +199,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
base.Update(); base.Update();
Size = BaseSize * Parent.RelativeChildSize;
// Make the swell stop at the hit target // Make the swell stop at the hit target
X = (float)Math.Max(Time.Current, HitObject.StartTime); X = (float)Math.Max(Time.Current, HitObject.StartTime);

View File

@ -16,6 +16,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{ {
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2); public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
protected readonly Vector2 BaseSize;
protected readonly TaikoPiece MainPiece; protected readonly TaikoPiece MainPiece;
public new TaikoHitType HitObject; public new TaikoHitType HitObject;
@ -29,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Origin = Anchor.Custom; Origin = Anchor.Custom;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Size = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE); Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
Add(MainPiece = CreateMainPiece()); Add(MainPiece = CreateMainPiece());
MainPiece.KiaiMode = HitObject.Kiai; MainPiece.KiaiMode = HitObject.Kiai;

View File

@ -4,7 +4,6 @@
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Input; using osu.Framework.Input;
using OpenTK.Input;
namespace osu.Game.Rulesets.Taiko.Replays namespace osu.Game.Rulesets.Taiko.Replays
{ {
@ -17,21 +16,18 @@ namespace osu.Game.Rulesets.Taiko.Replays
public override List<InputState> GetPendingStates() public override List<InputState> GetPendingStates()
{ {
var keys = new List<Key>(); var actions = new List<TaikoAction>();
if (CurrentFrame?.MouseRight1 == true) if (CurrentFrame?.MouseRight1 == true)
keys.Add(Key.F); actions.Add(TaikoAction.LeftCentre);
if (CurrentFrame?.MouseRight2 == true) if (CurrentFrame?.MouseRight2 == true)
keys.Add(Key.J); actions.Add(TaikoAction.RightCentre);
if (CurrentFrame?.MouseLeft1 == true) if (CurrentFrame?.MouseLeft1 == true)
keys.Add(Key.D); actions.Add(TaikoAction.LeftRim);
if (CurrentFrame?.MouseLeft2 == true) if (CurrentFrame?.MouseLeft2 == true)
keys.Add(Key.K); actions.Add(TaikoAction.RightRim);
return new List<InputState> return new List<InputState> { new ReplayState<TaikoAction> { PressedActions = actions } };
{
new InputState { Keyboard = new ReplayKeyboardState(keys) }
};
} }
} }
} }

View File

@ -135,6 +135,6 @@ namespace osu.Game.Rulesets.Taiko
return difficulty; return difficulty;
} }
protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter(); protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter(true);
} }
} }

View File

@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Taiko.UI
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter(); protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter(IsForCurrentRuleset);
public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo); public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);

View File

@ -13,7 +13,6 @@ using osu.Framework.Platform;
using osu.Game.IPC; using osu.Game.IPC;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets;
namespace osu.Game.Tests.Beatmaps.IO namespace osu.Game.Tests.Beatmaps.IO
{ {
@ -98,16 +97,14 @@ namespace osu.Game.Tests.Beatmaps.IO
private OsuGameBase loadOsu(GameHost host) private OsuGameBase loadOsu(GameHost host)
{ {
host.Storage.DeleteDatabase(@"client");
var osu = new OsuGameBase(); var osu = new OsuGameBase();
Task.Run(() => host.Run(osu)); Task.Run(() => host.Run(osu));
while (!osu.IsLoaded) while (!osu.IsLoaded)
Thread.Sleep(1); Thread.Sleep(1);
//reset beatmap database (sqlite and storage backing)
osu.Dependencies.Get<RulesetStore>().Reset();
osu.Dependencies.Get<BeatmapManager>().Reset();
return osu; return osu;
} }

View File

@ -30,11 +30,15 @@ namespace osu.Game.Beatmaps
public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject
{ {
protected readonly Beatmap Beatmap;
protected List<T> Objects; protected List<T> Objects;
protected DifficultyCalculator(Beatmap beatmap) protected DifficultyCalculator(Beatmap beatmap)
{ {
Objects = CreateBeatmapConverter().Convert(beatmap, true).HitObjects; Beatmap = beatmap;
Objects = CreateBeatmapConverter().Convert(beatmap).HitObjects;
foreach (var h in Objects) foreach (var h in Objects)
h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty); h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);

View File

@ -70,6 +70,8 @@ namespace osu.Game.Configuration
// Update // Update
Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer); Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
Set(OsuSetting.Version, string.Empty);
} }
public OsuConfigManager(Storage storage) : base(storage) public OsuConfigManager(Storage storage) : base(storage)
@ -106,6 +108,7 @@ namespace osu.Game.Configuration
SnakingInSliders, SnakingInSliders,
SnakingOutSliders, SnakingOutSliders,
ShowFpsDisplay, ShowFpsDisplay,
ChatDisplayHeight ChatDisplayHeight,
Version
} }
} }

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Configuration; using osu.Game.Configuration;
using System; using System;
using System.Diagnostics;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
namespace osu.Game.Graphics.Cursor namespace osu.Game.Graphics.Cursor
@ -21,11 +22,21 @@ namespace osu.Game.Graphics.Cursor
private bool dragging; private bool dragging;
private bool startRotation;
protected override bool OnMouseMove(InputState state) protected override bool OnMouseMove(InputState state)
{ {
if (dragging) if (dragging)
{ {
Vector2 offset = state.Mouse.Position - state.Mouse.PositionMouseDown ?? state.Mouse.Delta; Debug.Assert(state.Mouse.PositionMouseDown != null);
// don't start rotating until we're moved a minimum distance away from the mouse down location,
// else it can have an annoying effect.
startRotation |= Vector2.Distance(state.Mouse.Position, state.Mouse.PositionMouseDown.Value) > 30;
if (startRotation)
{
Vector2 offset = state.Mouse.Position - state.Mouse.PositionMouseDown.Value;
float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f; float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f;
// Always rotate in the direction of least distance // Always rotate in the direction of least distance
@ -36,6 +47,7 @@ namespace osu.Game.Graphics.Cursor
ActiveCursor.RotateTo(degrees, 600, Easing.OutQuint); ActiveCursor.RotateTo(degrees, 600, Easing.OutQuint);
} }
}
return base.OnMouseMove(state); return base.OnMouseMove(state);
} }
@ -61,6 +73,7 @@ namespace osu.Game.Graphics.Cursor
if (!state.Mouse.HasMainButtonPressed) if (!state.Mouse.HasMainButtonPressed)
{ {
dragging = false; dragging = false;
startRotation = false;
((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, Easing.OutQuint); ((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, Easing.OutQuint);
ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), Easing.OutElasticHalf); ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), Easing.OutElasticHalf);

View File

@ -9,6 +9,6 @@ namespace osu.Game.Graphics.Cursor
{ {
public class OsuContextMenuContainer : ContextMenuContainer public class OsuContextMenuContainer : ContextMenuContainer
{ {
protected override ContextMenu<ContextMenuItem> CreateContextMenu() => new OsuContextMenu<ContextMenuItem>(); protected override Menu CreateMenu() => new OsuContextMenu();
} }
} }

View File

@ -24,6 +24,8 @@ namespace osu.Game.Graphics.UserInterface
private SampleChannel sampleClick; private SampleChannel sampleClick;
private SampleChannel sampleHover; private SampleChannel sampleHover;
protected Triangles Triangles;
public OsuButton() public OsuButton()
{ {
Height = 40; Height = 40;
@ -52,7 +54,7 @@ namespace osu.Game.Graphics.UserInterface
AddRange(new Drawable[] AddRange(new Drawable[]
{ {
new Triangles Triangles = new Triangles
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
ColourDark = colours.BlueDarker, ColourDark = colours.BlueDarker,

View File

@ -1,30 +1,21 @@
// 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;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class OsuContextMenu<TItem> : ContextMenu<TItem> public class OsuContextMenu : OsuMenu
where TItem : ContextMenuItem
{
protected override Menu<TItem> CreateMenu() => new CustomMenu();
public class CustomMenu : Menu<TItem>
{ {
private const int fade_duration = 250; private const int fade_duration = 250;
public CustomMenu() public OsuContextMenu()
{ {
CornerRadius = 5; CornerRadius = 5;
ItemsContainer.Padding = new MarginPadding { Vertical = OsuContextMenuItem.MARGIN_VERTICAL };
Masking = true;
EdgeEffect = new EdgeEffectParameters EdgeEffect = new EdgeEffectParameters
{ {
Type = EdgeEffectType.Shadow, Type = EdgeEffectType.Shadow,
@ -36,17 +27,12 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
Background.Colour = colours.ContextMenuGray; BackgroundColour = colours.ContextMenuGray;
} }
protected override void AnimateOpen() => this.FadeIn(fade_duration, Easing.OutQuint); protected override void AnimateOpen() => this.FadeIn(fade_duration, Easing.OutQuint);
protected override void AnimateClose() => this.FadeOut(fade_duration, Easing.OutQuint); protected override void AnimateClose() => this.FadeOut(fade_duration, Easing.OutQuint);
protected override void UpdateContentHeight() protected override MarginPadding ItemFlowContainerPadding => new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL };
{
var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight;
this.ResizeTo(new Vector2(1, State == MenuState.Opened ? actualHeight : 0), 300, Easing.OutQuint);
}
}
} }
} }

View File

@ -1,114 +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 OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface
{
public class OsuContextMenuItem : ContextMenuItem
{
private const int transition_length = 80;
private const int margin_horizontal = 17;
public const int MARGIN_VERTICAL = 4;
private const int text_size = 17;
private OsuSpriteText text;
private OsuSpriteText textBold;
private SampleChannel sampleClick;
private SampleChannel sampleHover;
private readonly MenuItemType type;
protected override Container CreateTextContainer(string title) => new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Children = new Drawable[]
{
text = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = text_size,
Text = title,
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
},
textBold = new OsuSpriteText
{
AlwaysPresent = true,
Alpha = 0,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = text_size,
Text = title,
Font = @"Exo2.0-Bold",
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
}
}
};
public OsuContextMenuItem(string title, MenuItemType type = MenuItemType.Standard) : base(title)
{
this.type = type;
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleHover = audio.Sample.Get(@"UI/generic-hover");
sampleClick = audio.Sample.Get(@"UI/generic-click");
BackgroundColour = Color4.Transparent;
BackgroundColourHover = OsuColour.FromHex(@"172023");
updateTextColour();
}
private void updateTextColour()
{
switch (type)
{
case MenuItemType.Standard:
textBold.Colour = text.Colour = Color4.White;
break;
case MenuItemType.Destructive:
textBold.Colour = text.Colour = Color4.Red;
break;
case MenuItemType.Highlighted:
textBold.Colour = text.Colour = OsuColour.FromHex(@"ffcc22");
break;
}
}
protected override bool OnHover(InputState state)
{
sampleHover.Play();
textBold.FadeIn(transition_length, Easing.OutQuint);
text.FadeOut(transition_length, Easing.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
textBold.FadeOut(transition_length, Easing.OutQuint);
text.FadeIn(transition_length, Easing.OutQuint);
base.OnHoverLost(state);
}
protected override bool OnClick(InputState state)
{
sampleClick.Play();
return base.OnClick(state);
}
}
}

View File

@ -14,51 +14,150 @@ using OpenTK;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class OsuDropdown<T> : Dropdown<T> public class OsuDropdown<T> : Dropdown<T>, IHasAccentColour
{ {
protected override DropdownHeader CreateHeader() => new OsuDropdownHeader { AccentColour = AccentColour }; private Color4 accentColour;
public Color4 AccentColour
protected override Menu CreateMenu() => new OsuMenu();
private Color4? accentColour;
public virtual Color4 AccentColour
{ {
get { return accentColour.GetValueOrDefault(); } get { return accentColour; }
set set
{ {
accentColour = value; accentColour = value;
if (Header != null) updateAccentColour();
((OsuDropdownHeader)Header).AccentColour = value;
foreach (var item in MenuItems.OfType<OsuDropdownMenuItem>())
item.AccentColour = value;
} }
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
if (accentColour == null) if (accentColour == default(Color4))
AccentColour = colours.PinkDarker; accentColour = colours.PinkDarker;
updateAccentColour();
} }
protected override DropdownMenuItem<T> CreateMenuItem(string text, T value) => new OsuDropdownMenuItem(text, value) { AccentColour = AccentColour }; private void updateAccentColour()
public class OsuDropdownMenuItem : DropdownMenuItem<T>
{ {
public OsuDropdownMenuItem(string text, T current) : base(text, current) var header = Header as IHasAccentColour;
if (header != null) header.AccentColour = accentColour;
var menu = Menu as IHasAccentColour;
if (menu != null) menu.AccentColour = accentColour;
}
protected override DropdownHeader CreateHeader() => new OsuDropdownHeader();
protected override DropdownMenu CreateMenu() => new OsuDropdownMenu();
#region OsuDropdownMenu
protected class OsuDropdownMenu : DropdownMenu, IHasAccentColour
{
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
public OsuDropdownMenu()
{
CornerRadius = 4;
BackgroundColour = Color4.Black.Opacity(0.5f);
}
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint);
protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint);
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
protected override MarginPadding ItemFlowContainerPadding => new MarginPadding(5);
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
protected override void UpdateMenuHeight()
{
var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight;
this.ResizeHeightTo(State == MenuState.Opened ? actualHeight : 0, 300, Easing.OutQuint);
}
private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour; }
set
{
accentColour = value;
foreach (var c in Children.OfType<IHasAccentColour>())
c.AccentColour = value;
}
}
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuDropdownMenuItem(item) { AccentColour = accentColour };
#region DrawableOsuDropdownMenuItem
protected class DrawableOsuDropdownMenuItem : DrawableDropdownMenuItem, IHasAccentColour
{
private Color4? accentColour;
public Color4 AccentColour
{
get { return accentColour ?? nonAccentSelectedColour; }
set
{
accentColour = value;
updateColours();
}
}
private void updateColours()
{
BackgroundColourHover = accentColour ?? nonAccentHoverColour;
BackgroundColourSelected = accentColour ?? nonAccentSelectedColour;
UpdateBackgroundColour();
UpdateForegroundColour();
}
private Color4 nonAccentHoverColour;
private Color4 nonAccentSelectedColour;
public DrawableOsuDropdownMenuItem(MenuItem item)
: base(item)
{ {
Foreground.Padding = new MarginPadding(2); Foreground.Padding = new MarginPadding(2);
Masking = true; Masking = true;
CornerRadius = 6; CornerRadius = 6;
}
Children = new[] [BackgroundDependencyLoader]
private void load(OsuColour colours)
{ {
new FillFlowContainer BackgroundColour = Color4.Transparent;
nonAccentHoverColour = colours.PinkDarker;
nonAccentSelectedColour = Color4.Black.Opacity(0.5f);
updateColours();
}
protected override void UpdateForegroundColour()
{ {
Direction = FillDirection.Horizontal, base.UpdateForegroundColour();
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, var content = Foreground.Children.FirstOrDefault() as Content;
if (content != null) content.Chevron.Alpha = IsHovered ? 1 : 0;
}
protected override Drawable CreateContent() => new Content();
protected class Content : FillFlowContainer, IHasText
{
public string Text
{
get { return Label.Text; }
set { Label.Text = value; }
}
public readonly OsuSpriteText Label;
public readonly SpriteIcon Chevron;
public Content()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Direction = FillDirection.Horizontal;
Children = new Drawable[] Children = new Drawable[]
{ {
Chevron = new SpriteIcon Chevron = new SpriteIcon
@ -72,49 +171,20 @@ namespace osu.Game.Graphics.UserInterface
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
}, },
Label = new OsuSpriteText { Label = new OsuSpriteText
Text = text, {
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
} }
}
}
}; };
} }
private Color4? accentColour;
protected readonly SpriteIcon Chevron;
protected readonly OsuSpriteText Label;
protected override void FormatForeground(bool hover = false)
{
base.FormatForeground(hover);
Chevron.Alpha = hover ? 1 : 0;
}
public Color4 AccentColour
{
get { return accentColour.GetValueOrDefault(); }
set
{
accentColour = value;
BackgroundColourHover = BackgroundColourSelected = value;
FormatBackground();
FormatForeground();
} }
} }
#endregion
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = Color4.Transparent;
BackgroundColourHover = accentColour ?? colours.PinkDarker;
BackgroundColourSelected = Color4.Black.Opacity(0.5f);
}
} }
#endregion
public class OsuDropdownHeader : DropdownHeader public class OsuDropdownHeader : DropdownHeader, IHasAccentColour
{ {
protected readonly SpriteText Text; protected readonly SpriteText Text;
protected override string Label protected override string Label
@ -125,14 +195,14 @@ namespace osu.Game.Graphics.UserInterface
protected readonly SpriteIcon Icon; protected readonly SpriteIcon Icon;
private Color4? accentColour; private Color4 accentColour;
public virtual Color4 AccentColour public virtual Color4 AccentColour
{ {
get { return accentColour.GetValueOrDefault(); } get { return accentColour; }
set set
{ {
accentColour = value; accentColour = value;
BackgroundColourHover = value; BackgroundColourHover = accentColour;
} }
} }
@ -167,7 +237,7 @@ namespace osu.Game.Graphics.UserInterface
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
BackgroundColour = Color4.Black.Opacity(0.5f); BackgroundColour = Color4.Black.Opacity(0.5f);
BackgroundColourHover = accentColour ?? colours.PinkDarker; BackgroundColourHover = colours.PinkDarker;
} }
} }
} }

View File

@ -1,11 +1,17 @@
// 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; using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
@ -14,19 +20,136 @@ namespace osu.Game.Graphics.UserInterface
public OsuMenu() public OsuMenu()
{ {
CornerRadius = 4; CornerRadius = 4;
Background.Colour = Color4.Black.Opacity(0.5f); BackgroundColour = Color4.Black.Opacity(0.5f);
ItemsContainer.Padding = new MarginPadding(5);
} }
protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint); protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint);
protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint); protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint);
protected override void UpdateContentHeight() protected override void UpdateMenuHeight()
{ {
var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight; var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight;
this.ResizeTo(new Vector2(1, State == MenuState.Opened ? actualHeight : 0), 300, Easing.OutQuint); this.ResizeHeightTo(State == MenuState.Opened ? actualHeight : 0, 300, Easing.OutQuint);
}
protected override MarginPadding ItemFlowContainerPadding => new MarginPadding(5);
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuMenuItem(item);
protected class DrawableOsuMenuItem : DrawableMenuItem
{
private const int margin_horizontal = 17;
private const int text_size = 17;
private const int transition_length = 80;
public const int MARGIN_VERTICAL = 4;
private SampleChannel sampleClick;
private SampleChannel sampleHover;
private TextContainer text;
public DrawableOsuMenuItem(MenuItem item)
: base(item)
{
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleHover = audio.Sample.Get(@"UI/generic-hover");
sampleClick = audio.Sample.Get(@"UI/generic-click");
BackgroundColour = Color4.Transparent;
BackgroundColourHover = OsuColour.FromHex(@"172023");
updateTextColour();
}
private void updateTextColour()
{
switch ((Item as OsuMenuItem)?.Type)
{
default:
case MenuItemType.Standard:
text.Colour = Color4.White;
break;
case MenuItemType.Destructive:
text.Colour = Color4.Red;
break;
case MenuItemType.Highlighted:
text.Colour = OsuColour.FromHex(@"ffcc22");
break;
}
}
protected override bool OnHover(InputState state)
{
sampleHover.Play();
text.BoldText.FadeIn(transition_length, Easing.OutQuint);
text.NormalText.FadeOut(transition_length, Easing.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
text.BoldText.FadeOut(transition_length, Easing.OutQuint);
text.NormalText.FadeIn(transition_length, Easing.OutQuint);
base.OnHoverLost(state);
}
protected override bool OnClick(InputState state)
{
sampleClick.Play();
return base.OnClick(state);
}
protected override Drawable CreateContent() => text = new TextContainer();
private class TextContainer : Container, IHasText
{
public string Text
{
get { return NormalText.Text; }
set
{
NormalText.Text = value;
BoldText.Text = value;
}
}
public readonly SpriteText NormalText;
public readonly SpriteText BoldText;
public TextContainer()
{
Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft;
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
NormalText = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = text_size,
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
},
BoldText = new OsuSpriteText
{
AlwaysPresent = true,
Alpha = 0,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = text_size,
Font = @"Exo2.0-Bold",
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
}
};
}
}
} }
} }
} }

View File

@ -0,0 +1,25 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterface
{
public class OsuMenuItem : MenuItem
{
public readonly MenuItemType Type;
public OsuMenuItem(string text, MenuItemType type = MenuItemType.Standard)
: base(text)
{
Type = type;
}
public OsuMenuItem(string text, MenuItemType type, Action action)
: base(text, action)
{
Type = type;
}
}
}

View File

@ -1,11 +1,16 @@
// 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;
using osu.Framework.Graphics.Containers;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Platform;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
@ -15,6 +20,49 @@ namespace osu.Game.Graphics.UserInterface
public override bool AllowClipboardExport => false; public override bool AllowClipboardExport => false;
private readonly CapsWarning warning;
private GameHost host;
public OsuPasswordTextBox()
{
Add(warning = new CapsWarning
{
Size = new Vector2(20),
Origin = Anchor.CentreRight,
Anchor = Anchor.CentreRight,
Margin = new MarginPadding { Right = 10 },
Alpha = 0,
});
}
[BackgroundDependencyLoader]
private void load(GameHost host)
{
this.host = host;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (args.Key == Key.CapsLock)
updateCapsWarning(host.CapsLockEnabled);
return base.OnKeyDown(state, args);
}
protected override void OnFocus(InputState state)
{
updateCapsWarning(host.CapsLockEnabled);
base.OnFocus(state);
}
protected override void OnFocusLost(InputState state)
{
updateCapsWarning(false);
base.OnFocusLost(state);
}
private void updateCapsWarning(bool visible) => warning.FadeTo(visible ? 1 : 0, 250, Easing.OutQuint);
public class PasswordMaskChar : Container public class PasswordMaskChar : Container
{ {
private readonly CircularContainer circle; private readonly CircularContainer circle;
@ -51,5 +99,21 @@ namespace osu.Game.Graphics.UserInterface
circle.ResizeTo(new Vector2(0.8f), 500, Easing.OutQuint); circle.ResizeTo(new Vector2(0.8f), 500, Easing.OutQuint);
} }
} }
private class CapsWarning : SpriteIcon, IHasTooltip
{
public string TooltipText => @"Caps lock is active";
public CapsWarning()
{
Icon = FontAwesome.fa_warning;
}
[BackgroundDependencyLoader]
private void load(OsuColour colour)
{
Colour = colour.YellowLight;
}
}
} }
} }

View File

@ -37,34 +37,34 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
if (accentColour == null) if (accentColour == default(Color4))
AccentColour = colours.Blue; AccentColour = colours.Blue;
} }
private Color4? accentColour; private Color4 accentColour;
public Color4 AccentColour public Color4 AccentColour
{ {
get { return accentColour.GetValueOrDefault(); } get { return accentColour; }
set set
{ {
accentColour = value; accentColour = value;
var dropDown = Dropdown as OsuTabDropdown; var dropdown = Dropdown as IHasAccentColour;
if (dropDown != null) if (dropdown != null)
dropDown.AccentColour = value; dropdown.AccentColour = value;
foreach (var item in TabContainer.Children.OfType<OsuTabItem>()) foreach (var i in TabContainer.Children.OfType<IHasAccentColour>())
item.AccentColour = value; i.AccentColour = value;
} }
} }
public class OsuTabItem : TabItem<T> public class OsuTabItem : TabItem<T>, IHasAccentColour
{ {
protected readonly SpriteText Text; protected readonly SpriteText Text;
private readonly Box box; private readonly Box box;
private Color4? accentColour; private Color4 accentColour;
public Color4 AccentColour public Color4 AccentColour
{ {
get { return accentColour.GetValueOrDefault(); } get { return accentColour; }
set set
{ {
accentColour = value; accentColour = value;
@ -103,7 +103,7 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
if (accentColour == null) if (accentColour == default(Color4))
AccentColour = colours.Blue; AccentColour = colours.Blue;
} }
@ -140,38 +140,55 @@ namespace osu.Game.Graphics.UserInterface
protected override void OnDeactivated() => fadeInactive(); protected override void OnDeactivated() => fadeInactive();
} }
// todo: this needs to go
private class OsuTabDropdown : OsuDropdown<T> private class OsuTabDropdown : OsuDropdown<T>
{ {
protected override DropdownHeader CreateHeader() => new OsuTabDropdownHeader
{
AccentColour = AccentColour,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
};
protected override DropdownMenuItem<T> CreateMenuItem(string text, T value)
{
var item = base.CreateMenuItem(text, value);
item.ForegroundColourHover = Color4.Black;
return item;
}
public OsuTabDropdown() public OsuTabDropdown()
{ {
DropdownMenu.Anchor = Anchor.TopRight;
DropdownMenu.Origin = Anchor.TopRight;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
DropdownMenu.Background.Colour = Color4.Black.Opacity(0.7f);
DropdownMenu.MaxHeight = 400;
} }
protected override DropdownMenu CreateMenu() => new OsuTabDropdownMenu();
protected override DropdownHeader CreateHeader() => new OsuTabDropdownHeader
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight
};
private class OsuTabDropdownMenu : OsuDropdownMenu
{
public OsuTabDropdownMenu()
{
Anchor = Anchor.TopRight;
Origin = Anchor.TopRight;
BackgroundColour = Color4.Black.Opacity(0.7f);
MaxHeight = 400;
}
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuTabDropdownMenuItem(item) { AccentColour = AccentColour };
private class DrawableOsuTabDropdownMenuItem : DrawableOsuDropdownMenuItem
{
public DrawableOsuTabDropdownMenuItem(MenuItem item)
: base(item)
{
ForegroundColourHover = Color4.Black;
}
}
}
protected class OsuTabDropdownHeader : OsuDropdownHeader protected class OsuTabDropdownHeader : OsuDropdownHeader
{ {
public override Color4 AccentColour public override Color4 AccentColour
{ {
get { return base.AccentColour; } get
{
return base.AccentColour;
}
set set
{ {
base.AccentColour = value; base.AccentColour = value;
@ -179,18 +196,6 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
protected override bool OnHover(InputState state)
{
Foreground.Colour = BackgroundColour;
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
Foreground.Colour = BackgroundColourHover;
base.OnHoverLost(state);
}
public OsuTabDropdownHeader() public OsuTabDropdownHeader()
{ {
RelativeSizeAxes = Axes.None; RelativeSizeAxes = Axes.None;
@ -220,6 +225,18 @@ namespace osu.Game.Graphics.UserInterface
Padding = new MarginPadding { Left = 5, Right = 5 }; Padding = new MarginPadding { Left = 5, Right = 5 };
} }
protected override bool OnHover(InputState state)
{
Foreground.Colour = BackgroundColour;
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
Foreground.Colour = BackgroundColourHover;
base.OnHoverLost(state);
}
} }
} }
} }

View File

@ -0,0 +1,67 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using OpenTK.Graphics;
namespace osu.Game.Graphics.UserInterface
{
public class ProgressBar : SliderBar<double>
{
public Action<double> OnSeek;
private readonly Box fill;
private readonly Box background;
public Color4 FillColour
{
set { fill.Colour = value; }
}
public Color4 BackgroundColour
{
set
{
background.Alpha = 1;
background.Colour = value;
}
}
public double EndTime
{
set { CurrentNumber.MaxValue = value; }
}
public double CurrentTime
{
set { CurrentNumber.Value = value; }
}
public ProgressBar()
{
CurrentNumber.MinValue = 0;
CurrentNumber.MaxValue = 1;
RelativeSizeAxes = Axes.X;
Children = new Drawable[]
{
background = new Box
{
Alpha = 0,
RelativeSizeAxes = Axes.Both
},
fill = new Box { RelativeSizeAxes = Axes.Y }
};
}
protected override void UpdateValue(float value)
{
fill.Width = value * UsableWidth;
}
protected override void OnUserChange() => OnSeek?.Invoke(Current);
}
}

View File

@ -2,6 +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 osu.Framework.Input;
using osu.Framework.Input.Handlers; using osu.Framework.Input.Handlers;
using osu.Framework.Platform; using osu.Framework.Platform;
using OpenTK; using OpenTK;
@ -29,5 +31,18 @@ namespace osu.Game.Input.Handlers
public override bool IsActive => true; public override bool IsActive => true;
public override int Priority => 0; public override int Priority => 0;
public class ReplayState<T> : InputState
where T : struct
{
public List<T> PressedActions;
public override InputState Clone()
{
var clone = (ReplayState<T>)base.Clone();
clone.PressedActions = new List<T>(PressedActions);
return clone;
}
}
} }
} }

View File

@ -22,7 +22,7 @@ namespace osu.Game.Input
{ {
var ruleset = info.CreateInstance(); var ruleset = info.CreateInstance();
foreach (var variant in ruleset.AvailableVariants) foreach (var variant in ruleset.AvailableVariants)
insertDefaults(ruleset.GetDefaultKeyBindings(), info.ID, variant); insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant);
} }
} }

View File

@ -11,11 +11,11 @@ namespace osu.Game.Online.API
/// An API request with a well-defined response type. /// An API request with a well-defined response type.
/// </summary> /// </summary>
/// <typeparam name="T">Type of the response (used for deserialisation).</typeparam> /// <typeparam name="T">Type of the response (used for deserialisation).</typeparam>
public class APIRequest<T> : APIRequest public abstract class APIRequest<T> : APIRequest
{ {
protected override WebRequest CreateWebRequest() => new JsonWebRequest<T>(Uri); protected override WebRequest CreateWebRequest() => new JsonWebRequest<T>(Uri);
public APIRequest() protected APIRequest()
{ {
base.Success += onSuccess; base.Success += onSuccess;
} }
@ -28,10 +28,36 @@ namespace osu.Game.Online.API
public new event APISuccessHandler<T> Success; public new event APISuccessHandler<T> Success;
} }
public abstract class APIDownloadRequest : APIRequest
{
protected override WebRequest CreateWebRequest()
{
var request = new WebRequest(Uri);
request.DownloadProgress += request_Progress;
return request;
}
private void request_Progress(WebRequest request, long current, long total) => API.Scheduler.Add(delegate { Progress?.Invoke(current, total); });
protected APIDownloadRequest()
{
base.Success += onSuccess;
}
private void onSuccess()
{
Success?.Invoke(WebRequest.ResponseData);
}
public event APIProgressHandler Progress;
public new event APISuccessHandler<byte[]> Success;
}
/// <summary> /// <summary>
/// AN API request with no specified response type. /// AN API request with no specified response type.
/// </summary> /// </summary>
public class APIRequest public abstract class APIRequest
{ {
/// <summary> /// <summary>
/// The maximum amount of time before this request will fail. /// The maximum amount of time before this request will fail.
@ -42,7 +68,7 @@ namespace osu.Game.Online.API
protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri); protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri);
protected virtual string Uri => $@"{api.Endpoint}/api/v2/{Target}"; protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}";
private double remainingTime => Math.Max(0, Timeout - (DateTime.Now.TotalMilliseconds() - (startTime ?? 0))); private double remainingTime => Math.Max(0, Timeout - (DateTime.Now.TotalMilliseconds() - (startTime ?? 0)));
@ -52,7 +78,7 @@ namespace osu.Game.Online.API
public double StartTime => startTime ?? -1; public double StartTime => startTime ?? -1;
private APIAccess api; protected APIAccess API;
protected WebRequest WebRequest; protected WebRequest WebRequest;
public event APISuccessHandler Success; public event APISuccessHandler Success;
@ -64,7 +90,7 @@ namespace osu.Game.Online.API
public void Perform(APIAccess api) public void Perform(APIAccess api)
{ {
this.api = api; API = api;
if (checkAndProcessFailure()) if (checkAndProcessFailure())
return; return;
@ -109,9 +135,9 @@ namespace osu.Game.Online.API
/// <returns>Whether we are in a failed or cancelled state.</returns> /// <returns>Whether we are in a failed or cancelled state.</returns>
private bool checkAndProcessFailure() private bool checkAndProcessFailure()
{ {
if (api == null || pendingFailure == null) return cancelled; if (API == null || pendingFailure == null) return cancelled;
api.Scheduler.Add(pendingFailure); API.Scheduler.Add(pendingFailure);
pendingFailure = null; pendingFailure = null;
return true; return true;
} }
@ -119,5 +145,6 @@ namespace osu.Game.Online.API
public delegate void APIFailureHandler(Exception e); public delegate void APIFailureHandler(Exception e);
public delegate void APISuccessHandler(); public delegate void APISuccessHandler();
public delegate void APIProgressHandler(long current, long total);
public delegate void APISuccessHandler<in T>(T content); public delegate void APISuccessHandler<in T>(T content);
} }

View File

@ -46,6 +46,9 @@ namespace osu.Game.Online.API.Requests
[JsonProperty(@"favourite_count")] [JsonProperty(@"favourite_count")]
private int favouriteCount { get; set; } private int favouriteCount { get; set; }
[JsonProperty(@"id")]
private int onlineId { get; set; }
[JsonProperty(@"beatmaps")] [JsonProperty(@"beatmaps")]
private IEnumerable<GetBeatmapSetsBeatmapResponse> beatmaps { get; set; } private IEnumerable<GetBeatmapSetsBeatmapResponse> beatmaps { get; set; }
@ -53,6 +56,7 @@ namespace osu.Game.Online.API.Requests
{ {
return new BeatmapSetInfo return new BeatmapSetInfo
{ {
OnlineBeatmapSetID = onlineId,
Metadata = this, Metadata = this,
OnlineInfo = new BeatmapSetOnlineInfo OnlineInfo = new BeatmapSetOnlineInfo
{ {

View File

@ -219,15 +219,28 @@ namespace osu.Game
dependencies.Cache(settings); dependencies.Cache(settings);
dependencies.Cache(social); dependencies.Cache(social);
dependencies.Cache(direct);
dependencies.Cache(chat); dependencies.Cache(chat);
dependencies.Cache(userProfile); dependencies.Cache(userProfile);
dependencies.Cache(musicController); dependencies.Cache(musicController);
dependencies.Cache(notificationOverlay); dependencies.Cache(notificationOverlay);
dependencies.Cache(dialogOverlay); dependencies.Cache(dialogOverlay);
// ensure both overlays aren't presented at the same time // ensure only one of these overlays are open at once.
chat.StateChanged += (container, state) => social.State = state == Visibility.Visible ? Visibility.Hidden : social.State; var singleDisplayOverlays = new OverlayContainer[] { chat, social, direct };
social.StateChanged += (container, state) => chat.State = state == Visibility.Visible ? Visibility.Hidden : chat.State; foreach (var overlay in singleDisplayOverlays)
{
overlay.StateChanged += (container, state) =>
{
if (state == Visibility.Hidden) return;
foreach (var c in singleDisplayOverlays)
{
if (c == container) continue;
c.State = Visibility.Hidden;
}
};
}
LoadComponentAsync(Toolbar = new Toolbar LoadComponentAsync(Toolbar = new Toolbar
{ {

View File

@ -152,14 +152,10 @@ namespace osu.Game
Beatmap.ValueChanged += b => Beatmap.ValueChanged += b =>
{ {
// compare to last baetmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo) // compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo)
if (lastBeatmap?.Track != b.Track) if (lastBeatmap?.Track != b.Track)
{ {
// this disposal is done to stop the audio track. lastBeatmap?.Track?.Dispose();
// it may not be exactly what we want for cases beatmaps are reused, as it will
// trigger a fresh load of contained resources.
lastBeatmap?.Dispose();
Audio.Track.AddItem(b.Track); Audio.Track.AddItem(b.Track);
} }

View File

@ -12,6 +12,7 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Framework.Input;
namespace osu.Game.Overlays.Direct namespace osu.Game.Overlays.Direct
{ {
@ -25,23 +26,11 @@ namespace osu.Game.Overlays.Direct
public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap) public DirectGridPanel(BeatmapSetInfo beatmap) : base(beatmap)
{ {
Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image) Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image)
CornerRadius = 4;
Masking = true;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 1f),
Radius = 3f,
Colour = Color4.Black.Opacity(0.25f),
};
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
this.FadeInFromZero(200, Easing.Out);
bottomPanel.LayoutDuration = 200; bottomPanel.LayoutDuration = 200;
bottomPanel.LayoutEasing = Easing.Out; bottomPanel.LayoutEasing = Easing.Out;
bottomPanel.Origin = Anchor.BottomLeft; bottomPanel.Origin = Anchor.BottomLeft;
@ -50,14 +39,10 @@ namespace osu.Game.Overlays.Direct
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, LocalisationEngine localisation) private void load(OsuColour colours, LocalisationEngine localisation)
{ {
Children = new[] Content.CornerRadius = 4;
AddRange(new Drawable[]
{ {
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
CreateBackground(),
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -185,7 +170,13 @@ namespace osu.Game.Overlays.Direct
new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0), new Statistic(FontAwesome.fa_heart, SetInfo.OnlineInfo?.FavouriteCount ?? 0),
}, },
}, },
}; });
}
protected override bool OnClick(InputState state)
{
StartDownload();
return true;
} }
} }
} }

View File

@ -28,35 +28,15 @@ namespace osu.Game.Overlays.Direct
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = height; Height = height;
CornerRadius = 5;
Masking = true;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 1f),
Radius = 3f,
Colour = Color4.Black.Opacity(0.25f),
};
}
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeInFromZero(200, Easing.Out);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(LocalisationEngine localisation) private void load(LocalisationEngine localisation)
{ {
Children = new[] Content.CornerRadius = 5;
AddRange(new Drawable[]
{ {
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
CreateBackground(),
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -144,10 +124,11 @@ namespace osu.Game.Overlays.Direct
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
Size = new Vector2(height - vertical_padding * 2), Size = new Vector2(height - vertical_padding * 2),
Action = StartDownload
}, },
}, },
}, },
}; });
} }
private class DownloadButton : OsuClickableContainer private class DownloadButton : OsuClickableContainer

View File

@ -2,26 +2,220 @@
// 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.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using System.IO;
using System.Threading.Tasks;
using OpenTK; using OpenTK;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using OpenTK.Graphics;
using osu.Framework.Input;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Framework.Logging;
using osu.Game.Beatmaps.IO;
using osu.Game.Overlays.Notifications;
namespace osu.Game.Overlays.Direct namespace osu.Game.Overlays.Direct
{ {
public abstract class DirectPanel : Container public abstract class DirectPanel : Container
{ {
protected readonly BeatmapSetInfo SetInfo; public readonly BeatmapSetInfo SetInfo;
protected Box BlackBackground;
private const double hover_transition_time = 400;
private Container content;
private APIAccess api;
private ProgressBar progressBar;
private BeatmapManager beatmaps;
private NotificationOverlay notifications;
protected override Container<Drawable> Content => content;
protected DirectPanel(BeatmapSetInfo setInfo) protected DirectPanel(BeatmapSetInfo setInfo)
{ {
SetInfo = setInfo; SetInfo = setInfo;
} }
private readonly EdgeEffectParameters edgeEffectNormal = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 1f),
Radius = 2f,
Colour = Color4.Black.Opacity(0.25f),
};
private readonly EdgeEffectParameters edgeEffectHovered = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 5f),
Radius = 10f,
Colour = Color4.Black.Opacity(0.3f),
};
[BackgroundDependencyLoader(permitNulls: true)]
private void load(APIAccess api, BeatmapManager beatmaps, OsuColour colours, NotificationOverlay notifications)
{
this.api = api;
this.beatmaps = beatmaps;
this.notifications = notifications;
AddInternal(content = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
EdgeEffect = edgeEffectNormal,
Children = new[]
{
// temporary blackness until the actual background loads.
BlackBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
CreateBackground(),
progressBar = new ProgressBar
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Height = 0,
Alpha = 0,
BackgroundColour = Color4.Black.Opacity(0.7f),
FillColour = colours.Blue,
Depth = -1,
},
}
});
}
protected override bool OnHover(InputState state)
{
content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint);
content.MoveToY(-4, hover_transition_time, Easing.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint);
content.MoveToY(0, hover_transition_time, Easing.OutQuint);
base.OnHoverLost(state);
}
// this should eventually be moved to a more central place, like BeatmapManager.
private DownloadBeatmapSetRequest downloadRequest;
protected void StartDownload()
{
if (api == null) return;
// we already have an active download running.
if (downloadRequest != null)
{
content.MoveToX(-5, 50, Easing.OutSine).Then()
.MoveToX(5, 100, Easing.InOutSine).Then()
.MoveToX(-5, 100, Easing.InOutSine).Then()
.MoveToX(0, 50, Easing.InSine).Then();
return;
}
if (!api.LocalUser.Value.IsSupporter)
{
notifications.Post(new SimpleNotification
{
Icon = FontAwesome.fa_superpowers,
Text = "You gotta be a supporter to download for now 'yo"
});
return;
}
progressBar.FadeIn(400, Easing.OutQuint);
progressBar.ResizeHeightTo(4, 400, Easing.OutQuint);
progressBar.Current.Value = 0;
ProgressNotification downloadNotification = new ProgressNotification
{
Text = $"Downloading {SetInfo.Metadata.Artist} - {SetInfo.Metadata.Title}",
};
downloadRequest = new DownloadBeatmapSetRequest(SetInfo);
downloadRequest.Failure += e =>
{
progressBar.Current.Value = 0;
progressBar.FadeOut(500);
downloadNotification.State = ProgressNotificationState.Completed;
Logger.Error(e, "Failed to get beatmap download information");
downloadRequest = null;
};
downloadRequest.Progress += (current, total) =>
{
float progress = (float)current / total;
progressBar.Current.Value = progress;
downloadNotification.State = ProgressNotificationState.Active;
downloadNotification.Progress = progress;
};
downloadRequest.Success += data =>
{
progressBar.Current.Value = 1;
progressBar.FadeOut(500);
downloadNotification.State = ProgressNotificationState.Completed;
using (var stream = new MemoryStream(data))
using (var archive = new OszArchiveReader(stream))
beatmaps.Import(archive);
};
downloadNotification.CancelRequested += () =>
{
downloadRequest.Cancel();
downloadRequest = null;
return true;
};
notifications.Post(downloadNotification);
// don't run in the main api queue as this is a long-running task.
Task.Run(() => downloadRequest.Perform(api));
}
public class DownloadBeatmapSetRequest : APIDownloadRequest
{
private readonly BeatmapSetInfo beatmapSet;
public DownloadBeatmapSetRequest(BeatmapSetInfo beatmapSet)
{
this.beatmapSet = beatmapSet;
}
protected override string Target => $@"beatmapsets/{beatmapSet.OnlineBeatmapSetID}/download";
}
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeInFromZero(200, Easing.Out);
}
protected List<DifficultyIcon> GetDifficultyIcons() protected List<DifficultyIcon> GetDifficultyIcons()
{ {
var icons = new List<DifficultyIcon>(); var icons = new List<DifficultyIcon>();
@ -38,7 +232,11 @@ namespace osu.Game.Overlays.Direct
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fill, FillMode = FillMode.Fill,
OnLoadComplete = d => d.FadeInFromZero(400, Easing.Out), OnLoadComplete = d =>
{
d.FadeInFromZero(400, Easing.Out);
BlackBackground.Delay(400).FadeOut();
},
}) })
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,

View File

@ -30,7 +30,7 @@ namespace osu.Game.Overlays
private readonly FillFlowContainer resultCountsContainer; private readonly FillFlowContainer resultCountsContainer;
private readonly OsuSpriteText resultCountsText; private readonly OsuSpriteText resultCountsText;
private readonly FillFlowContainer<DirectPanel> panels; private FillFlowContainer<DirectPanel> panels;
protected override Color4 BackgroundColour => OsuColour.FromHex(@"485e74"); protected override Color4 BackgroundColour => OsuColour.FromHex(@"485e74");
protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"465b71"); protected override Color4 TrianglesColourLight => OsuColour.FromHex(@"465b71");
@ -48,17 +48,6 @@ namespace osu.Game.Overlays
if (beatmapSets?.Equals(value) ?? false) return; if (beatmapSets?.Equals(value) ?? false) return;
beatmapSets = value; beatmapSets = value;
if (BeatmapSets == null)
{
foreach (var p in panels.Children)
{
p.FadeOut(200);
p.Expire();
}
return;
}
recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value); recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value);
} }
} }
@ -108,13 +97,6 @@ namespace osu.Game.Overlays
}, },
} }
}, },
panels = new FillFlowContainer<DirectPanel>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(panel_padding),
Margin = new MarginPadding { Top = 10 },
},
}; };
Filter.Search.Current.ValueChanged += text => { if (text != string.Empty) Header.Tabs.Current.Value = DirectTab.Search; }; Filter.Search.Current.ValueChanged += text => { if (text != string.Empty) Header.Tabs.Current.Value = DirectTab.Search; };
@ -161,11 +143,19 @@ namespace osu.Game.Overlays
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, APIAccess api, RulesetStore rulesets) private void load(OsuColour colours, APIAccess api, RulesetStore rulesets, BeatmapManager beatmaps)
{ {
this.api = api; this.api = api;
this.rulesets = rulesets; this.rulesets = rulesets;
resultCountsContainer.Colour = colours.Yellow; resultCountsContainer.Colour = colours.Yellow;
beatmaps.BeatmapSetAdded += setAdded;
}
private void setAdded(BeatmapSetInfo set)
{
// if a new map was imported, we should remove it from search results (download completed etc.)
panels?.FirstOrDefault(p => p.SetInfo.OnlineBeatmapSetID == set.OnlineBeatmapSetID)?.FadeOut(400).Expire();
} }
private void updateResultCounts() private void updateResultCounts()
@ -185,9 +175,22 @@ namespace osu.Game.Overlays
private void recreatePanels(PanelDisplayStyle displayStyle) private void recreatePanels(PanelDisplayStyle displayStyle)
{ {
if (panels != null)
{
panels.FadeOut(200);
panels.Expire();
panels = null;
}
if (BeatmapSets == null) return; if (BeatmapSets == null) return;
panels.ChildrenEnumerable = BeatmapSets.Select<BeatmapSetInfo, DirectPanel>(b => var newPanels = new FillFlowContainer<DirectPanel>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(panel_padding),
Margin = new MarginPadding { Top = 10 },
ChildrenEnumerable = BeatmapSets.Select<BeatmapSetInfo, DirectPanel>(b =>
{ {
switch (displayStyle) switch (displayStyle)
{ {
@ -196,6 +199,13 @@ namespace osu.Game.Overlays
default: default:
return new DirectListPanel(b); return new DirectListPanel(b);
} }
})
};
LoadComponentAsync(newPanels, p =>
{
if (panels != null) ScrollFlow.Remove(panels);
ScrollFlow.Add(panels = newPanels);
}); });
} }

View File

@ -3,21 +3,29 @@
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Overlays.Settings;
namespace osu.Game.Overlays.KeyBinding namespace osu.Game.Overlays.KeyBinding
{ {
public class GlobalKeyBindingsSection : KeyBindingsSection public class GlobalKeyBindingsSection : SettingsSection
{ {
private readonly string name; public override FontAwesome Icon => FontAwesome.fa_osu_hot;
public override string Header => "Global";
public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; public GlobalKeyBindingsSection(KeyBindingInputManager manager)
public override string Header => name;
public GlobalKeyBindingsSection(KeyBindingInputManager manager, string name)
{ {
this.name = name; Add(new DefaultBindingsSubsection(manager));
}
private class DefaultBindingsSubsection : KeyBindingsSubsection
{
protected override string Header => string.Empty;
public DefaultBindingsSubsection(KeyBindingInputManager manager)
: base(null)
{
Defaults = manager.DefaultKeyBindings; Defaults = manager.DefaultKeyBindings;
} }
} }
}
} }

View File

@ -1,7 +1,6 @@
// 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 System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -22,7 +21,7 @@ namespace osu.Game.Overlays.KeyBinding
{ {
internal class KeyBindingRow : Container, IFilterable internal class KeyBindingRow : Container, IFilterable
{ {
private readonly Enum action; private readonly object action;
private readonly IEnumerable<Framework.Input.Bindings.KeyBinding> bindings; private readonly IEnumerable<Framework.Input.Bindings.KeyBinding> bindings;
private const float transition_time = 150; private const float transition_time = 150;
@ -50,7 +49,7 @@ namespace osu.Game.Overlays.KeyBinding
public string[] FilterTerms => new[] { text.Text }.Concat(bindings.Select(b => b.KeyCombination.ReadableString())).ToArray(); public string[] FilterTerms => new[] { text.Text }.Concat(bindings.Select(b => b.KeyCombination.ReadableString())).ToArray();
public KeyBindingRow(Enum action, IEnumerable<Framework.Input.Bindings.KeyBinding> bindings) public KeyBindingRow(object action, IEnumerable<Framework.Input.Bindings.KeyBinding> bindings)
{ {
this.action = action; this.action = action;
this.bindings = bindings; this.bindings = bindings;
@ -110,16 +109,27 @@ namespace osu.Game.Overlays.KeyBinding
buttons.Add(new KeyButton(b)); buttons.Add(new KeyButton(b));
} }
public void RestoreDefaults()
{
int i = 0;
foreach (var d in Defaults)
{
var button = buttons[i++];
button.UpdateKeyCombination(d);
store.Update(button.KeyBinding);
}
}
protected override bool OnHover(InputState state) protected override bool OnHover(InputState state)
{ {
this.FadeEdgeEffectTo<Container>(1, transition_time, Easing.OutQuint); FadeEdgeEffectTo(1, transition_time, Easing.OutQuint);
return base.OnHover(state); return base.OnHover(state);
} }
protected override void OnHoverLost(InputState state) protected override void OnHoverLost(InputState state)
{ {
this.FadeEdgeEffectTo<Container>(0, transition_time, Easing.OutQuint); FadeEdgeEffectTo(0, transition_time, Easing.OutQuint);
base.OnHoverLost(state); base.OnHoverLost(state);
} }
@ -130,6 +140,8 @@ namespace osu.Game.Overlays.KeyBinding
public bool AllowMainMouseButtons; public bool AllowMainMouseButtons;
public IEnumerable<KeyCombination> Defaults;
private bool isModifier(Key k) => k < Key.F1; private bool isModifier(Key k) => k < Key.F1;
protected override bool OnClick(InputState state) => true; protected override bool OnClick(InputState state) => true;

View File

@ -1,49 +0,0 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Game.Input;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets;
using OpenTK;
namespace osu.Game.Overlays.KeyBinding
{
public abstract class KeyBindingsSection : SettingsSection
{
protected IEnumerable<Framework.Input.Bindings.KeyBinding> Defaults;
protected RulesetInfo Ruleset;
protected KeyBindingsSection()
{
FlowContent.Spacing = new Vector2(0, 1);
}
[BackgroundDependencyLoader]
private void load(KeyBindingStore store)
{
var enumType = Defaults?.FirstOrDefault()?.Action?.GetType();
if (enumType == null) return;
// for now let's just assume a variant of zero.
// this will need to be implemented in a better way in the future.
int? variant = null;
if (Ruleset != null)
variant = 0;
var bindings = store.Query(Ruleset?.ID, variant);
foreach (Enum v in Enum.GetValues(enumType))
// one row per valid action.
Add(new KeyBindingRow(v, bindings.Where(b => b.Action.Equals((int)(object)v)))
{
AllowMainMouseButtons = Ruleset != null
});
}
}
}

View File

@ -0,0 +1,72 @@
// 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.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets;
using OpenTK;
using osu.Game.Graphics;
namespace osu.Game.Overlays.KeyBinding
{
public abstract class KeyBindingsSubsection : SettingsSubsection
{
protected IEnumerable<Framework.Input.Bindings.KeyBinding> Defaults;
protected RulesetInfo Ruleset;
private readonly int? variant;
protected KeyBindingsSubsection(int? variant)
{
this.variant = variant;
FlowContent.Spacing = new Vector2(0, 1);
}
[BackgroundDependencyLoader]
private void load(KeyBindingStore store)
{
var bindings = store.Query(Ruleset?.ID, variant);
foreach (var defaultGroup in Defaults.GroupBy(d => d.Action))
{
// one row per valid action.
Add(new KeyBindingRow(defaultGroup.Key, bindings.Where(b => b.Action.Equals((int)defaultGroup.Key)))
{
AllowMainMouseButtons = Ruleset != null,
Defaults = defaultGroup.Select(d => d.KeyCombination)
});
}
Add(new ResetButton
{
Action = () => Children.OfType<KeyBindingRow>().ForEach(k => k.RestoreDefaults())
});
}
}
internal class ResetButton : OsuButton
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Text = "Reset";
RelativeSizeAxes = Axes.X;
Margin = new MarginPadding { Top = 5 };
Height = 20;
Content.CornerRadius = 5;
BackgroundColour = colours.PinkDark;
Triangles.ColourDark = colours.PinkDarker;
Triangles.ColourLight = colours.Pink;
}
}
}

View File

@ -2,20 +2,26 @@
// 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.Graphics; using osu.Game.Graphics;
using osu.Game.Overlays.Settings;
using osu.Game.Rulesets; using osu.Game.Rulesets;
namespace osu.Game.Overlays.KeyBinding namespace osu.Game.Overlays.KeyBinding
{ {
public class RulesetBindingsSection : KeyBindingsSection public class RulesetBindingsSection : SettingsSection
{ {
public override FontAwesome Icon => FontAwesome.fa_osu_mod_nofail; public override FontAwesome Icon => FontAwesome.fa_osu_hot;
public override string Header => Ruleset.Name; public override string Header => ruleset.Name;
private readonly RulesetInfo ruleset;
public RulesetBindingsSection(RulesetInfo ruleset) public RulesetBindingsSection(RulesetInfo ruleset)
{ {
Ruleset = ruleset; this.ruleset = ruleset;
Defaults = ruleset.CreateInstance().GetDefaultKeyBindings(); var r = ruleset.CreateInstance();
foreach (var variant in r.AvailableVariants)
Add(new VariantBindingsSubsection(ruleset, variant));
} }
} }
} }

View File

@ -0,0 +1,24 @@
// 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.Rulesets;
namespace osu.Game.Overlays.KeyBinding
{
public class VariantBindingsSubsection : KeyBindingsSubsection
{
protected override string Header => variantName;
private readonly string variantName;
public VariantBindingsSubsection(RulesetInfo ruleset, int variant)
: base(variant)
{
Ruleset = ruleset;
var rulesetInstance = ruleset.CreateInstance();
variantName = rulesetInstance.GetVariantName(variant);
Defaults = rulesetInstance.GetDefaultKeyBindings(variant);
}
}
}

View File

@ -17,7 +17,7 @@ namespace osu.Game.Overlays
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader(permitNulls: true)]
private void load(RulesetStore rulesets, GlobalKeyBindingInputManager global) private void load(RulesetStore rulesets, GlobalKeyBindingInputManager global)
{ {
AddSection(new GlobalKeyBindingsSection(global, "Global")); AddSection(new GlobalKeyBindingsSection(global));
foreach (var ruleset in rulesets.AllRulesets) foreach (var ruleset in rulesets.AllRulesets)
AddSection(new RulesetBindingsSection(ruleset)); AddSection(new RulesetBindingsSection(ruleset));

View File

@ -15,15 +15,37 @@ namespace osu.Game.Overlays.Music
{ {
public class CollectionsDropdown<T> : OsuDropdown<T> public class CollectionsDropdown<T> : OsuDropdown<T>
{ {
protected override DropdownHeader CreateHeader() => new CollectionsHeader { AccentColour = AccentColour };
protected override Menu CreateMenu() => new CollectionsMenu();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
AccentColour = colours.Gray6; AccentColour = colours.Gray6;
} }
protected override DropdownHeader CreateHeader() => new CollectionsHeader();
protected override DropdownMenu CreateMenu() => new CollectionsMenu();
private class CollectionsMenu : OsuDropdownMenu
{
public CollectionsMenu()
{
CornerRadius = 5;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.3f),
Radius = 3,
Offset = new Vector2(0f, 1f),
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = colours.Gray4;
}
}
private class CollectionsHeader : OsuDropdownHeader private class CollectionsHeader : OsuDropdownHeader
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -48,26 +70,5 @@ namespace osu.Game.Overlays.Music
}; };
} }
} }
private class CollectionsMenu : OsuMenu
{
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Background.Colour = colours.Gray4;
}
public CollectionsMenu()
{
CornerRadius = 5;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.3f),
Radius = 3,
Offset = new Vector2(0f, 1f),
};
}
}
} }
} }

View File

@ -15,7 +15,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Framework.Threading; using osu.Framework.Threading;
@ -349,23 +348,23 @@ namespace osu.Game.Overlays
playerContainer.Add(new AsyncLoadWrapper(new Background(beatmap) playerContainer.Add(new AsyncLoadWrapper(new Background(beatmap)
{ {
OnLoadComplete = d => OnLoadComplete = newBackground =>
{ {
switch (direction) switch (direction)
{ {
case TransformDirection.Next: case TransformDirection.Next:
d.Position = new Vector2(400, 0); newBackground.Position = new Vector2(400, 0);
d.MoveToX(0, 500, Easing.OutCubic); newBackground.MoveToX(0, 500, Easing.OutCubic);
currentBackground.MoveToX(-400, 500, Easing.OutCubic); currentBackground.MoveToX(-400, 500, Easing.OutCubic);
break; break;
case TransformDirection.Prev: case TransformDirection.Prev:
d.Position = new Vector2(-400, 0); newBackground.Position = new Vector2(-400, 0);
d.MoveToX(0, 500, Easing.OutCubic); newBackground.MoveToX(0, 500, Easing.OutCubic);
currentBackground.MoveToX(400, 500, Easing.OutCubic); currentBackground.MoveToX(400, 500, Easing.OutCubic);
break; break;
} }
currentBackground.Expire(); currentBackground.Expire();
currentBackground = d; currentBackground = newBackground;
} }
}) })
{ {
@ -434,49 +433,5 @@ namespace osu.Game.Overlays
sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4"); sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4");
} }
} }
private class ProgressBar : SliderBar<double>
{
public Action<double> OnSeek;
private readonly Box fill;
public Color4 FillColour
{
set { fill.Colour = value; }
}
public double EndTime
{
set { CurrentNumber.MaxValue = value; }
}
public double CurrentTime
{
set { CurrentNumber.Value = value; }
}
public ProgressBar()
{
CurrentNumber.MinValue = 0;
CurrentNumber.MaxValue = 1;
RelativeSizeAxes = Axes.X;
Children = new Drawable[]
{
fill = new Box
{
RelativeSizeAxes = Axes.Y
}
};
}
protected override void UpdateValue(float value)
{
fill.Width = value * UsableWidth;
}
protected override void OnUserChange() => OnSeek?.Invoke(Current);
}
} }
} }

View File

@ -22,7 +22,7 @@ namespace osu.Game.Overlays
private ScrollContainer scrollContainer; private ScrollContainer scrollContainer;
private FlowContainer<NotificationSection> sections; private FlowContainer<NotificationSection> sections;
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Width = width; Width = width;
@ -72,6 +72,13 @@ namespace osu.Game.Overlays
private int runningDepth; private int runningDepth;
private void notificationClosed()
{
// hide ourselves if all notifications have been dismissed.
if (sections.Select(c => c.DisplayedCount).Sum() == 0)
State = Visibility.Hidden;
}
public void Post(Notification notification) public void Post(Notification notification)
{ {
Schedule(() => Schedule(() =>
@ -81,6 +88,8 @@ namespace osu.Game.Overlays
++runningDepth; ++runningDepth;
notification.Depth = notification.DisplayOnTop ? runningDepth : -runningDepth; notification.Depth = notification.DisplayOnTop ? runningDepth : -runningDepth;
notification.Closed += notificationClosed;
var hasCompletionTarget = notification as IHasCompletionTarget; var hasCompletionTarget = notification as IHasCompletionTarget;
if (hasCompletionTarget != null) if (hasCompletionTarget != null)
hasCompletionTarget.CompletionTarget = Post; hasCompletionTarget.CompletionTarget = Post;

View File

@ -19,9 +19,9 @@ namespace osu.Game.Overlays.Notifications
public abstract class Notification : Container public abstract class Notification : Container
{ {
/// <summary> /// <summary>
/// Use requested close. /// User requested close.
/// </summary> /// </summary>
public Action Closed; public event Action Closed;
/// <summary> /// <summary>
/// Run on user activating the notification. Return true to close. /// Run on user activating the notification. Return true to close.
@ -142,12 +142,12 @@ namespace osu.Game.Overlays.Notifications
NotificationContent.MoveToX(0, 500, Easing.OutQuint); NotificationContent.MoveToX(0, 500, Easing.OutQuint);
} }
private bool wasClosed; public bool WasClosed;
public virtual void Close() public virtual void Close()
{ {
if (wasClosed) return; if (WasClosed) return;
wasClosed = true; WasClosed = true;
Closed?.Invoke(); Closed?.Invoke();
this.FadeOut(100); this.FadeOut(100);

View File

@ -24,10 +24,9 @@ namespace osu.Game.Overlays.Notifications
private FlowContainer<Notification> notifications; private FlowContainer<Notification> notifications;
public void Add(Notification notification) public int DisplayedCount => notifications.Count(n => !n.WasClosed);
{
notifications.Add(notification); public void Add(Notification notification) => notifications.Add(notification);
}
public IEnumerable<Type> AcceptTypes; public IEnumerable<Type> AcceptTypes;

View File

@ -152,11 +152,14 @@ namespace osu.Game.Overlays.Notifications
break; break;
case ProgressNotificationState.Active: case ProgressNotificationState.Active:
case ProgressNotificationState.Queued: case ProgressNotificationState.Queued:
if (CancelRequested?.Invoke() != false)
State = ProgressNotificationState.Cancelled; State = ProgressNotificationState.Cancelled;
break; break;
} }
} }
public Func<bool> CancelRequested { get; set; }
/// <summary> /// <summary>
/// The function to post completion notifications back to. /// The function to post completion notifications back to.
/// </summary> /// </summary>

View File

@ -4,17 +4,16 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using OpenTK; using OpenTK;
namespace osu.Game.Overlays.Notifications namespace osu.Game.Overlays.Notifications
{ {
public class SimpleNotification : Notification public class SimpleNotification : Notification
{ {
private string text; private string text = string.Empty;
public string Text public string Text
{ {
get { return text; } get { return text; }
@ -36,7 +35,7 @@ namespace osu.Game.Overlays.Notifications
} }
} }
private readonly SpriteText textDrawable; private readonly TextFlowContainer textDrawable;
private readonly SpriteIcon iconDrawable; private readonly SpriteIcon iconDrawable;
protected Box IconBackgound; protected Box IconBackgound;
@ -59,9 +58,8 @@ namespace osu.Game.Overlays.Notifications
} }
}); });
Content.Add(textDrawable = new OsuSpriteText Content.Add(textDrawable = new TextFlowContainer(t => t.TextSize = 16)
{ {
TextSize = 16,
Colour = OsuColour.Gray(128), Colour = OsuColour.Gray(128),
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,

View File

@ -12,8 +12,9 @@ namespace osu.Game.Overlays.SearchableList
{ {
public class SlimEnumDropdown<T> : OsuEnumDropdown<T> public class SlimEnumDropdown<T> : OsuEnumDropdown<T>
{ {
protected override DropdownHeader CreateHeader() => new SlimDropdownHeader { AccentColour = AccentColour }; protected override DropdownHeader CreateHeader() => new SlimDropdownHeader();
protected override Menu CreateMenu() => new SlimMenu();
protected override DropdownMenu CreateMenu() => new SlimMenu();
private class SlimDropdownHeader : OsuDropdownHeader private class SlimDropdownHeader : OsuDropdownHeader
{ {
@ -31,11 +32,11 @@ namespace osu.Game.Overlays.SearchableList
} }
} }
private class SlimMenu : OsuMenu private class SlimMenu : OsuDropdownMenu
{ {
public SlimMenu() public SlimMenu()
{ {
Background.Colour = Color4.Black.Opacity(0.7f); BackgroundColour = Color4.Black.Opacity(0.7f);
} }
} }
} }

View File

@ -257,9 +257,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
private class UserDropdown : OsuEnumDropdown<UserAction> private class UserDropdown : OsuEnumDropdown<UserAction>
{ {
protected override DropdownHeader CreateHeader() => new UserDropdownHeader { AccentColour = AccentColour }; protected override DropdownHeader CreateHeader() => new UserDropdownHeader();
protected override Menu CreateMenu() => new UserDropdownMenu();
protected override DropdownMenuItem<UserAction> CreateMenuItem(string text, UserAction value) => new UserDropdownMenuItem(text, value) { AccentColour = AccentColour }; protected override DropdownMenu CreateMenu() => new UserDropdownMenu();
public Color4 StatusColour public Color4 StatusColour
{ {
@ -277,6 +277,49 @@ namespace osu.Game.Overlays.Settings.Sections.General
AccentColour = colours.Gray5; AccentColour = colours.Gray5;
} }
private class UserDropdownMenu : OsuDropdownMenu
{
public UserDropdownMenu()
{
Masking = true;
CornerRadius = 5;
Margin = new MarginPadding { Bottom = 5 };
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.25f),
Radius = 4,
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = colours.Gray3;
}
protected override MarginPadding ItemFlowContainerPadding => new MarginPadding();
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableUserDropdownMenuItem(item);
private class DrawableUserDropdownMenuItem : DrawableOsuDropdownMenuItem
{
public DrawableUserDropdownMenuItem(MenuItem item)
: base(item)
{
Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 5 };
CornerRadius = 5;
}
protected override Drawable CreateContent() => new Content
{
Label = { Margin = new MarginPadding { Left = UserDropdownHeader.LABEL_LEFT_MARGIN - 11 } }
};
}
}
private class UserDropdownHeader : OsuDropdownHeader private class UserDropdownHeader : OsuDropdownHeader
{ {
public const float LABEL_LEFT_MARGIN = 20; public const float LABEL_LEFT_MARGIN = 20;
@ -324,38 +367,9 @@ namespace osu.Game.Overlays.Settings.Sections.General
} }
} }
private class UserDropdownMenu : OsuMenu
{
public UserDropdownMenu()
{
Margin = new MarginPadding { Bottom = 5 };
CornerRadius = 5;
ItemsContainer.Padding = new MarginPadding(0);
Masking = true;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.25f),
Radius = 4,
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Background.Colour = colours.Gray3;
}
}
private class UserDropdownMenuItem : OsuDropdownMenuItem
{
public UserDropdownMenuItem(string text, UserAction current) : base(text, current)
{
Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 5 };
Label.Margin = new MarginPadding { Left = UserDropdownHeader.LABEL_LEFT_MARGIN - 11 };
CornerRadius = 5;
}
}
} }
private enum UserAction private enum UserAction

View File

@ -7,14 +7,15 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
namespace osu.Game.Overlays.Settings namespace osu.Game.Overlays.Settings
{ {
public abstract class SettingsSubsection : FillFlowContainer, IHasFilterableChildren public abstract class SettingsSubsection : FillFlowContainer, IHasFilterableChildren
{ {
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => FlowContent;
private readonly Container<Drawable> content; protected readonly FillFlowContainer FlowContent;
protected abstract string Header { get; } protected abstract string Header { get; }
@ -33,6 +34,19 @@ namespace osu.Game.Overlays.Settings
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
Direction = FillDirection.Vertical; Direction = FillDirection.Vertical;
FlowContent = new FillFlowContainer
{
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
};
}
[BackgroundDependencyLoader]
private void load()
{
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
new OsuSpriteText new OsuSpriteText
@ -41,13 +55,7 @@ namespace osu.Game.Overlays.Settings
Margin = new MarginPadding { Bottom = 10 }, Margin = new MarginPadding { Bottom = 10 },
Font = @"Exo2.0-Black", Font = @"Exo2.0-Black",
}, },
content = new FillFlowContainer FlowContent
{
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
},
}); });
} }
} }

View File

@ -58,6 +58,7 @@ namespace osu.Game.Overlays.Toolbar
AutoSizeAxes = Axes.X, AutoSizeAxes = Axes.X,
Children = new Drawable[] Children = new Drawable[]
{ {
new ToolbarDirectButton(),
new ToolbarChatButton(), new ToolbarChatButton(),
new ToolbarSocialButton(), new ToolbarSocialButton(),
new ToolbarMusicButton(), new ToolbarMusicButton(),

View File

@ -0,0 +1,22 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Game.Graphics;
namespace osu.Game.Overlays.Toolbar
{
internal class ToolbarDirectButton : ToolbarOverlayToggleButton
{
public ToolbarDirectButton()
{
SetIcon(FontAwesome.fa_osu_chevron_down_o);
}
[BackgroundDependencyLoader]
private void load(DirectOverlay direct)
{
StateContainer = direct;
}
}
}

View File

@ -26,21 +26,19 @@ namespace osu.Game.Rulesets.Beatmaps
/// Converts a Beatmap using this Beatmap Converter. /// Converts a Beatmap using this Beatmap Converter.
/// </summary> /// </summary>
/// <param name="original">The un-converted Beatmap.</param> /// <param name="original">The un-converted Beatmap.</param>
/// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
/// <returns>The converted Beatmap.</returns> /// <returns>The converted Beatmap.</returns>
public Beatmap<T> Convert(Beatmap original, bool isForCurrentRuleset) public Beatmap<T> Convert(Beatmap original)
{ {
// We always operate on a clone of the original beatmap, to not modify it game-wide // We always operate on a clone of the original beatmap, to not modify it game-wide
return ConvertBeatmap(new Beatmap(original), isForCurrentRuleset); return ConvertBeatmap(new Beatmap(original));
} }
/// <summary> /// <summary>
/// Performs the conversion of a Beatmap using this Beatmap Converter. /// Performs the conversion of a Beatmap using this Beatmap Converter.
/// </summary> /// </summary>
/// <param name="original">The un-converted Beatmap.</param> /// <param name="original">The un-converted Beatmap.</param>
/// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
/// <returns>The converted Beatmap.</returns> /// <returns>The converted Beatmap.</returns>
protected virtual Beatmap<T> ConvertBeatmap(Beatmap original, bool isForCurrentRuleset) protected virtual Beatmap<T> ConvertBeatmap(Beatmap original)
{ {
return new Beatmap<T> return new Beatmap<T>
{ {

View File

@ -3,7 +3,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Input.Handlers; using osu.Game.Input.Handlers;
@ -18,7 +17,7 @@ namespace osu.Game.Rulesets.Replays
/// The ReplayHandler will take a replay and handle the propagation of updates to the input stack. /// The ReplayHandler will take a replay and handle the propagation of updates to the input stack.
/// It handles logic of any frames which *must* be executed. /// It handles logic of any frames which *must* be executed.
/// </summary> /// </summary>
public class FramedReplayInputHandler : ReplayInputHandler public abstract class FramedReplayInputHandler : ReplayInputHandler
{ {
private readonly Replay replay; private readonly Replay replay;
@ -31,7 +30,7 @@ namespace osu.Game.Rulesets.Replays
private int nextFrameIndex => MathHelper.Clamp(currentFrameIndex + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1); private int nextFrameIndex => MathHelper.Clamp(currentFrameIndex + (currentDirection > 0 ? 1 : -1), 0, Frames.Count - 1);
public FramedReplayInputHandler(Replay replay) protected FramedReplayInputHandler(Replay replay)
{ {
this.replay = replay; this.replay = replay;
} }
@ -51,7 +50,7 @@ namespace osu.Game.Rulesets.Replays
{ {
} }
private Vector2? position protected Vector2? Position
{ {
get get
{ {
@ -62,23 +61,7 @@ namespace osu.Game.Rulesets.Replays
} }
} }
public override List<InputState> GetPendingStates() public override List<InputState> GetPendingStates() => new List<InputState>();
{
var buttons = new HashSet<MouseButton>();
if (CurrentFrame?.MouseLeft ?? false)
buttons.Add(MouseButton.Left);
if (CurrentFrame?.MouseRight ?? false)
buttons.Add(MouseButton.Right);
return new List<InputState>
{
new InputState
{
Mouse = new ReplayMouseState(ToScreenSpace(position ?? Vector2.Zero), buttons),
Keyboard = new ReplayKeyboardState(new List<Key>())
}
};
}
public bool AtLastFrame => currentFrameIndex == Frames.Count - 1; public bool AtLastFrame => currentFrameIndex == Frames.Count - 1;
public bool AtFirstFrame => currentFrameIndex == 0; public bool AtFirstFrame => currentFrameIndex == 0;
@ -133,10 +116,9 @@ namespace osu.Game.Rulesets.Replays
protected class ReplayMouseState : MouseState protected class ReplayMouseState : MouseState
{ {
public ReplayMouseState(Vector2 position, IEnumerable<MouseButton> list) public ReplayMouseState(Vector2 position)
{ {
Position = position; Position = position;
list.ForEach(b => SetPressed(b, true));
} }
} }

View File

@ -63,5 +63,12 @@ namespace osu.Game.Rulesets
/// <param name="variant">A variant.</param> /// <param name="variant">A variant.</param>
/// <returns>A list of valid <see cref="KeyBinding"/>s.</returns> /// <returns>A list of valid <see cref="KeyBinding"/>s.</returns>
public virtual IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new KeyBinding[] { }; public virtual IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new KeyBinding[] { };
/// <summary>
/// Gets the name for a key binding variant. This is used for display in the settings overlay.
/// </summary>
/// <param name="variant">The variant.</param>
/// <returns>A descriptive name of the variant.</returns>
public virtual string GetVariantName(int variant) => string.Empty;
} }
} }

View File

@ -137,7 +137,7 @@ namespace osu.Game.Rulesets.Scoring
frames.Add(new ReplayFrame( frames.Add(new ReplayFrame(
lastTime, lastTime,
float.Parse(split[1]), float.Parse(split[1]),
384 - float.Parse(split[2]), float.Parse(split[2]),
(ReplayButtonState)int.Parse(split[3]) (ReplayButtonState)int.Parse(split[3])
)); ));
} }

View File

@ -10,12 +10,10 @@ namespace osu.Game.Rulesets.Timing
/// </summary> /// </summary>
internal class LinearScrollingContainer : ScrollingContainer internal class LinearScrollingContainer : ScrollingContainer
{ {
private readonly Axes scrollingAxes;
private readonly MultiplierControlPoint controlPoint; private readonly MultiplierControlPoint controlPoint;
public LinearScrollingContainer(Axes scrollingAxes, MultiplierControlPoint controlPoint) public LinearScrollingContainer(MultiplierControlPoint controlPoint)
{ {
this.scrollingAxes = scrollingAxes;
this.controlPoint = controlPoint; this.controlPoint = controlPoint;
} }
@ -23,8 +21,8 @@ namespace osu.Game.Rulesets.Timing
{ {
base.Update(); base.Update();
if ((scrollingAxes & Axes.X) > 0) X = (float)(controlPoint.StartTime - Time.Current); if ((ScrollingAxes & Axes.X) > 0) X = (float)(controlPoint.StartTime - Time.Current);
if ((scrollingAxes & Axes.Y) > 0) Y = (float)(controlPoint.StartTime - Time.Current); if ((ScrollingAxes & Axes.Y) > 0) Y = (float)(controlPoint.StartTime - Time.Current);
} }
} }
} }

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 System;
using System.Linq; using System.Linq;
using osu.Framework.Caching; using osu.Framework.Caching;
using osu.Framework.Configuration; using osu.Framework.Configuration;
@ -45,50 +46,34 @@ namespace osu.Game.Rulesets.Timing
RelativePositionAxes = Axes.Both; RelativePositionAxes = Axes.Both;
} }
public override void InvalidateFromChild(Invalidation invalidation) protected override int Compare(Drawable x, Drawable y)
{ {
// We only want to re-compute our size when a child's size or position has changed var hX = (DrawableHitObject)x;
if ((invalidation & Invalidation.RequiredParentSizeToFit) == 0) var hY = (DrawableHitObject)y;
{
base.InvalidateFromChild(invalidation); int result = hY.HitObject.StartTime.CompareTo(hX.HitObject.StartTime);
return; if (result != 0)
return result;
return base.Compare(y, x);
} }
public override void Add(DrawableHitObject drawable)
{
durationBacking.Invalidate(); durationBacking.Invalidate();
base.Add(drawable);
base.InvalidateFromChild(invalidation);
} }
private double computeDuration() public override bool Remove(DrawableHitObject drawable)
{ {
if (!Children.Any()) durationBacking.Invalidate();
return 0; return base.Remove(drawable);
double baseDuration = Children.Max(c => (c.HitObject as IHasEndTime)?.EndTime ?? c.HitObject.StartTime) - ControlPoint.StartTime;
// If we have a singular hit object at the timing section's start time, let's set a sane default duration
if (baseDuration == 0)
baseDuration = 1;
// This container needs to resize such that it completely encloses the hit objects to avoid masking optimisations. This is done by converting the largest
// absolutely-sized element along the scrolling axes and adding a corresponding duration value. This introduces a bit of error, but will never under-estimate.ion.
// Find the largest element that is absolutely-sized along ScrollingAxes
float maxAbsoluteSize = Children.Where(c => (c.RelativeSizeAxes & ScrollingAxes) == 0)
.Select(c => (ScrollingAxes & Axes.X) > 0 ? c.Width : c.Height)
.DefaultIfEmpty().Max();
float ourAbsoluteSize = (ScrollingAxes & Axes.X) > 0 ? DrawWidth : DrawHeight;
// Add the extra duration to account for the absolute size
baseDuration *= 1 + maxAbsoluteSize / ourAbsoluteSize;
return baseDuration;
} }
// Todo: This may underestimate the size of the hit object in some cases, but won't be too much of a problem for now
private double computeDuration() => Math.Max(0, Children.Select(c => (c.HitObject as IHasEndTime)?.EndTime ?? c.HitObject.StartTime).DefaultIfEmpty().Max() - ControlPoint.StartTime) + 1000;
/// <summary> /// <summary>
/// The maximum duration of any one hit object inside this <see cref="ScrollingContainer"/>. This is calculated as the maximum /// An approximate total duration of this scrolling container.
/// duration of all hit objects relative to this <see cref="ScrollingContainer"/>'s <see cref="MultiplierControlPoint.StartTime"/>.
/// </summary> /// </summary>
public double Duration => durationBacking.IsValid ? durationBacking : (durationBacking.Value = computeDuration()); public double Duration => durationBacking.IsValid ? durationBacking : (durationBacking.Value = computeDuration());
@ -96,6 +81,8 @@ namespace osu.Game.Rulesets.Timing
{ {
base.Update(); base.Update();
RelativeChildOffset = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)ControlPoint.StartTime : 0, (ScrollingAxes & Axes.Y) > 0 ? (float)ControlPoint.StartTime : 0);
// We want our size and position-space along the scrolling axes to span our duration to completely enclose all the hit objects // We want our size and position-space along the scrolling axes to span our duration to completely enclose all the hit objects
Size = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)Duration : Size.X, (ScrollingAxes & Axes.Y) > 0 ? (float)Duration : Size.Y); Size = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)Duration : Size.X, (ScrollingAxes & Axes.Y) > 0 ? (float)Duration : Size.Y);
// And we need to make sure the hit object's position-space doesn't change due to our resizing // And we need to make sure the hit object's position-space doesn't change due to our resizing

View File

@ -31,7 +31,11 @@ namespace osu.Game.Rulesets.Timing
/// <summary> /// <summary>
/// The axes which the content of this container will scroll through. /// The axes which the content of this container will scroll through.
/// </summary> /// </summary>
public Axes ScrollingAxes { get; internal set; } public Axes ScrollingAxes
{
get { return scrollingContainer.ScrollingAxes; }
set { scrollingContainer.ScrollingAxes = value; }
}
public override bool RemoveWhenNotAlive => false; public override bool RemoveWhenNotAlive => false;
@ -52,11 +56,8 @@ namespace osu.Game.Rulesets.Timing
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
scrollingContainer = CreateScrollingContainer(); scrollingContainer = CreateScrollingContainer();
scrollingContainer.ScrollingAxes = ScrollingAxes;
scrollingContainer.ControlPoint = ControlPoint; scrollingContainer.ControlPoint = ControlPoint;
scrollingContainer.VisibleTimeRange.BindTo(VisibleTimeRange); scrollingContainer.VisibleTimeRange.BindTo(VisibleTimeRange);
scrollingContainer.RelativeChildOffset = new Vector2((ScrollingAxes & Axes.X) > 0 ? (float)ControlPoint.StartTime : 0, (ScrollingAxes & Axes.Y) > 0 ? (float)ControlPoint.StartTime : 0);
AddInternal(content = scrollingContainer); AddInternal(content = scrollingContainer);
} }
@ -98,11 +99,6 @@ namespace osu.Game.Rulesets.Timing
base.Add(drawable); base.Add(drawable);
} }
/// <summary>
/// Whether a <see cref="DrawableHitObject"/> falls within this <see cref="SpeedAdjustmentContainer"/>s affecting timespan.
/// </summary>
public bool CanContain(DrawableHitObject hitObject) => CanContain(hitObject.HitObject.StartTime);
/// <summary> /// <summary>
/// Whether a point in time falls within this <see cref="SpeedAdjustmentContainer"/>s affecting timespan. /// Whether a point in time falls within this <see cref="SpeedAdjustmentContainer"/>s affecting timespan.
/// </summary> /// </summary>
@ -112,6 +108,6 @@ namespace osu.Game.Rulesets.Timing
/// Creates the <see cref="ScrollingContainer"/> which contains the scrolling <see cref="DrawableHitObject"/>s of this container. /// Creates the <see cref="ScrollingContainer"/> which contains the scrolling <see cref="DrawableHitObject"/>s of this container.
/// </summary> /// </summary>
/// <returns>The <see cref="ScrollingContainer"/>.</returns> /// <returns>The <see cref="ScrollingContainer"/>.</returns>
protected virtual ScrollingContainer CreateScrollingContainer() => new LinearScrollingContainer(ScrollingAxes, ControlPoint); protected virtual ScrollingContainer CreateScrollingContainer() => new LinearScrollingContainer(ControlPoint);
} }
} }

View File

@ -9,7 +9,6 @@ using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Screens.Play;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
@ -43,12 +42,12 @@ namespace osu.Game.Rulesets.UI
/// <summary> /// <summary>
/// The input manager for this RulesetContainer. /// The input manager for this RulesetContainer.
/// </summary> /// </summary>
internal readonly PlayerInputManager InputManager = new PlayerInputManager(); internal IHasReplayHandler ReplayInputManager => KeyBindingInputManager as IHasReplayHandler;
/// <summary> /// <summary>
/// The key conversion input manager for this RulesetContainer. /// The key conversion input manager for this RulesetContainer.
/// </summary> /// </summary>
public readonly PassThroughInputManager KeyBindingInputManager; public PassThroughInputManager KeyBindingInputManager;
/// <summary> /// <summary>
/// Whether we are currently providing the local user a gameplay cursor. /// Whether we are currently providing the local user a gameplay cursor.
@ -58,7 +57,7 @@ namespace osu.Game.Rulesets.UI
/// <summary> /// <summary>
/// Whether we have a replay loaded currently. /// Whether we have a replay loaded currently.
/// </summary> /// </summary>
public bool HasReplayLoaded => InputManager.ReplayInputHandler != null; public bool HasReplayLoaded => ReplayInputManager?.ReplayInputHandler != null;
public abstract IEnumerable<HitObject> Objects { get; } public abstract IEnumerable<HitObject> Objects { get; }
@ -76,6 +75,7 @@ namespace osu.Game.Rulesets.UI
internal RulesetContainer(Ruleset ruleset) internal RulesetContainer(Ruleset ruleset)
{ {
Ruleset = ruleset; Ruleset = ruleset;
KeyBindingInputManager = CreateInputManager(); KeyBindingInputManager = CreateInputManager();
KeyBindingInputManager.RelativeSizeAxes = Axes.Both; KeyBindingInputManager.RelativeSizeAxes = Axes.Both;
} }
@ -97,7 +97,7 @@ namespace osu.Game.Rulesets.UI
/// <returns>The input manager.</returns> /// <returns>The input manager.</returns>
public abstract PassThroughInputManager CreateInputManager(); public abstract PassThroughInputManager CreateInputManager();
protected virtual FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new FramedReplayInputHandler(replay); protected virtual FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => null;
public Replay Replay { get; private set; } public Replay Replay { get; private set; }
@ -105,10 +105,13 @@ namespace osu.Game.Rulesets.UI
/// Sets a replay to be used, overriding local input. /// Sets a replay to be used, overriding local input.
/// </summary> /// </summary>
/// <param name="replay">The replay, null for local input.</param> /// <param name="replay">The replay, null for local input.</param>
public void SetReplay(Replay replay) public virtual void SetReplay(Replay replay)
{ {
if (ReplayInputManager == null)
throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available");
Replay = replay; Replay = replay;
InputManager.ReplayInputHandler = replay != null ? CreateReplayInputHandler(replay) : null; ReplayInputManager.ReplayInputHandler = replay != null ? CreateReplayInputHandler(replay) : null;
} }
} }
@ -139,16 +142,30 @@ namespace osu.Game.Rulesets.UI
protected IEnumerable<Mod> Mods; protected IEnumerable<Mod> Mods;
/// <summary> /// <summary>
/// The <see cref="WorkingBeatmap"/> this <see cref="RulesetContainer{TObject}"/> was created with.
/// </summary>
protected readonly WorkingBeatmap WorkingBeatmap;
/// <summary>
/// Whether the specified beatmap is assumed to be specific to the current ruleset.
/// </summary>
protected readonly bool IsForCurrentRuleset;
/// <summary>
/// Whether to assume the beatmap passed into this <see cref="RulesetContainer{TObject}"/> is for the current ruleset.
/// Creates a hit renderer for a beatmap. /// Creates a hit renderer for a beatmap.
/// </summary> /// </summary>
/// <param name="ruleset">The ruleset being repesented.</param> /// <param name="ruleset">The ruleset being repesented.</param>
/// <param name="beatmap">The beatmap to create the hit renderer for.</param> /// <param name="workingBeatmap">The beatmap to create the hit renderer for.</param>
/// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param> /// <param name="isForCurrentRuleset">Whether to assume the beatmap is for the current ruleset.</param>
internal RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) : base(ruleset) internal RulesetContainer(Ruleset ruleset, WorkingBeatmap workingBeatmap, bool isForCurrentRuleset)
: base(ruleset)
{ {
Debug.Assert(beatmap != null, "RulesetContainer initialized with a null beatmap."); Debug.Assert(workingBeatmap != null, "RulesetContainer initialized with a null beatmap.");
Mods = beatmap.Mods.Value; WorkingBeatmap = workingBeatmap;
IsForCurrentRuleset = isForCurrentRuleset;
Mods = workingBeatmap.Mods.Value;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -156,11 +173,11 @@ namespace osu.Game.Rulesets.UI
BeatmapProcessor<TObject> processor = CreateBeatmapProcessor(); BeatmapProcessor<TObject> processor = CreateBeatmapProcessor();
// Check if the beatmap can be converted // Check if the beatmap can be converted
if (!converter.CanConvert(beatmap.Beatmap)) if (!converter.CanConvert(workingBeatmap.Beatmap))
throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter})."); throw new BeatmapInvalidForRulesetException($"{nameof(Beatmap)} can not be converted for the current ruleset (converter: {converter}).");
// Convert the beatmap // Convert the beatmap
Beatmap = converter.Convert(beatmap.Beatmap, isForCurrentRuleset); Beatmap = converter.Convert(workingBeatmap.Beatmap);
// Apply difficulty adjustments from mods before using Difficulty. // Apply difficulty adjustments from mods before using Difficulty.
foreach (var mod in Mods.OfType<IApplicableToDifficulty>()) foreach (var mod in Mods.OfType<IApplicableToDifficulty>())
@ -173,8 +190,6 @@ namespace osu.Game.Rulesets.UI
// Post-process the beatmap // Post-process the beatmap
processor.PostProcess(Beatmap); processor.PostProcess(Beatmap);
ApplyBeatmap();
// Add mods, should always be the last thing applied to give full control to mods // Add mods, should always be the last thing applied to give full control to mods
applyMods(Mods); applyMods(Mods);
} }
@ -192,11 +207,6 @@ namespace osu.Game.Rulesets.UI
mod.ApplyToRulesetContainer(this); mod.ApplyToRulesetContainer(this);
} }
/// <summary>
/// Called when the beatmap of this hit renderer has been set. Used to apply any default values from the beatmap.
/// </summary>
protected virtual void ApplyBeatmap() { }
/// <summary> /// <summary>
/// Creates a processor to perform post-processing operations /// Creates a processor to perform post-processing operations
/// on HitObjects in converted Beatmaps. /// on HitObjects in converted Beatmaps.
@ -237,7 +247,7 @@ namespace osu.Game.Rulesets.UI
public Playfield<TObject, TJudgement> Playfield { get; private set; } public Playfield<TObject, TJudgement> Playfield { get; private set; }
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private readonly Container content; private Container content;
private readonly List<DrawableHitObject<TObject, TJudgement>> drawableObjects = new List<DrawableHitObject<TObject, TJudgement>>(); private readonly List<DrawableHitObject<TObject, TJudgement>> drawableObjects = new List<DrawableHitObject<TObject, TJudgement>>();
@ -250,24 +260,28 @@ namespace osu.Game.Rulesets.UI
protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset) protected RulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(ruleset, beatmap, isForCurrentRuleset) : base(ruleset, beatmap, isForCurrentRuleset)
{ {
InputManager.Add(content = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new[] { KeyBindingInputManager }
});
AddInternal(InputManager);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
KeyBindingInputManager.Add(content = new Container
{
RelativeSizeAxes = Axes.Both,
});
AddInternal(KeyBindingInputManager);
KeyBindingInputManager.Add(Playfield = CreatePlayfield()); KeyBindingInputManager.Add(Playfield = CreatePlayfield());
loadObjects(); loadObjects();
}
if (InputManager?.ReplayInputHandler != null) public override void SetReplay(Replay replay)
InputManager.ReplayInputHandler.ToScreenSpace = Playfield.ScaledContent.ToScreenSpace; {
base.SetReplay(replay);
if (ReplayInputManager?.ReplayInputHandler != null)
ReplayInputManager.ReplayInputHandler.ToScreenSpace = input => Playfield.ScaledContent.ToScreenSpace(input);
} }
/// <summary> /// <summary>

View File

@ -1,20 +1,194 @@
// 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 System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Input;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Timing;
using osu.Game.Configuration;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Input.Handlers;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using OpenTK.Input;
namespace osu.Game.Rulesets.UI namespace osu.Game.Rulesets.UI
{ {
public abstract class RulesetInputManager<T> : DatabasedKeyBindingInputManager<T>, ICanAttachKeyCounter public abstract class RulesetInputManager<T> : DatabasedKeyBindingInputManager<T>, ICanAttachKeyCounter, IHasReplayHandler
where T : struct where T : struct
{ {
protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique) protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique)
{ {
} }
#region Action mapping (for replays)
private List<T> lastPressedActions = new List<T>();
protected override void HandleNewState(InputState state)
{
base.HandleNewState(state);
var replayState = state as ReplayInputHandler.ReplayState<T>;
if (replayState == null) return;
// Here we handle states specifically coming from a replay source.
// These have extra action information rather than keyboard keys or mouse buttons.
List<T> newActions = replayState.PressedActions;
foreach (var released in lastPressedActions.Except(newActions))
PropagateReleased(KeyBindingInputQueue, released);
foreach (var pressed in newActions.Except(lastPressedActions))
PropagatePressed(KeyBindingInputQueue, pressed);
lastPressedActions = newActions;
}
#endregion
#region IHasReplayHandler
private ReplayInputHandler replayInputHandler;
public ReplayInputHandler ReplayInputHandler
{
get
{
return replayInputHandler;
}
set
{
if (replayInputHandler != null) RemoveHandler(replayInputHandler);
replayInputHandler = value;
UseParentState = replayInputHandler == null;
if (replayInputHandler != null)
AddHandler(replayInputHandler);
}
}
#endregion
#region Clock control
private ManualClock clock;
private IFrameBasedClock parentClock;
protected override void LoadComplete()
{
base.LoadComplete();
//our clock will now be our parent's clock, but we want to replace this to allow manual control.
parentClock = Clock;
Clock = new FramedClock(clock = new ManualClock
{
CurrentTime = parentClock.CurrentTime,
Rate = parentClock.Rate,
});
}
/// <summary>
/// Whether we are running up-to-date with our parent clock.
/// If not, we will need to keep processing children until we catch up.
/// </summary>
private bool requireMoreUpdateLoops;
/// <summary>
/// Whether we are in a valid state (ie. should we keep processing children frames).
/// This should be set to false when the replay is, for instance, waiting for future frames to arrive.
/// </summary>
private bool validState;
protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState;
private bool isAttached => replayInputHandler != null && !UseParentState;
private const int max_catch_up_updates_per_frame = 50;
public override bool UpdateSubTree()
{
requireMoreUpdateLoops = true;
validState = true;
int loops = 0;
while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame)
if (!base.UpdateSubTree())
return false;
return true;
}
protected override void Update()
{
if (parentClock == null) return;
clock.Rate = parentClock.Rate;
clock.IsRunning = parentClock.IsRunning;
if (!isAttached)
{
clock.CurrentTime = parentClock.CurrentTime;
}
else
{
double? newTime = replayInputHandler.SetFrameFromTime(parentClock.CurrentTime);
if (newTime == null)
{
// we shouldn't execute for this time value. probably waiting on more replay data.
validState = false;
return;
}
clock.CurrentTime = newTime.Value;
}
requireMoreUpdateLoops = clock.CurrentTime != parentClock.CurrentTime;
base.Update();
}
#endregion
#region Setting application (disables etc.)
private Bindable<bool> mouseDisabled;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
}
protected override void TransformState(InputState state)
{
base.TransformState(state);
// we don't want to transform the state if a replay is present (for now, at least).
if (replayInputHandler != null) return;
var mouse = state.Mouse as Framework.Input.MouseState;
if (mouse != null)
{
if (mouseDisabled.Value)
{
mouse.SetPressed(MouseButton.Left, false);
mouse.SetPressed(MouseButton.Right, false);
}
}
}
#endregion
#region Key Counter Attachment
public void Attach(KeyCounterCollection keyCounter) public void Attach(KeyCounterCollection keyCounter)
{ {
var receptor = new ActionReceptor(keyCounter); var receptor = new ActionReceptor(keyCounter);
@ -35,8 +209,22 @@ namespace osu.Game.Rulesets.UI
public bool OnReleased(T action) => Target.Children.OfType<KeyCounterAction<T>>().Any(c => c.OnReleased(action)); public bool OnReleased(T action) => Target.Children.OfType<KeyCounterAction<T>>().Any(c => c.OnReleased(action));
} }
#endregion
} }
/// <summary>
/// Expose the <see cref="ReplayInputHandler"/> in a capable <see cref="InputManager"/>.
/// </summary>
public interface IHasReplayHandler
{
ReplayInputHandler ReplayInputHandler { get; set; }
}
/// <summary>
/// Supports attaching a <see cref="KeyCounterCollection"/>.
/// Keys will be populated automatically and a receptor will be injected inside.
/// </summary>
public interface ICanAttachKeyCounter public interface ICanAttachKeyCounter
{ {
void Attach(KeyCounterCollection keyCounter); void Attach(KeyCounterCollection keyCounter);

View File

@ -55,9 +55,9 @@ namespace osu.Game.Rulesets.UI
}; };
/// <summary> /// <summary>
/// Whether to reverse the scrolling direction is reversed. /// Whether to reverse the scrolling direction is reversed. Note that this does _not_ invert the hit objects.
/// </summary> /// </summary>
public readonly BindableBool Reversed = new BindableBool(); protected readonly BindableBool Reversed = new BindableBool();
/// <summary> /// <summary>
/// The container that contains the <see cref="SpeedAdjustmentContainer"/>s and <see cref="DrawableHitObject"/>s. /// The container that contains the <see cref="SpeedAdjustmentContainer"/>s and <see cref="DrawableHitObject"/>s.
@ -151,7 +151,7 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
public readonly BindableBool Reversed = new BindableBool(); public readonly BindableBool Reversed = new BindableBool();
private readonly Container<SpeedAdjustmentContainer> speedAdjustments; private readonly SortedContainer speedAdjustments;
public IReadOnlyList<SpeedAdjustmentContainer> SpeedAdjustments => speedAdjustments; public IReadOnlyList<SpeedAdjustmentContainer> SpeedAdjustments => speedAdjustments;
private readonly SpeedAdjustmentContainer defaultSpeedAdjustment; private readonly SpeedAdjustmentContainer defaultSpeedAdjustment;
@ -166,14 +166,15 @@ namespace osu.Game.Rulesets.UI
{ {
this.scrollingAxes = scrollingAxes; this.scrollingAxes = scrollingAxes;
AddInternal(speedAdjustments = new Container<SpeedAdjustmentContainer> { RelativeSizeAxes = Axes.Both }); AddInternal(speedAdjustments = new SortedContainer { RelativeSizeAxes = Axes.Both });
// Default speed adjustment // Default speed adjustment
AddSpeedAdjustment(defaultSpeedAdjustment = new SpeedAdjustmentContainer(new MultiplierControlPoint(0))); AddSpeedAdjustment(defaultSpeedAdjustment = new SpeedAdjustmentContainer(new MultiplierControlPoint(0)));
} }
/// <summary> /// <summary>
/// Adds a <see cref="SpeedAdjustmentContainer"/> to this container. /// Adds a <see cref="SpeedAdjustmentContainer"/> to this container, re-sorting all hit objects
/// in the last <see cref="SpeedAdjustmentContainer"/> that occurred (time-wise) before it.
/// </summary> /// </summary>
/// <param name="speedAdjustment">The <see cref="SpeedAdjustmentContainer"/>.</param> /// <param name="speedAdjustment">The <see cref="SpeedAdjustmentContainer"/>.</param>
public void AddSpeedAdjustment(SpeedAdjustmentContainer speedAdjustment) public void AddSpeedAdjustment(SpeedAdjustmentContainer speedAdjustment)
@ -181,28 +182,29 @@ namespace osu.Game.Rulesets.UI
speedAdjustment.ScrollingAxes = scrollingAxes; speedAdjustment.ScrollingAxes = scrollingAxes;
speedAdjustment.VisibleTimeRange.BindTo(VisibleTimeRange); speedAdjustment.VisibleTimeRange.BindTo(VisibleTimeRange);
speedAdjustment.Reversed.BindTo(Reversed); speedAdjustment.Reversed.BindTo(Reversed);
speedAdjustments.Add(speedAdjustment);
// We now need to re-sort the hit objects in the last speed adjustment prior to this one, to see if they need a new parent if (speedAdjustments.Count > 0)
var previousSpeedAdjustment = speedAdjustments.LastOrDefault(s => s != speedAdjustment && s.ControlPoint.StartTime <= speedAdjustment.ControlPoint.StartTime);
if (previousSpeedAdjustment == null)
return;
for (int i = 0; i < previousSpeedAdjustment.Children.Count; i++)
{ {
DrawableHitObject hitObject = previousSpeedAdjustment[i]; // We need to re-sort all hit objects in the speed adjustment container prior to figure out if they
// should now lie within this one
var existingAdjustment = adjustmentContainerAt(speedAdjustment.ControlPoint.StartTime);
for (int i = 0; i < existingAdjustment.Count; i++)
{
DrawableHitObject hitObject = existingAdjustment[i];
var newSpeedAdjustment = adjustmentContainerFor(hitObject); if (!speedAdjustment.CanContain(hitObject.HitObject.StartTime))
if (newSpeedAdjustment == previousSpeedAdjustment)
continue; continue;
previousSpeedAdjustment.Remove(hitObject); existingAdjustment.Remove(hitObject);
newSpeedAdjustment.Add(hitObject); speedAdjustment.Add(hitObject);
i--; i--;
} }
} }
speedAdjustments.Add(speedAdjustment);
}
/// <summary> /// <summary>
/// Removes a <see cref="SpeedAdjustmentContainer"/> from this container, re-sorting all hit objects /// Removes a <see cref="SpeedAdjustmentContainer"/> from this container, re-sorting all hit objects
/// which it contained into new <see cref="SpeedAdjustmentContainer"/>s. /// which it contained into new <see cref="SpeedAdjustmentContainer"/>s.
@ -237,27 +239,32 @@ namespace osu.Game.Rulesets.UI
if (!(hitObject is IScrollingHitObject)) if (!(hitObject is IScrollingHitObject))
throw new InvalidOperationException($"Hit objects added to a {nameof(ScrollingHitObjectContainer)} must implement {nameof(IScrollingHitObject)}."); throw new InvalidOperationException($"Hit objects added to a {nameof(ScrollingHitObjectContainer)} must implement {nameof(IScrollingHitObject)}.");
adjustmentContainerFor(hitObject).Add(hitObject); adjustmentContainerAt(hitObject.HitObject.StartTime).Add(hitObject);
} }
public override bool Remove(DrawableHitObject hitObject) => speedAdjustments.Any(s => s.Remove(hitObject)); public override bool Remove(DrawableHitObject hitObject) => speedAdjustments.Any(s => s.Remove(hitObject));
/// <summary>
/// Finds the <see cref="SpeedAdjustmentContainer"/> which provides the speed adjustment active at the start time
/// of a hit object. If there is no <see cref="SpeedAdjustmentContainer"/> active at the start time of the hit object,
/// then the first (time-wise) speed adjustment is returned.
/// </summary>
/// <param name="hitObject">The hit object to find the active <see cref="SpeedAdjustmentContainer"/> for.</param>
/// <returns>The <see cref="SpeedAdjustmentContainer"/> active at <paramref name="hitObject"/>'s start time. Null if there are no speed adjustments.</returns>
private SpeedAdjustmentContainer adjustmentContainerFor(DrawableHitObject hitObject) => speedAdjustments.LastOrDefault(c => c.CanContain(hitObject)) ?? defaultSpeedAdjustment;
/// <summary> /// <summary>
/// Finds the <see cref="SpeedAdjustmentContainer"/> which provides the speed adjustment active at a time. /// Finds the <see cref="SpeedAdjustmentContainer"/> which provides the speed adjustment active at a time.
/// If there is no <see cref="SpeedAdjustmentContainer"/> active at the time, then the first (time-wise) speed adjustment is returned. /// If there is no <see cref="SpeedAdjustmentContainer"/> active at the time, then the first (time-wise) speed adjustment is returned.
/// </summary> /// </summary>
/// <param name="time">The time to find the active <see cref="SpeedAdjustmentContainer"/> at.</param> /// <param name="time">The time to find the active <see cref="SpeedAdjustmentContainer"/> at.</param>
/// <returns>The <see cref="SpeedAdjustmentContainer"/> active at <paramref name="time"/>. Null if there are no speed adjustments.</returns> /// <returns>The <see cref="SpeedAdjustmentContainer"/> active at <paramref name="time"/>. Null if there are no speed adjustments.</returns>
private SpeedAdjustmentContainer adjustmentContainerAt(double time) => speedAdjustments.LastOrDefault(c => c.CanContain(time)) ?? defaultSpeedAdjustment; private SpeedAdjustmentContainer adjustmentContainerAt(double time) => speedAdjustments.FirstOrDefault(c => c.CanContain(time)) ?? defaultSpeedAdjustment;
private class SortedContainer : Container<SpeedAdjustmentContainer>
{
protected override int Compare(Drawable x, Drawable y)
{
var sX = (SpeedAdjustmentContainer)x;
var sY = (SpeedAdjustmentContainer)y;
int result = sY.ControlPoint.StartTime.CompareTo(sX.ControlPoint.StartTime);
if (result != 0)
return result;
return base.Compare(y, x);
}
}
} }
} }
} }

View File

@ -39,17 +39,6 @@ namespace osu.Game.Rulesets.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{
DefaultControlPoints.ForEach(c => applySpeedAdjustment(c, Playfield));
}
private void applySpeedAdjustment(MultiplierControlPoint controlPoint, ScrollingPlayfield<TObject, TJudgement> playfield)
{
playfield.HitObjects.AddSpeedAdjustment(CreateSpeedAdjustmentContainer(controlPoint));
playfield.NestedPlayfields.ForEach(p => applySpeedAdjustment(controlPoint, p));
}
protected override void ApplyBeatmap()
{ {
// Calculate default multiplier control points // Calculate default multiplier control points
var lastTimingPoint = new TimingControlPoint(); var lastTimingPoint = new TimingControlPoint();
@ -95,6 +84,14 @@ namespace osu.Game.Rulesets.UI
// If we have no control points, add a default one // If we have no control points, add a default one
if (DefaultControlPoints.Count == 0) if (DefaultControlPoints.Count == 0)
DefaultControlPoints.Add(new MultiplierControlPoint()); DefaultControlPoints.Add(new MultiplierControlPoint());
DefaultControlPoints.ForEach(c => applySpeedAdjustment(c, Playfield));
}
private void applySpeedAdjustment(MultiplierControlPoint controlPoint, ScrollingPlayfield<TObject, TJudgement> playfield)
{
playfield.HitObjects.AddSpeedAdjustment(CreateSpeedAdjustmentContainer(controlPoint));
playfield.NestedPlayfields.ForEach(p => applySpeedAdjustment(controlPoint, p));
} }
/// <summary> /// <summary>

View File

@ -1,140 +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 OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Input;
using osu.Framework.Timing;
using osu.Game.Configuration;
using osu.Game.Input.Handlers;
namespace osu.Game.Screens.Play
{
public class PlayerInputManager : PassThroughInputManager
{
private ManualClock clock;
private IFrameBasedClock parentClock;
private ReplayInputHandler replayInputHandler;
public ReplayInputHandler ReplayInputHandler
{
get
{
return replayInputHandler;
}
set
{
if (replayInputHandler != null) RemoveHandler(replayInputHandler);
replayInputHandler = value;
UseParentState = replayInputHandler == null;
if (replayInputHandler != null)
AddHandler(replayInputHandler);
}
}
private Bindable<bool> mouseDisabled;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
mouseDisabled = config.GetBindable<bool>(OsuSetting.MouseDisableButtons);
}
protected override void LoadComplete()
{
base.LoadComplete();
//our clock will now be our parent's clock, but we want to replace this to allow manual control.
parentClock = Clock;
Clock = new FramedClock(clock = new ManualClock
{
CurrentTime = parentClock.CurrentTime,
Rate = parentClock.Rate,
});
}
/// <summary>
/// Whether we running up-to-date with our parent clock.
/// If not, we will need to keep processing children until we catch up.
/// </summary>
private bool requireMoreUpdateLoops;
/// <summary>
/// Whether we in a valid state (ie. should we keep processing children frames).
/// This should be set to false when the replay is, for instance, waiting for future frames to arrive.
/// </summary>
private bool validState;
protected override bool RequiresChildrenUpdate => base.RequiresChildrenUpdate && validState;
private bool isAttached => replayInputHandler != null && !UseParentState;
private const int max_catch_up_updates_per_frame = 50;
public override bool UpdateSubTree()
{
requireMoreUpdateLoops = true;
validState = true;
int loops = 0;
while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame)
if (!base.UpdateSubTree())
return false;
return true;
}
protected override void Update()
{
if (parentClock == null) return;
clock.Rate = parentClock.Rate;
clock.IsRunning = parentClock.IsRunning;
if (!isAttached)
{
clock.CurrentTime = parentClock.CurrentTime;
}
else
{
double? newTime = replayInputHandler.SetFrameFromTime(parentClock.CurrentTime);
if (newTime == null)
{
// we shouldn't execute for this time value. probably waiting on more replay data.
validState = false;
return;
}
clock.CurrentTime = newTime.Value;
}
requireMoreUpdateLoops = clock.CurrentTime != parentClock.CurrentTime;
base.Update();
}
protected override void TransformState(InputState state)
{
base.TransformState(state);
// we don't want to transform the state if a replay is present (for now, at least).
if (replayInputHandler != null) return;
var mouse = state.Mouse as Framework.Input.MouseState;
if (mouse != null)
{
if (mouseDisabled.Value)
{
mouse.SetPressed(MouseButton.Left, false);
mouse.SetPressed(MouseButton.Right, false);
}
}
}
}
}

View File

@ -248,7 +248,6 @@ namespace osu.Game.Screens.Select
if (beatmap == null) if (beatmap == null)
{ {
if (!Beatmap.IsDefault)
performLoad(); performLoad();
} }
else else

View File

@ -85,14 +85,16 @@
<Compile Include="Graphics\Containers\OsuFocusedOverlayContainer.cs" /> <Compile Include="Graphics\Containers\OsuFocusedOverlayContainer.cs" />
<Compile Include="Graphics\Containers\OsuHoverContainer.cs" /> <Compile Include="Graphics\Containers\OsuHoverContainer.cs" />
<Compile Include="Graphics\Containers\OsuScrollContainer.cs" /> <Compile Include="Graphics\Containers\OsuScrollContainer.cs" />
<Compile Include="Graphics\Containers\SectionsContainer.cs" />
<Compile Include="Graphics\Cursor\OsuContextMenuContainer.cs" /> <Compile Include="Graphics\Cursor\OsuContextMenuContainer.cs" />
<Compile Include="Graphics\Containers\OsuTextFlowContainer.cs" /> <Compile Include="Graphics\Containers\OsuTextFlowContainer.cs" />
<Compile Include="Graphics\UserInterface\IconButton.cs" /> <Compile Include="Graphics\UserInterface\IconButton.cs" />
<Compile Include="Graphics\UserInterface\ProgressBar.cs" />
<Compile Include="Configuration\SelectionRandomType.cs" /> <Compile Include="Configuration\SelectionRandomType.cs" />
<Compile Include="Graphics\UserInterface\LineGraph.cs" /> <Compile Include="Graphics\UserInterface\LineGraph.cs" />
<Compile Include="Graphics\UserInterface\MenuItemType.cs" /> <Compile Include="Graphics\UserInterface\MenuItemType.cs" />
<Compile Include="Graphics\UserInterface\OsuContextMenu.cs" /> <Compile Include="Graphics\UserInterface\OsuContextMenu.cs" />
<Compile Include="Graphics\UserInterface\OsuContextMenuItem.cs" /> <Compile Include="Graphics\UserInterface\OsuMenuItem.cs" />
<Compile Include="Input\Bindings\DatabasedKeyBinding.cs" /> <Compile Include="Input\Bindings\DatabasedKeyBinding.cs" />
<Compile Include="Input\Bindings\DatabasedKeyBindingInputManager.cs" /> <Compile Include="Input\Bindings\DatabasedKeyBindingInputManager.cs" />
<Compile Include="Input\KeyBindingStore.cs" /> <Compile Include="Input\KeyBindingStore.cs" />
@ -106,16 +108,16 @@
<Compile Include="Overlays\Chat\ChatTabControl.cs" /> <Compile Include="Overlays\Chat\ChatTabControl.cs" />
<Compile Include="Overlays\KeyBinding\GlobalKeyBindingsSection.cs" /> <Compile Include="Overlays\KeyBinding\GlobalKeyBindingsSection.cs" />
<Compile Include="Overlays\KeyBinding\KeyBindingRow.cs" /> <Compile Include="Overlays\KeyBinding\KeyBindingRow.cs" />
<Compile Include="Overlays\KeyBinding\KeyBindingsSection.cs" /> <Compile Include="Overlays\KeyBinding\KeyBindingsSubsection.cs" />
<Compile Include="Overlays\KeyBindingOverlay.cs" /> <Compile Include="Overlays\KeyBindingOverlay.cs" />
<Compile Include="Overlays\KeyBinding\RulesetBindingsSection.cs" /> <Compile Include="Overlays\KeyBinding\RulesetBindingsSection.cs" />
<Compile Include="Overlays\KeyBinding\VariantBindingsSubsection.cs" />
<Compile Include="Overlays\MainSettings.cs" /> <Compile Include="Overlays\MainSettings.cs" />
<Compile Include="Overlays\Music\CollectionsDropdown.cs" /> <Compile Include="Overlays\Music\CollectionsDropdown.cs" />
<Compile Include="Overlays\Music\FilterControl.cs" /> <Compile Include="Overlays\Music\FilterControl.cs" />
<Compile Include="Overlays\Music\PlaylistItem.cs" /> <Compile Include="Overlays\Music\PlaylistItem.cs" />
<Compile Include="Overlays\Music\PlaylistList.cs" /> <Compile Include="Overlays\Music\PlaylistList.cs" />
<Compile Include="Overlays\OnScreenDisplay.cs" /> <Compile Include="Overlays\OnScreenDisplay.cs" />
<Compile Include="Graphics\Containers\SectionsContainer.cs" />
<Compile Include="Overlays\Profile\Sections\Ranks\DrawableScore.cs" /> <Compile Include="Overlays\Profile\Sections\Ranks\DrawableScore.cs" />
<Compile Include="Overlays\Settings\Sections\Maintenance\GeneralSettings.cs" /> <Compile Include="Overlays\Settings\Sections\Maintenance\GeneralSettings.cs" />
<Compile Include="Overlays\Settings\SettingsHeader.cs" /> <Compile Include="Overlays\Settings\SettingsHeader.cs" />
@ -130,6 +132,7 @@
<Compile Include="Overlays\Profile\Sections\RanksSection.cs" /> <Compile Include="Overlays\Profile\Sections\RanksSection.cs" />
<Compile Include="Overlays\Profile\Sections\RecentSection.cs" /> <Compile Include="Overlays\Profile\Sections\RecentSection.cs" />
<Compile Include="Graphics\Containers\ConstrainedIconContainer.cs" /> <Compile Include="Graphics\Containers\ConstrainedIconContainer.cs" />
<Compile Include="Overlays\Toolbar\ToolbarDirectButton.cs" />
<Compile Include="Rulesets\Mods\IApplicableToDifficulty.cs" /> <Compile Include="Rulesets\Mods\IApplicableToDifficulty.cs" />
<Compile Include="Rulesets\UI\RulesetInputManager.cs" /> <Compile Include="Rulesets\UI\RulesetInputManager.cs" />
<Compile Include="Screens\Play\KeyCounterAction.cs" /> <Compile Include="Screens\Play\KeyCounterAction.cs" />
@ -322,7 +325,6 @@
<Compile Include="Screens\Multiplayer\MatchCreate.cs" /> <Compile Include="Screens\Multiplayer\MatchCreate.cs" />
<Compile Include="Screens\Play\FailOverlay.cs" /> <Compile Include="Screens\Play\FailOverlay.cs" />
<Compile Include="Screens\Play\MenuOverlay.cs" /> <Compile Include="Screens\Play\MenuOverlay.cs" />
<Compile Include="Screens\Play\PlayerInputManager.cs" />
<Compile Include="Screens\Play\PlayerLoader.cs" /> <Compile Include="Screens\Play\PlayerLoader.cs" />
<Compile Include="Screens\Play\ReplayPlayer.cs" /> <Compile Include="Screens\Play\ReplayPlayer.cs" />
<Compile Include="Screens\Play\SkipButton.cs" /> <Compile Include="Screens\Play\SkipButton.cs" />