1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-31 13:33:20 +08:00

Merge branch 'master' of git://github.com/ppy/osu into caps-warning

This commit is contained in:
Jorolf 2017-08-25 16:33:29 +02:00
commit 26323caf6f
65 changed files with 1316 additions and 764 deletions

@ -1 +1 @@
Subproject commit ba70b8eaa9b79d4248873d4399f3b9e918fc3c8f Subproject commit 3db7e231653ec6ffe28b5dcd1a86230ec754cc1c

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

@ -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)
{
Clear();
var inputManager = new ManiaInputManager(maniaRuleset, cols) { RelativeSizeAxes = Axes.Both };
Add(inputManager);
ManiaPlayfield playfield;
inputManager.Add(playfield = new ManiaPlayfield(cols)
{ {
Key = column.Key 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

@ -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,15 +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.Reversed.BindTo(Reversed);
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]
@ -145,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;
} }
@ -167,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;
@ -85,7 +83,7 @@ namespace osu.Game.Rulesets.Mania.UI
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() protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter()
{ {
@ -109,15 +107,15 @@ namespace osu.Game.Rulesets.Mania.UI
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

@ -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

@ -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

@ -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,20 +22,31 @@ 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);
float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f;
// Always rotate in the direction of least distance // don't start rotating until we're moved a minimum distance away from the mouse down location,
float diff = (degrees - ActiveCursor.Rotation) % 360; // else it can have an annoying effect.
if (diff < -180) diff += 360; startRotation |= Vector2.Distance(state.Mouse.Position, state.Mouse.PositionMouseDown.Value) > 30;
if (diff > 180) diff -= 360;
degrees = ActiveCursor.Rotation + diff;
ActiveCursor.RotateTo(degrees, 600, Easing.OutQuint); if (startRotation)
{
Vector2 offset = state.Mouse.Position - state.Mouse.PositionMouseDown.Value;
float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f;
// Always rotate in the direction of least distance
float diff = (degrees - ActiveCursor.Rotation) % 360;
if (diff < -180) diff += 360;
if (diff > 180) diff -= 360;
degrees = ActiveCursor.Rotation + diff;
ActiveCursor.RotateTo(degrees, 600, 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

@ -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,
@ -120,4 +122,4 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
} }
} }

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,18 +175,38 @@ 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>
{ {
switch (displayStyle) RelativeSizeAxes = Axes.X,
{ AutoSizeAxes = Axes.Y,
case PanelDisplayStyle.Grid: Spacing = new Vector2(panel_padding),
return new DirectGridPanel(b) { Width = 400 }; Margin = new MarginPadding { Top = 10 },
default: ChildrenEnumerable = BeatmapSets.Select<BeatmapSetInfo, DirectPanel>(b =>
return new DirectListPanel(b); {
} switch (displayStyle)
}); {
case PanelDisplayStyle.Grid:
return new DirectGridPanel(b) { Width = 400 };
default:
return new DirectListPanel(b);
}
})
};
LoadComponentAsync(newPanels, p =>
{
if (panels != null) ScrollFlow.Remove(panels);
ScrollFlow.Add(panels = newPanels);
});
} }
private GetBeatmapSetsRequest getSetsRequest; private GetBeatmapSetsRequest getSetsRequest;

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));
}
Defaults = manager.DefaultKeyBindings; private class DefaultBindingsSubsection : KeyBindingsSubsection
{
protected override string Header => string.Empty;
public DefaultBindingsSubsection(KeyBindingInputManager manager)
: base(null)
{
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,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:
State = ProgressNotificationState.Cancelled; if (CancelRequested?.Invoke() != false)
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

@ -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

@ -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);
return;
}
int result = hY.HitObject.StartTime.CompareTo(hX.HitObject.StartTime);
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;
} }
} }
@ -244,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>>();
@ -257,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,10 +209,24 @@ 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,26 +182,27 @@ 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>
@ -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

@ -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

@ -105,9 +105,10 @@
<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" />
@ -115,6 +116,7 @@
<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="Graphics\Containers\SectionsContainer.cs" />
<Compile Include="Graphics\UserInterface\ProgressBar.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" />
<Compile Include="Overlays\Settings\Sections\Audio\MainMenuSettings.cs" /> <Compile Include="Overlays\Settings\Sections\Audio\MainMenuSettings.cs" />
@ -128,6 +130,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" />
@ -320,7 +323,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" />