Merge branch 'master' into hitobject-pooling-base

This commit is contained in:
smoogipoo
2020-11-11 17:09:18 +09:00
57 changed files with 899 additions and 567 deletions

50
.vscode/tasks.json vendored
View File

@ -11,9 +11,9 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Desktop", "osu.Desktop",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
@ -26,10 +26,10 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Desktop", "osu.Desktop",
"/p:Configuration=Release", "-p:Configuration=Release",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
@ -42,9 +42,9 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Tests", "osu.Game.Tests",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
@ -57,10 +57,10 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Tests", "osu.Game.Tests",
"/p:Configuration=Release", "-p:Configuration=Release",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
@ -73,9 +73,9 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Tournament.Tests", "osu.Game.Tournament.Tests",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
@ -88,10 +88,10 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Tournament.Tests", "osu.Game.Tournament.Tests",
"/p:Configuration=Release", "-p:Configuration=Release",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
@ -104,10 +104,10 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Benchmarks", "osu.Game.Benchmarks",
"/p:Configuration=Release", "-p:Configuration=Release",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"

View File

@ -135,6 +135,9 @@ namespace osu.Desktop
case UserActivity.Editing edit: case UserActivity.Editing edit:
return edit.Beatmap.ToString(); return edit.Beatmap.ToString();
case UserActivity.InLobby lobby:
return lobby.Room.Name.Value;
} }
return string.Empty; return string.Empty;

View File

@ -11,9 +11,9 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Rulesets.Catch.Tests.csproj", "osu.Game.Rulesets.Catch.Tests.csproj",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
@ -26,10 +26,10 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Rulesets.Catch.Tests.csproj", "osu.Game.Rulesets.Catch.Tests.csproj",
"/p:Configuration=Release", "-p:Configuration=Release",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"

View File

@ -11,9 +11,9 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Rulesets.Mania.Tests.csproj", "osu.Game.Rulesets.Mania.Tests.csproj",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
@ -26,10 +26,10 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Rulesets.Mania.Tests.csproj", "osu.Game.Rulesets.Mania.Tests.csproj",
"/p:Configuration=Release", "-p:Configuration=Release",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"

View File

@ -11,9 +11,9 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Rulesets.Osu.Tests.csproj", "osu.Game.Rulesets.Osu.Tests.csproj",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
@ -26,10 +26,10 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Rulesets.Osu.Tests.csproj", "osu.Game.Rulesets.Osu.Tests.csproj",
"/p:Configuration=Release", "-p:Configuration=Release",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"

View File

@ -280,8 +280,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
private void addClickStep(MouseButton button) private void addClickStep(MouseButton button)
{ {
AddStep($"press {button}", () => InputManager.PressButton(button)); AddStep($"click {button}", () => InputManager.Click(button));
AddStep($"release {button}", () => InputManager.ReleaseButton(button));
} }
private void assertPlaced(bool expected) => AddAssert($"slider {(expected ? "placed" : "not placed")}", () => (getSlider() != null) == expected); private void assertPlaced(bool expected) => AddAssert($"slider {(expected ? "placed" : "not placed")}", () => (getSlider() != null) == expected);

View File

@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public void UpdateProgress(double completionProgress) public void UpdateProgress(double completionProgress)
{ {
if (drawableSlider == null) if (drawableSlider?.HitObject == null)
return; return;
Slider slider = drawableSlider.HitObject; Slider slider = drawableSlider.HitObject;
@ -92,7 +92,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public void Refresh() public void Refresh()
{ {
if (drawableSlider == null) if (drawableSlider?.HitObject == null)
return; return;
// Generate the entire curve // Generate the entire curve

View File

@ -11,9 +11,9 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Rulesets.Taiko.Tests.csproj", "osu.Game.Rulesets.Taiko.Tests.csproj",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
@ -26,10 +26,10 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Rulesets.Taiko.Tests.csproj", "osu.Game.Rulesets.Taiko.Tests.csproj",
"/p:Configuration=Release", "-p:Configuration=Release",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"

View File

@ -41,11 +41,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("store time", () => time = Player.GameplayClockContainer.GameplayClock.CurrentTime); AddStep("store time", () => time = Player.GameplayClockContainer.GameplayClock.CurrentTime);
// test seek via keyboard // test seek via keyboard
AddStep("seek with right arrow key", () => press(Key.Right)); AddStep("seek with right arrow key", () => InputManager.Key(Key.Right));
AddAssert("time seeked forward", () => Player.GameplayClockContainer.GameplayClock.CurrentTime > time + 2000); AddAssert("time seeked forward", () => Player.GameplayClockContainer.GameplayClock.CurrentTime > time + 2000);
AddStep("store time", () => time = Player.GameplayClockContainer.GameplayClock.CurrentTime); AddStep("store time", () => time = Player.GameplayClockContainer.GameplayClock.CurrentTime);
AddStep("seek with left arrow key", () => press(Key.Left)); AddStep("seek with left arrow key", () => InputManager.Key(Key.Left));
AddAssert("time seeked backward", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < time); AddAssert("time seeked backward", () => Player.GameplayClockContainer.GameplayClock.CurrentTime < time);
seekToBreak(0); seekToBreak(0);
@ -67,11 +67,5 @@ namespace osu.Game.Tests.Visual.Gameplay
BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex); BreakPeriod destBreak() => Beatmap.Value.Beatmap.Breaks.ElementAt(breakIndex);
} }
private void press(Key key)
{
InputManager.PressKey(key);
InputManager.ReleaseKey(key);
}
} }
} }

View File

@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
showOverlay(); showOverlay();
AddStep("Up arrow", () => press(Key.Up)); AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value); AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
} }
@ -98,7 +98,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
showOverlay(); showOverlay();
AddStep("Down arrow", () => press(Key.Down)); AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("First button selected", () => getButton(0).Selected.Value); AddAssert("First button selected", () => getButton(0).Selected.Value);
} }
@ -110,11 +110,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddStep("Show overlay", () => failOverlay.Show()); AddStep("Show overlay", () => failOverlay.Show());
AddStep("Up arrow", () => press(Key.Up)); AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Up arrow", () => press(Key.Up)); AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Up arrow", () => press(Key.Up)); AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
} }
@ -126,11 +126,11 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
AddStep("Show overlay", () => failOverlay.Show()); AddStep("Show overlay", () => failOverlay.Show());
AddStep("Down arrow", () => press(Key.Down)); AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
AddStep("Down arrow", () => press(Key.Down)); AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
AddStep("Down arrow", () => press(Key.Down)); AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
} }
@ -177,7 +177,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
showOverlay(); showOverlay();
AddStep("Down arrow", () => press(Key.Down)); AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddAssert("First button not selected", () => !getButton(0).Selected.Value); AddAssert("First button not selected", () => !getButton(0).Selected.Value);
AddAssert("Second button selected", () => getButton(1).Selected.Value); AddAssert("Second button selected", () => getButton(1).Selected.Value);
@ -195,7 +195,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}); });
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Up arrow", () => press(Key.Up)); AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("Second button not selected", () => !getButton(1).Selected.Value); AddAssert("Second button not selected", () => !getButton(1).Selected.Value);
AddAssert("First button selected", () => getButton(0).Selected.Value); AddAssert("First button selected", () => getButton(0).Selected.Value);
} }
@ -210,7 +210,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero)); AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero));
AddStep("Down arrow", () => press(Key.Down)); AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition
} }
@ -246,8 +246,8 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Select second button", () => AddStep("Select second button", () =>
{ {
press(Key.Down); InputManager.Key(Key.Down);
press(Key.Down); InputManager.Key(Key.Down);
}); });
bool triggered = false; bool triggered = false;
@ -256,7 +256,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
lastAction = pauseOverlay.OnRetry; lastAction = pauseOverlay.OnRetry;
pauseOverlay.OnRetry = () => triggered = true; pauseOverlay.OnRetry = () => triggered = true;
press(Key.Enter); InputManager.Key(Key.Enter);
}); });
AddAssert("Action was triggered", () => AddAssert("Action was triggered", () =>
@ -290,12 +290,6 @@ namespace osu.Game.Tests.Visual.Gameplay
private DialogButton getButton(int index) => pauseOverlay.Buttons.Skip(index).First(); private DialogButton getButton(int index) => pauseOverlay.Buttons.Skip(index).First();
private void press(Key key)
{
InputManager.PressKey(key);
InputManager.ReleaseKey(key);
}
private void press(GlobalAction action) private void press(GlobalAction action)
{ {
globalActionContainer.TriggerPressed(action); globalActionContainer.TriggerPressed(action);

View File

@ -32,11 +32,7 @@ namespace osu.Game.Tests.Visual.Gameplay
[Test] [Test]
public void TestDefaultsWhenNotDatabased() public void TestDefaultsWhenNotDatabased()
{ {
AddStep("fire key", () => AddStep("fire key", () => InputManager.Key(Key.A));
{
InputManager.PressKey(Key.A);
InputManager.ReleaseKey(Key.A);
});
AddAssert("received key", () => receiver.ReceivedAction); AddAssert("received key", () => receiver.ReceivedAction);
} }

View File

@ -40,11 +40,7 @@ namespace osu.Game.Tests.Visual.Gameplay
void addPressKeyStep() void addPressKeyStep()
{ {
AddStep($"Press {testKey} key", () => AddStep($"Press {testKey} key", () => InputManager.Key(testKey));
{
InputManager.PressKey(testKey);
InputManager.ReleaseKey(testKey);
});
} }
addPressKeyStep(); addPressKeyStep();

View File

@ -59,11 +59,7 @@ namespace osu.Game.Tests.Visual.Gameplay
confirmClockRunning(false); confirmClockRunning(false);
confirmPauseOverlayShown(false); confirmPauseOverlayShown(false);
AddStep("click to resume", () => AddStep("click to resume", () => InputManager.Click(MouseButton.Left));
{
InputManager.PressButton(MouseButton.Left);
InputManager.ReleaseButton(MouseButton.Left);
});
confirmClockRunning(true); confirmClockRunning(true);
} }

View File

@ -158,8 +158,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("much move with press", () => moveFunction = Scheduler.AddDelayed(() => AddStep("much move with press", () => moveFunction = Scheduler.AddDelayed(() =>
{ {
InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0)); InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(-1, 0));
InputManager.PressButton(MouseButton.Left); InputManager.Click(MouseButton.Left);
InputManager.ReleaseButton(MouseButton.Left);
}, 10, true)); }, 10, true));
AddWaitStep("move", 10); AddWaitStep("move", 10);
AddStep("stop move", () => moveFunction.Cancel()); AddStep("stop move", () => moveFunction.Cancel());

View File

@ -65,10 +65,8 @@ namespace osu.Game.Tests.Visual.Menus
AddStep($"switch to ruleset {i} via shortcut", () => AddStep($"switch to ruleset {i} via shortcut", () =>
{ {
InputManager.PressKey(Key.ControlLeft); InputManager.PressKey(Key.ControlLeft);
InputManager.PressKey(numberKey); InputManager.Key(numberKey);
InputManager.ReleaseKey(Key.ControlLeft); InputManager.ReleaseKey(Key.ControlLeft);
InputManager.ReleaseKey(numberKey);
}); });
AddUntilStep("ruleset switched", () => rulesetSelector.Current.Value.Equals(expected)); AddUntilStep("ruleset switched", () => rulesetSelector.Current.Value.Equals(expected));

View File

@ -71,11 +71,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void press(Key down) private void press(Key down)
{ {
AddStep($"press {down}", () => AddStep($"press {down}", () => InputManager.Key(down));
{
InputManager.PressKey(down);
InputManager.ReleaseKey(down);
});
} }
[Test] [Test]

View File

@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Navigation
if (withUserPause) if (withUserPause)
AddStep("pause", () => Game.Dependencies.Get<MusicController>().Stop(true)); AddStep("pause", () => Game.Dependencies.Get<MusicController>().Stop(true));
AddStep("press enter", () => pressAndRelease(Key.Enter)); AddStep("press enter", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null); AddUntilStep("wait for player", () => (player = Game.ScreenStack.CurrentScreen as Player) != null);
AddUntilStep("wait for fail", () => player.HasFailed); AddUntilStep("wait for fail", () => player.HasFailed);
@ -122,11 +122,11 @@ namespace osu.Game.Tests.Visual.Navigation
public void TestOpenOptionsAndExitWithEscape() public void TestOpenOptionsAndExitWithEscape()
{ {
AddUntilStep("Wait for options to load", () => Game.Settings.IsLoaded); AddUntilStep("Wait for options to load", () => Game.Settings.IsLoaded);
AddStep("Enter menu", () => pressAndRelease(Key.Enter)); AddStep("Enter menu", () => InputManager.Key(Key.Enter));
AddStep("Move mouse to options overlay", () => InputManager.MoveMouseTo(optionsButtonPosition)); AddStep("Move mouse to options overlay", () => InputManager.MoveMouseTo(optionsButtonPosition));
AddStep("Click options overlay", () => InputManager.Click(MouseButton.Left)); AddStep("Click options overlay", () => InputManager.Click(MouseButton.Left));
AddAssert("Options overlay was opened", () => Game.Settings.State.Value == Visibility.Visible); AddAssert("Options overlay was opened", () => Game.Settings.State.Value == Visibility.Visible);
AddStep("Hide options overlay using escape", () => pressAndRelease(Key.Escape)); AddStep("Hide options overlay using escape", () => InputManager.Key(Key.Escape));
AddAssert("Options overlay was closed", () => Game.Settings.State.Value == Visibility.Hidden); AddAssert("Options overlay was closed", () => Game.Settings.State.Value == Visibility.Hidden);
} }
@ -158,10 +158,8 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("Change ruleset to osu!taiko", () => AddStep("Change ruleset to osu!taiko", () =>
{ {
InputManager.PressKey(Key.ControlLeft); InputManager.PressKey(Key.ControlLeft);
InputManager.PressKey(Key.Number2); InputManager.Key(Key.Number2);
InputManager.ReleaseKey(Key.ControlLeft); InputManager.ReleaseKey(Key.ControlLeft);
InputManager.ReleaseKey(Key.Number2);
}); });
AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType<ToolbarRulesetSelector>().Single().Current.Value.ID == 1); AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType<ToolbarRulesetSelector>().Single().Current.Value.ID == 1);
@ -181,10 +179,8 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("Change ruleset to osu!taiko", () => AddStep("Change ruleset to osu!taiko", () =>
{ {
InputManager.PressKey(Key.ControlLeft); InputManager.PressKey(Key.ControlLeft);
InputManager.PressKey(Key.Number2); InputManager.Key(Key.Number2);
InputManager.ReleaseKey(Key.ControlLeft); InputManager.ReleaseKey(Key.ControlLeft);
InputManager.ReleaseKey(Key.Number2);
}); });
AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType<ToolbarRulesetSelector>().Single().Current.Value.ID == 1); AddAssert("Ruleset changed to osu!taiko", () => Game.Toolbar.ChildrenOfType<ToolbarRulesetSelector>().Single().Current.Value.ID == 1);
@ -193,7 +189,7 @@ namespace osu.Game.Tests.Visual.Navigation
} }
private void pushEscape() => private void pushEscape() =>
AddStep("Press escape", () => pressAndRelease(Key.Escape)); AddStep("Press escape", () => InputManager.Key(Key.Escape));
private void exitViaEscapeAndConfirm() private void exitViaEscapeAndConfirm()
{ {
@ -208,12 +204,6 @@ namespace osu.Game.Tests.Visual.Navigation
ConfirmAtMainMenu(); ConfirmAtMainMenu();
} }
private void pressAndRelease(Key key)
{
InputManager.PressKey(key);
InputManager.ReleaseKey(key);
}
private class TestSongSelect : PlaySongSelect private class TestSongSelect : PlaySongSelect
{ {
public ModSelectOverlay ModSelectOverlay => ModSelect; public ModSelectOverlay ModSelectOverlay => ModSelect;

View File

@ -103,11 +103,7 @@ namespace osu.Game.Tests.Visual.Online
public void TestChannelShortcutKeys() public void TestChannelShortcutKeys()
{ {
AddStep("Join channels", () => channels.ForEach(channel => channelManager.JoinChannel(channel))); AddStep("Join channels", () => channels.ForEach(channel => channelManager.JoinChannel(channel)));
AddStep("Close channel selector", () => AddStep("Close channel selector", () => InputManager.Key(Key.Escape));
{
InputManager.PressKey(Key.Escape);
InputManager.ReleaseKey(Key.Escape);
});
AddUntilStep("Wait for close", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); AddUntilStep("Wait for close", () => chatOverlay.SelectionOverlayState == Visibility.Hidden);
for (int zeroBasedIndex = 0; zeroBasedIndex < 10; ++zeroBasedIndex) for (int zeroBasedIndex = 0; zeroBasedIndex < 10; ++zeroBasedIndex)
@ -216,9 +212,8 @@ namespace osu.Game.Tests.Visual.Online
{ {
var channelKey = Key.Number0 + number; var channelKey = Key.Number0 + number;
InputManager.PressKey(Key.AltLeft); InputManager.PressKey(Key.AltLeft);
InputManager.PressKey(channelKey); InputManager.Key(channelKey);
InputManager.ReleaseKey(Key.AltLeft); InputManager.ReleaseKey(Key.AltLeft);
InputManager.ReleaseKey(channelKey);
} }
private void clickDrawable(Drawable d) private void clickDrawable(Drawable d)

View File

@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Online
[Test] [Test]
public void TestGenericActivity() public void TestGenericActivity()
{ {
AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby()); AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby(null));
AddStep("Run command", () => Add(new NowPlayingCommand())); AddStep("Run command", () => Add(new NowPlayingCommand()));
@ -57,7 +57,7 @@ namespace osu.Game.Tests.Visual.Online
[TestCase(false)] [TestCase(false)]
public void TestLinkPresence(bool hasOnlineId) public void TestLinkPresence(bool hasOnlineId)
{ {
AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby()); AddStep("Set activity", () => API.Activity.Value = new UserActivity.InLobby(null));
AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null) AddStep("Set beatmap", () => Beatmap.Value = new DummyWorkingBeatmap(Audio, null)
{ {

View File

@ -0,0 +1,57 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Overlays.Profile.Sections.Historical;
using osu.Framework.Graphics;
using osu.Game.Overlays;
using osu.Framework.Allocation;
using static osu.Game.Users.User;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneUserHistoryGraph : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
public TestSceneUserHistoryGraph()
{
UserHistoryGraph graph;
Add(graph = new UserHistoryGraph
{
RelativeSizeAxes = Axes.X,
Height = 200,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
TooltipCounterName = "Test"
});
var values = new[]
{
new UserHistoryCount { Date = new DateTime(2000, 1, 1), Count = 10 },
new UserHistoryCount { Date = new DateTime(2000, 2, 1), Count = 20 },
new UserHistoryCount { Date = new DateTime(2000, 3, 1), Count = 100 },
new UserHistoryCount { Date = new DateTime(2000, 4, 1), Count = 15 },
new UserHistoryCount { Date = new DateTime(2000, 5, 1), Count = 30 }
};
var moreValues = new[]
{
new UserHistoryCount { Date = new DateTime(2010, 5, 1), Count = 1000 },
new UserHistoryCount { Date = new DateTime(2010, 6, 1), Count = 20 },
new UserHistoryCount { Date = new DateTime(2010, 7, 1), Count = 20000 },
new UserHistoryCount { Date = new DateTime(2010, 8, 1), Count = 30 },
new UserHistoryCount { Date = new DateTime(2010, 9, 1), Count = 50 },
new UserHistoryCount { Date = new DateTime(2010, 10, 1), Count = 2000 },
new UserHistoryCount { Date = new DateTime(2010, 11, 1), Count = 2100 }
};
AddStep("Set fake values", () => graph.Values = values);
AddStep("Set more values", () => graph.Values = moreValues);
AddStep("Set null values", () => graph.Values = null);
AddStep("Set empty values", () => graph.Values = Array.Empty<UserHistoryCount>());
}
}
}

View File

@ -51,8 +51,7 @@ namespace osu.Game.Tests.Visual.Settings
clickDelegate = Scheduler.AddDelayed(() => clickDelegate = Scheduler.AddDelayed(() =>
{ {
InputManager.PressButton(MouseButton.Left); InputManager.Click(MouseButton.Left);
InputManager.ReleaseButton(MouseButton.Left);
if (++buttonClicks == 2) if (++buttonClicks == 2)
{ {

View File

@ -98,10 +98,8 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select next and enter", () => AddStep("select next and enter", () =>
{ {
InputManager.PressKey(Key.Down); InputManager.Key(Key.Down);
InputManager.ReleaseKey(Key.Down); InputManager.Key(Key.Enter);
InputManager.PressKey(Key.Enter);
InputManager.ReleaseKey(Key.Enter);
}); });
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
@ -123,10 +121,8 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("select next and enter", () => AddStep("select next and enter", () =>
{ {
InputManager.PressKey(Key.Enter); InputManager.Key(Key.Enter);
InputManager.ReleaseKey(Key.Enter); InputManager.Key(Key.Down);
InputManager.PressKey(Key.Down);
InputManager.ReleaseKey(Key.Down);
}); });
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
@ -151,11 +147,9 @@ namespace osu.Game.Tests.Visual.SongSelect
InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmap>() InputManager.MoveMouseTo(songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmap>()
.First(b => ((CarouselBeatmap)b.Item).Beatmap != songSelect.Carousel.SelectedBeatmap)); .First(b => ((CarouselBeatmap)b.Item).Beatmap != songSelect.Carousel.SelectedBeatmap));
InputManager.PressButton(MouseButton.Left); InputManager.Click(MouseButton.Left);
InputManager.ReleaseButton(MouseButton.Left);
InputManager.PressKey(Key.Enter); InputManager.Key(Key.Enter);
InputManager.ReleaseKey(Key.Enter);
}); });
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen()); AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
@ -182,8 +176,7 @@ namespace osu.Game.Tests.Visual.SongSelect
InputManager.PressButton(MouseButton.Left); InputManager.PressButton(MouseButton.Left);
InputManager.PressKey(Key.Enter); InputManager.Key(Key.Enter);
InputManager.ReleaseKey(Key.Enter);
InputManager.ReleaseButton(MouseButton.Left); InputManager.ReleaseButton(MouseButton.Left);
}); });
@ -567,10 +560,8 @@ namespace osu.Game.Tests.Visual.SongSelect
AddStep("press ctrl+enter", () => AddStep("press ctrl+enter", () =>
{ {
InputManager.PressKey(Key.ControlLeft); InputManager.PressKey(Key.ControlLeft);
InputManager.PressKey(Key.Enter); InputManager.Key(Key.Enter);
InputManager.ReleaseKey(Key.ControlLeft); InputManager.ReleaseKey(Key.ControlLeft);
InputManager.ReleaseKey(Key.Enter);
}); });
AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader); AddUntilStep("wait for player", () => Stack.CurrentScreen is PlayerLoader);
@ -617,8 +608,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
InputManager.MoveMouseTo(difficultyIcon); InputManager.MoveMouseTo(difficultyIcon);
InputManager.PressButton(MouseButton.Left); InputManager.Click(MouseButton.Left);
InputManager.ReleaseButton(MouseButton.Left);
}); });
AddAssert("Selected beatmap correct", () => getCurrentBeatmapIndex() == getDifficultyIconIndex(set, difficultyIcon)); AddAssert("Selected beatmap correct", () => getCurrentBeatmapIndex() == getDifficultyIconIndex(set, difficultyIcon));
@ -647,8 +637,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
InputManager.MoveMouseTo(filteredIcon); InputManager.MoveMouseTo(filteredIcon);
InputManager.PressButton(MouseButton.Left); InputManager.Click(MouseButton.Left);
InputManager.ReleaseButton(MouseButton.Left);
}); });
AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmap == filteredBeatmap); AddAssert("Selected beatmap correct", () => songSelect.Carousel.SelectedBeatmap == filteredBeatmap);
@ -691,8 +680,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
InputManager.MoveMouseTo(difficultyIcon); InputManager.MoveMouseTo(difficultyIcon);
InputManager.PressButton(MouseButton.Left); InputManager.Click(MouseButton.Left);
InputManager.ReleaseButton(MouseButton.Left);
}); });
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3); AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);
@ -738,8 +726,7 @@ namespace osu.Game.Tests.Visual.SongSelect
{ {
InputManager.MoveMouseTo(groupIcon); InputManager.MoveMouseTo(groupIcon);
InputManager.PressButton(MouseButton.Left); InputManager.Click(MouseButton.Left);
InputManager.ReleaseButton(MouseButton.Left);
}); });
AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3); AddUntilStep("Check ruleset changed to mania", () => Ruleset.Value.ID == 3);

View File

@ -48,7 +48,7 @@ namespace osu.Game.Tests.Visual.UserInterface
}); });
AddStep("enter text", () => commentEditor.Current.Value = "text"); AddStep("enter text", () => commentEditor.Current.Value = "text");
AddStep("press Enter", () => press(Key.Enter)); AddStep("press Enter", () => InputManager.Key(Key.Enter));
AddAssert("text committed", () => commentEditor.CommittedText == "text"); AddAssert("text committed", () => commentEditor.CommittedText == "text");
AddAssert("button is loading", () => commentEditor.IsLoading); AddAssert("button is loading", () => commentEditor.IsLoading);
@ -63,7 +63,7 @@ namespace osu.Game.Tests.Visual.UserInterface
InputManager.Click(MouseButton.Left); InputManager.Click(MouseButton.Left);
}); });
AddStep("press Enter", () => press(Key.Enter)); AddStep("press Enter", () => InputManager.Key(Key.Enter));
AddAssert("no text committed", () => commentEditor.CommittedText == null); AddAssert("no text committed", () => commentEditor.CommittedText == null);
AddAssert("button is not loading", () => !commentEditor.IsLoading); AddAssert("button is not loading", () => !commentEditor.IsLoading);
@ -101,12 +101,6 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("cancel action fired", () => cancellableCommentEditor.Cancelled); AddAssert("cancel action fired", () => cancellableCommentEditor.Cancelled);
} }
private void press(Key key)
{
InputManager.PressKey(key);
InputManager.ReleaseKey(key);
}
private class TestCommentEditor : CommentEditor private class TestCommentEditor : CommentEditor
{ {
public new Bindable<string> Current => base.Current; public new Bindable<string> Current => base.Current;

View File

@ -11,9 +11,9 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Tournament.Tests.csproj", "osu.Game.Tournament.Tests.csproj",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
@ -26,10 +26,10 @@
"build", "build",
"--no-restore", "--no-restore",
"osu.Game.Tournament.Tests.csproj", "osu.Game.Tournament.Tests.csproj",
"/p:Configuration=Release", "-p:Configuration=Release",
"/p:GenerateFullPaths=true", "-p:GenerateFullPaths=true",
"/m", "-m",
"/verbosity:m" "-verbosity:m"
], ],
"group": "build", "group": "build",
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"

View File

@ -30,8 +30,22 @@ namespace osu.Game.Beatmaps
// Too many simultaneous updates can lead to stutters. One thread seems to work fine for song select display purposes. // Too many simultaneous updates can lead to stutters. One thread seems to work fine for song select display purposes.
private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(1, nameof(BeatmapDifficultyCache)); private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(1, nameof(BeatmapDifficultyCache));
// All bindables that should be updated along with the current ruleset + mods. /// <summary>
private readonly LockedWeakList<BindableStarDifficulty> trackedBindables = new LockedWeakList<BindableStarDifficulty>(); /// All bindables that should be updated along with the current ruleset + mods.
/// </summary>
private readonly WeakList<BindableStarDifficulty> trackedBindables = new WeakList<BindableStarDifficulty>();
/// <summary>
/// Cancellation sources used by tracked bindables.
/// </summary>
private readonly List<CancellationTokenSource> linkedCancellationSources = new List<CancellationTokenSource>();
/// <summary>
/// Lock to be held when operating on <see cref="trackedBindables"/> or <see cref="linkedCancellationSources"/>.
/// </summary>
private readonly object bindableUpdateLock = new object();
private CancellationTokenSource trackedUpdateCancellationSource;
[Resolved] [Resolved]
private BeatmapManager beatmapManager { get; set; } private BeatmapManager beatmapManager { get; set; }
@ -59,7 +73,10 @@ namespace osu.Game.Beatmaps
public IBindable<StarDifficulty> GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default) public IBindable<StarDifficulty> GetBindableDifficulty([NotNull] BeatmapInfo beatmapInfo, CancellationToken cancellationToken = default)
{ {
var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken); var bindable = createBindable(beatmapInfo, currentRuleset.Value, currentMods.Value, cancellationToken);
lock (bindableUpdateLock)
trackedBindables.Add(bindable); trackedBindables.Add(bindable);
return bindable; return bindable;
} }
@ -86,7 +103,8 @@ namespace osu.Game.Beatmaps
/// <param name="mods">The <see cref="Mod"/>s to get the difficulty with.</param> /// <param name="mods">The <see cref="Mod"/>s to get the difficulty with.</param>
/// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops computing the star difficulty.</param> /// <param name="cancellationToken">An optional <see cref="CancellationToken"/> which stops computing the star difficulty.</param>
/// <returns>The <see cref="StarDifficulty"/>.</returns> /// <returns>The <see cref="StarDifficulty"/>.</returns>
public Task<StarDifficulty> GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IEnumerable<Mod> mods = null, CancellationToken cancellationToken = default) public Task<StarDifficulty> GetDifficultyAsync([NotNull] BeatmapInfo beatmapInfo, [CanBeNull] RulesetInfo rulesetInfo = null, [CanBeNull] IEnumerable<Mod> mods = null,
CancellationToken cancellationToken = default)
{ {
// In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset. // In the case that the user hasn't given us a ruleset, use the beatmap's default ruleset.
rulesetInfo ??= beatmapInfo.Ruleset; rulesetInfo ??= beatmapInfo.Ruleset;
@ -140,13 +158,12 @@ namespace osu.Game.Beatmaps
return DifficultyRating.Easy; return DifficultyRating.Easy;
} }
private CancellationTokenSource trackedUpdateCancellationSource;
private readonly List<CancellationTokenSource> linkedCancellationSources = new List<CancellationTokenSource>();
/// <summary> /// <summary>
/// Updates all tracked <see cref="BindableStarDifficulty"/> using the current ruleset and mods. /// Updates all tracked <see cref="BindableStarDifficulty"/> using the current ruleset and mods.
/// </summary> /// </summary>
private void updateTrackedBindables() private void updateTrackedBindables()
{
lock (bindableUpdateLock)
{ {
cancelTrackedBindableUpdate(); cancelTrackedBindableUpdate();
trackedUpdateCancellationSource = new CancellationTokenSource(); trackedUpdateCancellationSource = new CancellationTokenSource();
@ -159,11 +176,14 @@ namespace osu.Game.Beatmaps
updateBindable(b, currentRuleset.Value, currentMods.Value, linkedSource.Token); updateBindable(b, currentRuleset.Value, currentMods.Value, linkedSource.Token);
} }
} }
}
/// <summary> /// <summary>
/// Cancels the existing update of all tracked <see cref="BindableStarDifficulty"/> via <see cref="updateTrackedBindables"/>. /// Cancels the existing update of all tracked <see cref="BindableStarDifficulty"/> via <see cref="updateTrackedBindables"/>.
/// </summary> /// </summary>
private void cancelTrackedBindableUpdate() private void cancelTrackedBindableUpdate()
{
lock (bindableUpdateLock)
{ {
trackedUpdateCancellationSource?.Cancel(); trackedUpdateCancellationSource?.Cancel();
trackedUpdateCancellationSource = null; trackedUpdateCancellationSource = null;
@ -176,6 +196,7 @@ namespace osu.Game.Beatmaps
linkedCancellationSources.Clear(); linkedCancellationSources.Clear();
} }
} }
}
/// <summary> /// <summary>
/// Creates a new <see cref="BindableStarDifficulty"/> and triggers an initial value update. /// Creates a new <see cref="BindableStarDifficulty"/> and triggers an initial value update.

View File

@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Diagnostics;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking; using osu.Framework.Configuration.Tracking;
@ -8,6 +10,7 @@ using osu.Framework.Extensions;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Input; using osu.Game.Input;
using osu.Game.Input.Bindings;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
@ -172,14 +175,30 @@ namespace osu.Game.Configuration
} }
} }
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings public override TrackedSettings CreateTrackedSettings()
{ {
new TrackedSetting<bool>(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")), // these need to be assigned in normal game startup scenarios.
new TrackedSetting<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode, m => new SettingDescription(m, "HUD Visibility", m.GetDescription())), Debug.Assert(LookupKeyBindings != null);
Debug.Assert(LookupSkinName != null);
return new TrackedSettings
{
new TrackedSetting<bool>(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled", LookupKeyBindings(GlobalAction.ToggleGameplayMouseButtons))),
new TrackedSetting<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode, m => new SettingDescription(m, "HUD Visibility", m.GetDescription(), $"cycle: shift-tab quick view: {LookupKeyBindings(GlobalAction.HoldForHUD)}")),
new TrackedSetting<ScalingMode>(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())), new TrackedSetting<ScalingMode>(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())),
new TrackedSetting<int>(OsuSetting.Skin, m =>
{
string skinName = LookupSkinName(m) ?? string.Empty;
return new SettingDescription(skinName, "skin", skinName, $"random: {LookupKeyBindings(GlobalAction.RandomSkin)}");
})
}; };
} }
public Func<int, string> LookupSkinName { private get; set; }
public Func<GlobalAction, string> LookupKeyBindings { private get; set; }
}
public enum OsuSetting public enum OsuSetting
{ {
Ruleset, Ruleset,

View File

@ -48,6 +48,8 @@ namespace osu.Game.Input.Bindings
new KeyBinding(InputKey.Space, GlobalAction.Select), new KeyBinding(InputKey.Space, GlobalAction.Select),
new KeyBinding(InputKey.Enter, GlobalAction.Select), new KeyBinding(InputKey.Enter, GlobalAction.Select),
new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select), new KeyBinding(InputKey.KeypadEnter, GlobalAction.Select),
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.R }, GlobalAction.RandomSkin),
}; };
public IEnumerable<KeyBinding> EditorKeyBindings => new[] public IEnumerable<KeyBinding> EditorKeyBindings => new[]
@ -191,5 +193,8 @@ namespace osu.Game.Input.Bindings
[Description("Hold for HUD")] [Description("Hold for HUD")]
HoldForHUD, HoldForHUD,
[Description("Random Skin")]
RandomSkin,
} }
} }

View File

@ -32,6 +32,23 @@ namespace osu.Game.Input
public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings); public void Register(KeyBindingContainer manager) => insertDefaults(manager.DefaultKeyBindings);
/// <summary>
/// Retrieve all user-defined key combinations (in a format that can be displayed) for a specific action.
/// </summary>
/// <param name="globalAction">The action to lookup.</param>
/// <returns>A set of display strings for all the user's key configuration for the action.</returns>
public IEnumerable<string> GetReadableKeyCombinationsFor(GlobalAction globalAction)
{
foreach (var action in Query().Where(b => (GlobalAction)b.Action == globalAction))
{
string str = action.KeyCombination.ReadableString();
// even if found, the readable string may be empty for an unbound action.
if (str.Length > 0)
yield return str;
}
}
private void insertDefaults(IEnumerable<KeyBinding> defaults, int? rulesetId = null, int? variant = null) private void insertDefaults(IEnumerable<KeyBinding> defaults, int? rulesetId = null, int? variant = null)
{ {
using (var usage = ContextFactory.GetForWrite()) using (var usage = ContextFactory.GetForWrite())

View File

@ -51,6 +51,7 @@ using osu.Game.Screens.Select;
using osu.Game.Updater; using osu.Game.Updater;
using osu.Game.Utils; using osu.Game.Utils;
using LogLevel = osu.Framework.Logging.LogLevel; using LogLevel = osu.Framework.Logging.LogLevel;
using osu.Game.Users;
namespace osu.Game namespace osu.Game
{ {
@ -547,6 +548,20 @@ namespace osu.Game
ScoreManager.GetStableStorage = GetStorageForStableInstall; ScoreManager.GetStableStorage = GetStorageForStableInstall;
ScoreManager.PresentImport = items => PresentScore(items.First()); ScoreManager.PresentImport = items => PresentScore(items.First());
// make config aware of how to lookup skins for on-screen display purposes.
// if this becomes a more common thing, tracked settings should be reconsidered to allow local DI.
LocalConfig.LookupSkinName = id => SkinManager.GetAllUsableSkins().FirstOrDefault(s => s.ID == id)?.ToString() ?? "Unknown";
LocalConfig.LookupKeyBindings = l =>
{
var combinations = KeyBindingStore.GetReadableKeyCombinationsFor(l).ToArray();
if (combinations.Length == 0)
return "none";
return string.Join(" or ", combinations);
};
Container logoContainer; Container logoContainer;
BackButton.Receptor receptor; BackButton.Receptor receptor;
@ -612,7 +627,12 @@ namespace osu.Game
loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add, true); loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add, true);
loadComponentSingleFile(new OnScreenDisplay(), Add, true); var onScreenDisplay = new OnScreenDisplay();
onScreenDisplay.BeginTracking(this, frameworkConfig);
onScreenDisplay.BeginTracking(this, LocalConfig);
loadComponentSingleFile(onScreenDisplay, Add, true);
loadComponentSingleFile(notifications.With(d => loadComponentSingleFile(notifications.With(d =>
{ {
@ -872,6 +892,10 @@ namespace osu.Game
case GlobalAction.ToggleGameplayMouseButtons: case GlobalAction.ToggleGameplayMouseButtons:
LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get<bool>(OsuSetting.MouseDisableButtons)); LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get<bool>(OsuSetting.MouseDisableButtons));
return true; return true;
case GlobalAction.RandomSkin:
SkinManager.SelectRandomSkin();
return true;
} }
return false; return false;
@ -961,11 +985,15 @@ namespace osu.Game
LocalUserPlaying.Value = false; LocalUserPlaying.Value = false;
if (current is IOsuScreen currentOsuScreen) if (current is IOsuScreen currentOsuScreen)
{
OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode); OverlayActivationMode.UnbindFrom(currentOsuScreen.OverlayActivationMode);
API.Activity.UnbindFrom(currentOsuScreen.Activity);
}
if (newScreen is IOsuScreen newOsuScreen) if (newScreen is IOsuScreen newOsuScreen)
{ {
OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode); OverlayActivationMode.BindTo(newOsuScreen.OverlayActivationMode);
((IBindable<UserActivity>)API.Activity).BindTo(newOsuScreen.Activity);
MusicController.AllowRateAdjustments = newOsuScreen.AllowRateAdjustments; MusicController.AllowRateAdjustments = newOsuScreen.AllowRateAdjustments;

View File

@ -32,6 +32,11 @@ namespace osu.Game.Overlays.BeatmapListing
/// </summary> /// </summary>
public Action SearchStarted; public Action SearchStarted;
/// <summary>
/// Any time the search text box receives key events (even while masked).
/// </summary>
public Action TypingStarted;
/// <summary> /// <summary>
/// True when pagination has reached the end of available results. /// True when pagination has reached the end of available results.
/// </summary> /// </summary>
@ -82,7 +87,10 @@ namespace osu.Game.Overlays.BeatmapListing
Radius = 3, Radius = 3,
Offset = new Vector2(0f, 1f), Offset = new Vector2(0f, 1f),
}, },
Child = searchControl = new BeatmapListingSearchControl(), Child = searchControl = new BeatmapListingSearchControl
{
TypingStarted = () => TypingStarted?.Invoke()
}
}, },
new Container new Container
{ {

View File

@ -1,12 +1,14 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
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 osuTK; using osuTK;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
@ -19,6 +21,11 @@ namespace osu.Game.Overlays.BeatmapListing
{ {
public class BeatmapListingSearchControl : CompositeDrawable public class BeatmapListingSearchControl : CompositeDrawable
{ {
/// <summary>
/// Any time the text box receives key events (even while masked).
/// </summary>
public Action TypingStarted;
public Bindable<string> Query => textBox.Current; public Bindable<string> Query => textBox.Current;
public Bindable<RulesetInfo> Ruleset => modeFilter.Current; public Bindable<RulesetInfo> Ruleset => modeFilter.Current;
@ -102,6 +109,7 @@ namespace osu.Game.Overlays.BeatmapListing
textBox = new BeatmapSearchTextBox textBox = new BeatmapSearchTextBox
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
TypingStarted = () => TypingStarted?.Invoke(),
}, },
new ReverseChildIDFillFlowContainer<Drawable> new ReverseChildIDFillFlowContainer<Drawable>
{ {
@ -138,12 +146,26 @@ namespace osu.Game.Overlays.BeatmapListing
private class BeatmapSearchTextBox : SearchTextBox private class BeatmapSearchTextBox : SearchTextBox
{ {
/// <summary>
/// Any time the text box receives key events (even while masked).
/// </summary>
public Action TypingStarted;
protected override Color4 SelectionColour => Color4.Gray; protected override Color4 SelectionColour => Color4.Gray;
public BeatmapSearchTextBox() public BeatmapSearchTextBox()
{ {
PlaceholderText = @"type in keywords..."; PlaceholderText = @"type in keywords...";
} }
protected override bool OnKeyDown(KeyDownEvent e)
{
if (!base.OnKeyDown(e))
return false;
TypingStarted?.Invoke();
return true;
}
} }
} }
} }

View File

@ -68,6 +68,7 @@ namespace osu.Game.Overlays
Header, Header,
filterControl = new BeatmapListingFilterControl filterControl = new BeatmapListingFilterControl
{ {
TypingStarted = onTypingStarted,
SearchStarted = onSearchStarted, SearchStarted = onSearchStarted,
SearchFinished = onSearchFinished, SearchFinished = onSearchFinished,
}, },
@ -102,6 +103,12 @@ namespace osu.Game.Overlays
}; };
} }
private void onTypingStarted()
{
// temporary until the textbox/header is updated to always stay on screen.
resultScrollContainer.ScrollToStart();
}
protected override void OnFocus(FocusEvent e) protected override void OnFocus(FocusEvent e)
{ {
base.OnFocus(e); base.OnFocus(e);

View File

@ -110,7 +110,11 @@ namespace osu.Game.Overlays.Dashboard
User = user; User = user;
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
{
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new FillFlowContainer new FillFlowContainer
@ -121,7 +125,7 @@ namespace osu.Game.Overlays.Dashboard
Width = 290, Width = 290,
Children = new Drawable[] Children = new Drawable[]
{ {
new UserGridPanel(user) new UserGridPanel(User)
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
@ -133,7 +137,8 @@ namespace osu.Game.Overlays.Dashboard
Text = "Watch", Text = "Watch",
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Action = () => game?.PerformFromScreen(s => s.Push(new Spectator(user))) Action = () => game?.PerformFromScreen(s => s.Push(new Spectator(User))),
Enabled = { Value = User.Id != api.LocalUser.Value.Id }
} }
} }
}, },

View File

@ -85,6 +85,8 @@ namespace osu.Game.Overlays.Mods
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
{ {
if (e.ControlPressed) return false;
if (ToggleKeys != null) if (ToggleKeys != null)
{ {
var index = Array.IndexOf(ToggleKeys, e.Key); var index = Array.IndexOf(ToggleKeys, e.Key);

View File

@ -3,16 +3,14 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking; using osu.Framework.Configuration.Tracking;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osuTK;
using osu.Framework.Graphics.Transforms; using osu.Framework.Graphics.Transforms;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Configuration;
using osu.Game.Overlays.OSD; using osu.Game.Overlays.OSD;
using osuTK;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
@ -47,13 +45,6 @@ namespace osu.Game.Overlays
}; };
} }
[BackgroundDependencyLoader]
private void load(FrameworkConfigManager frameworkConfig, OsuConfigManager osuConfig)
{
BeginTracking(this, frameworkConfig);
BeginTracking(this, osuConfig);
}
private readonly Dictionary<(object, IConfigManager), TrackedSettings> trackedConfigManagers = new Dictionary<(object, IConfigManager), TrackedSettings>(); private readonly Dictionary<(object, IConfigManager), TrackedSettings> trackedConfigManagers = new Dictionary<(object, IConfigManager), TrackedSettings>();
/// <summary> /// <summary>

View File

@ -4,307 +4,89 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using Humanizer;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Users; using osu.Game.Users;
using osuTK;
namespace osu.Game.Overlays.Profile.Header.Components namespace osu.Game.Overlays.Profile.Header.Components
{ {
public class RankGraph : Container, IHasCustomTooltip public class RankGraph : UserGraph<int, int>
{ {
private const float secondary_textsize = 13;
private const float padding = 10;
private const float fade_duration = 150;
private const int ranked_days = 88; private const int ranked_days = 88;
private readonly RankChartLineGraph graph;
private readonly OsuSpriteText placeholder;
private KeyValuePair<int, int>[] ranks;
private int dayIndex;
public readonly Bindable<UserStatistics> Statistics = new Bindable<UserStatistics>(); public readonly Bindable<UserStatistics> Statistics = new Bindable<UserStatistics>();
private readonly OsuSpriteText placeholder;
public RankGraph() public RankGraph()
{ {
Padding = new MarginPadding { Vertical = padding }; Add(placeholder = new OsuSpriteText
Children = new Drawable[]
{
placeholder = new OsuSpriteText
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Text = "No recent plays", Text = "No recent plays",
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular) Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular)
}, });
graph = new RankChartLineGraph
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
RelativeSizeAxes = Axes.Both,
Y = -secondary_textsize,
Alpha = 0,
}
};
graph.OnBallMove += i => dayIndex = i;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
graph.LineColour = colours.Yellow;
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
Statistics.BindValueChanged(statistics => updateStatistics(statistics.NewValue), true); Statistics.BindValueChanged(statistics => updateStatistics(statistics.NewValue), true);
} }
private void updateStatistics(UserStatistics statistics) private void updateStatistics(UserStatistics statistics)
{ {
placeholder.FadeIn(fade_duration, Easing.Out); int[] userRanks = statistics?.RankHistory?.Data;
Data = userRanks?.Select((x, index) => new KeyValuePair<int, int>(index, x)).Where(x => x.Value != 0).ToArray();
if (statistics?.Ranks.Global == null)
{
graph.FadeOut(fade_duration, Easing.Out);
ranks = null;
return;
} }
int[] userRanks = statistics.RankHistory?.Data ?? new[] { statistics.Ranks.Global.Value }; protected override float GetDataPointHeight(int rank) => -MathF.Log(rank);
ranks = userRanks.Select((x, index) => new KeyValuePair<int, int>(index, x)).Where(x => x.Value != 0).ToArray();
if (ranks.Length > 1) protected override void ShowGraph()
{ {
placeholder.FadeOut(fade_duration, Easing.Out); base.ShowGraph();
placeholder.FadeOut(FADE_DURATION, Easing.Out);
graph.DefaultValueCount = ranks.Length;
graph.Values = ranks.Select(x => -MathF.Log(x.Value));
} }
graph.FadeTo(ranks.Length > 1 ? 1 : 0, fade_duration, Easing.Out); protected override void HideGraph()
{
base.HideGraph();
placeholder.FadeIn(FADE_DURATION, Easing.Out);
} }
protected override bool OnHover(HoverEvent e) protected override object GetTooltipContent(int index, int rank)
{ {
if (ranks?.Length > 1) var days = ranked_days - index + 1;
{
graph.UpdateBallPosition(e.MousePosition.X);
graph.ShowBar();
}
return base.OnHover(e);
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (ranks?.Length > 1)
graph.UpdateBallPosition(e.MousePosition.X);
return base.OnMouseMove(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
if (ranks?.Length > 1)
{
graph.HideBar();
}
base.OnHoverLost(e);
}
private class RankChartLineGraph : LineGraph
{
private readonly CircularContainer movingBall;
private readonly Container bar;
private readonly Box ballBg;
private readonly Box line;
public Action<int> OnBallMove;
public RankChartLineGraph()
{
Add(bar = new Container
{
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Alpha = 0,
RelativePositionAxes = Axes.Both,
Children = new Drawable[]
{
line = new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Width = 1.5f,
},
movingBall = new CircularContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
Size = new Vector2(18),
Masking = true,
BorderThickness = 4,
RelativePositionAxes = Axes.Y,
Child = ballBg = new Box { RelativeSizeAxes = Axes.Both }
}
}
});
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider, OsuColour colours)
{
ballBg.Colour = colourProvider.Background5;
movingBall.BorderColour = line.Colour = colours.Yellow;
}
public void UpdateBallPosition(float mouseXPosition)
{
const int duration = 200;
int index = calculateIndex(mouseXPosition);
Vector2 position = calculateBallPosition(index);
movingBall.MoveToY(position.Y, duration, Easing.OutQuint);
bar.MoveToX(position.X, duration, Easing.OutQuint);
OnBallMove.Invoke(index);
}
public void ShowBar() => bar.FadeIn(fade_duration);
public void HideBar() => bar.FadeOut(fade_duration);
private int calculateIndex(float mouseXPosition) => (int)MathF.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1));
private Vector2 calculateBallPosition(int index)
{
float y = GetYPosition(Values.ElementAt(index));
return new Vector2(index / (float)(DefaultValueCount - 1), y);
}
}
public object TooltipContent
{
get
{
if (Statistics.Value?.Ranks.Global == null)
return null;
var days = ranked_days - ranks[dayIndex].Key + 1;
return new TooltipDisplayContent return new TooltipDisplayContent
{ {
Rank = $"#{ranks[dayIndex].Value:#,##0}", Rank = $"#{rank:N0}",
Time = days == 0 ? "now" : $"{days} days ago" Time = days == 0 ? "now" : $"{"day".ToQuantity(days)} ago"
}; };
} }
}
public ITooltip GetCustomTooltip() => new RankGraphTooltip(); protected override UserGraphTooltip GetTooltip() => new RankGraphTooltip();
private class RankGraphTooltip : VisibilityContainer, ITooltip private class RankGraphTooltip : UserGraphTooltip
{ {
private readonly OsuSpriteText globalRankingText, timeText;
private readonly Box background;
public RankGraphTooltip() public RankGraphTooltip()
: base("Global Ranking")
{ {
AutoSizeAxes = Axes.Both;
Masking = true;
CornerRadius = 10;
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Padding = new MarginPadding(10),
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
Text = "Global Ranking "
},
globalRankingText = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
}
}
},
timeText = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
}
}
}
};
} }
[BackgroundDependencyLoader] public override bool SetContent(object content)
private void load(OsuColour colours)
{
// Temporary colour since it's currently impossible to change it without bugs (see https://github.com/ppy/osu-framework/issues/3231)
// If above is fixed, this should use OverlayColourProvider
background.Colour = colours.Gray1;
}
public bool SetContent(object content)
{ {
if (!(content is TooltipDisplayContent info)) if (!(content is TooltipDisplayContent info))
return false; return false;
globalRankingText.Text = info.Rank; Counter.Text = info.Rank;
timeText.Text = info.Time; BottomText.Text = info.Time;
return true; return true;
} }
private bool instantMove = true;
public void Move(Vector2 pos)
{
if (instantMove)
{
Position = pos;
instantMove = false;
}
else
this.MoveTo(pos, 200, Easing.OutQuint);
}
protected override void PopIn()
{
instantMove |= !IsPresent;
this.FadeIn(200, Easing.OutQuint);
}
protected override void PopOut() => this.FadeOut(200, Easing.OutQuint);
} }
private class TooltipDisplayContent private class TooltipDisplayContent

View File

@ -0,0 +1,62 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using static osu.Game.Users.User;
namespace osu.Game.Overlays.Profile.Sections.Historical
{
public class UserHistoryGraph : UserGraph<DateTime, long>
{
[CanBeNull]
public UserHistoryCount[] Values
{
set => Data = value?.Select(v => new KeyValuePair<DateTime, long>(v.Date, v.Count)).ToArray();
}
/// <summary>
/// Text describing the value being plotted on the graph, which will be displayed as a prefix to the value in the <see cref="HistoryGraphTooltip"/>.
/// </summary>
public string TooltipCounterName { get; set; } = "Plays";
protected override float GetDataPointHeight(long playCount) => playCount;
protected override UserGraphTooltip GetTooltip() => new HistoryGraphTooltip(TooltipCounterName);
protected override object GetTooltipContent(DateTime date, long playCount)
{
return new TooltipDisplayContent
{
Count = playCount.ToString("N0"),
Date = date.ToString("MMMM yyyy")
};
}
protected class HistoryGraphTooltip : UserGraphTooltip
{
public HistoryGraphTooltip(string tooltipCounterName)
: base(tooltipCounterName)
{
}
public override bool SetContent(object content)
{
if (!(content is TooltipDisplayContent info))
return false;
Counter.Text = info.Count;
BottomText.Text = info.Date;
return true;
}
}
private class TooltipDisplayContent
{
public string Count;
public string Date;
}
}
}

View File

@ -46,6 +46,9 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
protected override void OnItemsReceived(List<APILegacyScoreInfo> items) protected override void OnItemsReceived(List<APILegacyScoreInfo> items)
{ {
if (VisiblePages == 0)
drawableItemIndex = 0;
base.OnItemsReceived(items); base.OnItemsReceived(items);
if (type == ScoreType.Recent) if (type == ScoreType.Recent)
@ -55,6 +58,8 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
protected override APIRequest<List<APILegacyScoreInfo>> CreateRequest() => protected override APIRequest<List<APILegacyScoreInfo>> CreateRequest() =>
new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage); new GetUserScoresRequest(User.Value.Id, type, VisiblePages++, ItemsPerPage);
private int drawableItemIndex;
protected override Drawable CreateDrawableItem(APILegacyScoreInfo model) protected override Drawable CreateDrawableItem(APILegacyScoreInfo model)
{ {
switch (type) switch (type)
@ -63,7 +68,7 @@ namespace osu.Game.Overlays.Profile.Sections.Ranks
return new DrawableProfileScore(model.CreateScoreInfo(Rulesets)); return new DrawableProfileScore(model.CreateScoreInfo(Rulesets));
case ScoreType.Best: case ScoreType.Best:
return new DrawableProfileWeightedScore(model.CreateScoreInfo(Rulesets), Math.Pow(0.95, ItemsContainer.Count)); return new DrawableProfileWeightedScore(model.CreateScoreInfo(Rulesets), Math.Pow(0.95, drawableItemIndex++));
} }
} }
} }

View File

@ -0,0 +1,294 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
namespace osu.Game.Overlays.Profile
{
/// <summary>
/// Graph which is used in <see cref="UserProfileOverlay"/> to present changes in user statistics over time.
/// </summary>
/// <typeparam name="TKey">Type of data to be used for X-axis of the graph.</typeparam>
/// <typeparam name="TValue">Type of data to be used for Y-axis of the graph.</typeparam>
public abstract class UserGraph<TKey, TValue> : Container, IHasCustomTooltip
{
protected const float FADE_DURATION = 150;
private readonly UserLineGraph graph;
private KeyValuePair<TKey, TValue>[] data;
private int hoveredIndex = -1;
protected UserGraph()
{
Add(graph = new UserLineGraph
{
RelativeSizeAxes = Axes.Both,
Alpha = 0
});
graph.OnBallMove += i => hoveredIndex = i;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
graph.LineColour = colours.Yellow;
}
private float lastHoverPosition;
protected override bool OnHover(HoverEvent e)
{
if (data?.Length > 1)
{
graph.UpdateBallPosition(lastHoverPosition = e.MousePosition.X);
graph.ShowBar();
return true;
}
return base.OnHover(e);
}
protected override bool OnMouseMove(MouseMoveEvent e)
{
if (data?.Length > 1)
graph.UpdateBallPosition(e.MousePosition.X);
return base.OnMouseMove(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
graph.HideBar();
base.OnHoverLost(e);
}
/// <summary>
/// Set of values which will be used to create a graph.
/// </summary>
[CanBeNull]
protected KeyValuePair<TKey, TValue>[] Data
{
set
{
data = value;
redrawGraph();
}
}
private void redrawGraph()
{
hoveredIndex = -1;
if (data?.Length > 1)
{
graph.DefaultValueCount = data.Length;
graph.Values = data.Select(pair => GetDataPointHeight(pair.Value)).ToArray();
ShowGraph();
if (IsHovered)
graph.UpdateBallPosition(lastHoverPosition);
return;
}
HideGraph();
}
/// <summary>
/// Function used to convert <see cref="Data"/> point to it's Y-axis position on the graph.
/// </summary>
/// <param name="value">Value to convert.</param>
protected abstract float GetDataPointHeight(TValue value);
protected virtual void ShowGraph() => graph.FadeIn(FADE_DURATION, Easing.Out);
protected virtual void HideGraph() => graph.FadeOut(FADE_DURATION, Easing.Out);
public ITooltip GetCustomTooltip() => GetTooltip();
protected abstract UserGraphTooltip GetTooltip();
public object TooltipContent
{
get
{
if (data == null || hoveredIndex == -1)
return null;
var (key, value) = data[hoveredIndex];
return GetTooltipContent(key, value);
}
}
protected abstract object GetTooltipContent(TKey key, TValue value);
protected class UserLineGraph : LineGraph
{
private readonly CircularContainer movingBall;
private readonly Container bar;
private readonly Box ballBg;
private readonly Box line;
public Action<int> OnBallMove;
public UserLineGraph()
{
Add(bar = new Container
{
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
Alpha = 0,
RelativePositionAxes = Axes.Both,
Children = new Drawable[]
{
line = new Box
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
Width = 2,
},
movingBall = new CircularContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre,
Size = new Vector2(20),
Masking = true,
BorderThickness = 4,
RelativePositionAxes = Axes.Y,
Child = ballBg = new Box { RelativeSizeAxes = Axes.Both }
}
}
});
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider, OsuColour colours)
{
ballBg.Colour = colourProvider.Background5;
movingBall.BorderColour = line.Colour = colours.Yellow;
}
public void UpdateBallPosition(float mouseXPosition)
{
const int duration = 200;
int index = calculateIndex(mouseXPosition);
Vector2 position = calculateBallPosition(index);
movingBall.MoveToY(position.Y, duration, Easing.OutQuint);
bar.MoveToX(position.X, duration, Easing.OutQuint);
OnBallMove.Invoke(index);
}
public void ShowBar() => bar.FadeIn(FADE_DURATION);
public void HideBar() => bar.FadeOut(FADE_DURATION);
private int calculateIndex(float mouseXPosition) => (int)Math.Clamp(MathF.Round(mouseXPosition / DrawWidth * (DefaultValueCount - 1)), 0, DefaultValueCount - 1);
private Vector2 calculateBallPosition(int index)
{
float y = GetYPosition(Values.ElementAt(index));
return new Vector2(index / (float)(DefaultValueCount - 1), y);
}
}
protected abstract class UserGraphTooltip : VisibilityContainer, ITooltip
{
protected readonly OsuSpriteText Counter, BottomText;
private readonly Box background;
protected UserGraphTooltip(string tooltipCounterName)
{
AutoSizeAxes = Axes.Both;
Masking = true;
CornerRadius = 10;
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Padding = new MarginPadding(10),
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(3, 0),
Children = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
Text = tooltipCounterName
},
Counter = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
}
}
},
BottomText = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular),
}
}
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
// Temporary colour since it's currently impossible to change it without bugs (see https://github.com/ppy/osu-framework/issues/3231)
// If above is fixed, this should use OverlayColourProvider
background.Colour = colours.Gray1;
}
public abstract bool SetContent(object content);
private bool instantMove = true;
public void Move(Vector2 pos)
{
if (instantMove)
{
Position = pos;
instantMove = false;
}
else
this.MoveTo(pos, 200, Easing.OutQuint);
}
protected override void PopIn()
{
instantMove |= !IsPresent;
this.FadeIn(200, Easing.OutQuint);
}
protected override void PopOut() => this.FadeOut(200, Easing.OutQuint);
}
}
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -29,6 +30,14 @@ namespace osu.Game.Overlays.Settings.Sections
private readonly Bindable<SkinInfo> dropdownBindable = new Bindable<SkinInfo> { Default = SkinInfo.Default }; private readonly Bindable<SkinInfo> dropdownBindable = new Bindable<SkinInfo> { Default = SkinInfo.Default };
private readonly Bindable<int> configBindable = new Bindable<int>(); private readonly Bindable<int> configBindable = new Bindable<int>();
private static readonly SkinInfo random_skin_info = new SkinInfo
{
ID = SkinInfo.RANDOM_SKIN,
Name = "<Random Skin>",
};
private List<SkinInfo> skinItems;
[Resolved] [Resolved]
private SkinManager skins { get; set; } private SkinManager skins { get; set; }
@ -82,14 +91,37 @@ namespace osu.Game.Overlays.Settings.Sections
config.BindWith(OsuSetting.Skin, configBindable); config.BindWith(OsuSetting.Skin, configBindable);
skinDropdown.Current = dropdownBindable; skinDropdown.Current = dropdownBindable;
skinDropdown.Items = skins.GetAllUsableSkins().ToArray(); updateItems();
// Todo: This should not be necessary when OsuConfigManager is databased // Todo: This should not be necessary when OsuConfigManager is databased
if (skinDropdown.Items.All(s => s.ID != configBindable.Value)) if (skinDropdown.Items.All(s => s.ID != configBindable.Value))
configBindable.Value = 0; configBindable.Value = 0;
configBindable.BindValueChanged(id => dropdownBindable.Value = skinDropdown.Items.Single(s => s.ID == id.NewValue), true); configBindable.BindValueChanged(id => dropdownBindable.Value = skinDropdown.Items.Single(s => s.ID == id.NewValue), true);
dropdownBindable.BindValueChanged(skin => configBindable.Value = skin.NewValue.ID); dropdownBindable.BindValueChanged(skin =>
{
if (skin.NewValue == random_skin_info)
{
skins.SelectRandomSkin();
return;
}
configBindable.Value = skin.NewValue.ID;
});
}
private void updateItems()
{
skinItems = skins.GetAllUsableSkins();
// insert after lazer built-in skins
int firstNonDefault = skinItems.FindIndex(s => s.ID > 0);
if (firstNonDefault < 0)
firstNonDefault = skinItems.Count;
skinItems.Insert(firstNonDefault, random_skin_info);
skinDropdown.Items = skinItems;
} }
private void itemUpdated(ValueChangedEvent<WeakReference<SkinInfo>> weakItem) private void itemUpdated(ValueChangedEvent<WeakReference<SkinInfo>> weakItem)

View File

@ -290,7 +290,7 @@ namespace osu.Game.Rulesets.Scoring
/// </summary> /// </summary>
public virtual void PopulateScore(ScoreInfo score) public virtual void PopulateScore(ScoreInfo score)
{ {
score.TotalScore = (long)Math.Round(TotalScore.Value); score.TotalScore = (long)Math.Round(GetStandardisedScore());
score.Combo = Combo.Value; score.Combo = Combo.Value;
score.MaxCombo = HighestCombo.Value; score.MaxCombo = HighestCombo.Value;
score.Accuracy = Math.Round(Accuracy.Value, 4); score.Accuracy = Math.Round(Accuracy.Value, 4);

View File

@ -295,21 +295,15 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <returns>Whether a selection was performed.</returns> /// <returns>Whether a selection was performed.</returns>
private bool beginClickSelection(MouseButtonEvent e) private bool beginClickSelection(MouseButtonEvent e)
{ {
Debug.Assert(!clickSelectionBegan);
bool selectedPerformed = true;
foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren) foreach (SelectionBlueprint blueprint in SelectionBlueprints.AliveChildren)
{ {
if (blueprint.IsHovered) if (!blueprint.IsHovered) continue;
{
selectedPerformed &= SelectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); if (SelectionHandler.HandleSelectionRequested(blueprint, e))
clickSelectionBegan = true; return clickSelectionBegan = true;
break;
}
} }
return selectedPerformed; return false;
} }
/// <summary> /// <summary>

View File

@ -14,7 +14,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.States; using osu.Framework.Input.Events;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
@ -218,17 +218,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// Handle a blueprint requesting selection. /// Handle a blueprint requesting selection.
/// </summary> /// </summary>
/// <param name="blueprint">The blueprint.</param> /// <param name="blueprint">The blueprint.</param>
/// <param name="state">The input state at the point of selection.</param> /// <param name="e">The mouse event responsible for selection.</param>
/// <returns>Whether a selection was performed.</returns> /// <returns>Whether a selection was performed.</returns>
internal bool HandleSelectionRequested(SelectionBlueprint blueprint, InputState state) internal bool HandleSelectionRequested(SelectionBlueprint blueprint, MouseButtonEvent e)
{ {
if (state.Keyboard.ShiftPressed && state.Mouse.IsPressed(MouseButton.Right)) if (e.ShiftPressed && e.Button == MouseButton.Right)
{ {
handleQuickDeletion(blueprint); handleQuickDeletion(blueprint);
return false; return false;
} }
if (state.Keyboard.ControlPressed && state.Mouse.IsPressed(MouseButton.Left)) if (e.ControlPressed && e.Button == MouseButton.Left)
blueprint.ToggleSelection(); blueprint.ToggleSelection();
else else
ensureSelected(blueprint); ensureSelected(blueprint);

View File

@ -85,12 +85,13 @@ namespace osu.Game.Screens.Edit.Timing
{ {
textBox.Text = string.Empty; textBox.Text = string.Empty;
textBox.Current.Disabled = true; // cannot use textBox.Current.Disabled due to https://github.com/ppy/osu-framework/issues/3919
textBox.ReadOnly = true;
button.Enabled.Value = false; button.Enabled.Value = false;
return; return;
} }
textBox.Current.Disabled = false; textBox.ReadOnly = false;
button.Enabled.Value = true; button.Enabled.Value = true;
textBox.Text = $"{group.NewValue.Time:n0}"; textBox.Text = $"{group.NewValue.Time:n0}";

View File

@ -6,6 +6,7 @@ using osu.Framework.Screens;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Users;
namespace osu.Game.Screens namespace osu.Game.Screens
{ {
@ -43,6 +44,11 @@ namespace osu.Game.Screens
/// </summary> /// </summary>
IBindable<OverlayActivation> OverlayActivationMode { get; } IBindable<OverlayActivation> OverlayActivationMode { get; }
/// <summary>
/// The current <see cref="UserActivity"/> for this screen.
/// </summary>
IBindable<UserActivity> Activity { get; }
/// <summary> /// <summary>
/// The amount of parallax to be applied while this screen is displayed. /// The amount of parallax to be applied while this screen is displayed.
/// </summary> /// </summary>

View File

@ -14,6 +14,7 @@ using osu.Game.Online.Multiplayer;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Screens.Multi.Lounge.Components;
using osu.Game.Screens.Multi.Match; using osu.Game.Screens.Multi.Match;
using osu.Game.Users;
namespace osu.Game.Screens.Multi.Lounge namespace osu.Game.Screens.Multi.Lounge
{ {
@ -24,6 +25,8 @@ namespace osu.Game.Screens.Multi.Lounge
protected FilterControl Filter; protected FilterControl Filter;
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
private readonly Bindable<bool> initialRoomsReceived = new Bindable<bool>(); private readonly Bindable<bool> initialRoomsReceived = new Bindable<bool>();
private Container content; private Container content;

View File

@ -21,6 +21,7 @@ using osu.Game.Screens.Multi.Play;
using osu.Game.Screens.Multi.Ranking; using osu.Game.Screens.Multi.Ranking;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osu.Game.Users;
using Footer = osu.Game.Screens.Multi.Match.Components.Footer; using Footer = osu.Game.Screens.Multi.Match.Components.Footer;
namespace osu.Game.Screens.Multi.Match namespace osu.Game.Screens.Multi.Match
@ -60,6 +61,7 @@ namespace osu.Game.Screens.Multi.Match
public MatchSubScreen(Room room) public MatchSubScreen(Room room)
{ {
Title = room.RoomID.Value == null ? "New room" : room.Name.Value; Title = room.RoomID.Value == null ? "New room" : room.Name.Value;
Activity.Value = new UserActivity.InLobby(room);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -24,6 +24,7 @@ using osu.Game.Screens.Multi.Lounge;
using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Screens.Multi.Lounge.Components;
using osu.Game.Screens.Multi.Match; using osu.Game.Screens.Multi.Match;
using osu.Game.Screens.Multi.Match.Components; using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users;
using osuTK; using osuTK;
namespace osu.Game.Screens.Multi namespace osu.Game.Screens.Multi
@ -140,10 +141,10 @@ namespace osu.Game.Screens.Multi
} }
}; };
screenStack.Push(loungeSubScreen = new LoungeSubScreen());
screenStack.ScreenPushed += screenPushed; screenStack.ScreenPushed += screenPushed;
screenStack.ScreenExited += screenExited; screenStack.ScreenExited += screenExited;
screenStack.Push(loungeSubScreen = new LoungeSubScreen());
} }
private readonly IBindable<APIState> apiState = new Bindable<APIState>(); private readonly IBindable<APIState> apiState = new Bindable<APIState>();
@ -311,18 +312,18 @@ namespace osu.Game.Screens.Multi
private void screenPushed(IScreen lastScreen, IScreen newScreen) private void screenPushed(IScreen lastScreen, IScreen newScreen)
{ {
subScreenChanged(newScreen); subScreenChanged(lastScreen, newScreen);
} }
private void screenExited(IScreen lastScreen, IScreen newScreen) private void screenExited(IScreen lastScreen, IScreen newScreen)
{ {
subScreenChanged(newScreen); subScreenChanged(lastScreen, newScreen);
if (screenStack.CurrentScreen == null && this.IsCurrentScreen()) if (screenStack.CurrentScreen == null && this.IsCurrentScreen())
this.Exit(); this.Exit();
} }
private void subScreenChanged(IScreen newScreen) private void subScreenChanged(IScreen lastScreen, IScreen newScreen)
{ {
switch (newScreen) switch (newScreen)
{ {
@ -337,6 +338,12 @@ namespace osu.Game.Screens.Multi
break; break;
} }
if (lastScreen is IOsuScreen lastOsuScreen)
Activity.UnbindFrom(lastOsuScreen.Activity);
if (newScreen is IOsuScreen newOsuScreen)
((IBindable<UserActivity>)Activity).BindTo(newOsuScreen.Activity);
updatePollingRate(isIdle.Value); updatePollingRate(isIdle.Value);
createButton.FadeTo(newScreen is LoungeSubScreen ? 1 : 0, 200); createButton.FadeTo(newScreen is LoungeSubScreen ? 1 : 0, 200);

View File

@ -14,7 +14,6 @@ using osu.Game.Rulesets;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Users; using osu.Game.Users;
using osu.Game.Online.API;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
namespace osu.Game.Screens namespace osu.Game.Screens
@ -57,28 +56,18 @@ namespace osu.Game.Screens
protected new OsuGameBase Game => base.Game as OsuGameBase; protected new OsuGameBase Game => base.Game as OsuGameBase;
/// <summary> /// <summary>
/// The <see cref="UserActivity"/> to set the user's activity automatically to when this screen is entered /// The <see cref="UserActivity"/> to set the user's activity automatically to when this screen is entered.
/// <para>This <see cref="Activity"/> will be automatically set to <see cref="InitialActivity"/> for this screen on entering unless /// <para>This <see cref="Activity"/> will be automatically set to <see cref="InitialActivity"/> for this screen on entering for the first time
/// <see cref="Activity"/> is manually set before.</para> /// unless <see cref="Activity"/> is manually set before.</para>
/// </summary> /// </summary>
protected virtual UserActivity InitialActivity => null; protected virtual UserActivity InitialActivity => null;
private UserActivity activity;
/// <summary> /// <summary>
/// The current <see cref="UserActivity"/> for this screen. /// The current <see cref="UserActivity"/> for this screen.
/// </summary> /// </summary>
protected UserActivity Activity protected readonly Bindable<UserActivity> Activity = new Bindable<UserActivity>();
{
get => activity;
set
{
if (value == activity) return;
activity = value; IBindable<UserActivity> IOsuScreen.Activity => Activity;
updateActivity();
}
}
/// <summary> /// <summary>
/// Whether to disallow changes to game-wise Beatmap/Ruleset bindables for this screen (and all children). /// Whether to disallow changes to game-wise Beatmap/Ruleset bindables for this screen (and all children).
@ -135,9 +124,6 @@ namespace osu.Game.Screens
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private OsuLogo logo { get; set; } private OsuLogo logo { get; set; }
[Resolved(canBeNull: true)]
private IAPIProvider api { get; set; }
protected OsuScreen() protected OsuScreen()
{ {
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
@ -150,6 +136,8 @@ namespace osu.Game.Screens
private void load(OsuGame osu, AudioManager audio) private void load(OsuGame osu, AudioManager audio)
{ {
sampleExit = audio.Samples.Get(@"UI/screen-back"); sampleExit = audio.Samples.Get(@"UI/screen-back");
Activity.Value ??= InitialActivity;
} }
public override void OnResuming(IScreen last) public override void OnResuming(IScreen last)
@ -158,8 +146,6 @@ namespace osu.Game.Screens
sampleExit?.Play(); sampleExit?.Play();
applyArrivingDefaults(true); applyArrivingDefaults(true);
updateActivity();
base.OnResuming(last); base.OnResuming(last);
} }
@ -176,9 +162,6 @@ namespace osu.Game.Screens
backgroundStack?.Push(localBackground = CreateBackground()); backgroundStack?.Push(localBackground = CreateBackground());
if (activity == null)
Activity = InitialActivity;
base.OnEntering(last); base.OnEntering(last);
} }
@ -196,12 +179,6 @@ namespace osu.Game.Screens
return false; return false;
} }
private void updateActivity()
{
if (api != null)
api.Activity.Value = activity;
}
/// <summary> /// <summary>
/// Fired when this screen was entered or resumed and the logo state is required to be adjusted. /// Fired when this screen was entered or resumed and the logo state is required to be adjusted.
/// </summary> /// </summary>

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq; using System.Linq;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -61,7 +62,9 @@ namespace osu.Game.Screens.Play
[Resolved] [Resolved]
private RulesetStore rulesets { get; set; } private RulesetStore rulesets { get; set; }
private Replay replay; private Score score;
private readonly object scoreLock = new object();
private Container beatmapPanelContainer; private Container beatmapPanelContainer;
@ -198,14 +201,22 @@ namespace osu.Game.Screens.Play
private void userSentFrames(int userId, FrameDataBundle data) private void userSentFrames(int userId, FrameDataBundle data)
{ {
// this is not scheduled as it handles propagation of frames even when in a child screen (at which point we are not alive).
// probably not the safest way to handle this.
if (userId != targetUser.Id) if (userId != targetUser.Id)
return; return;
lock (scoreLock)
{
// this should never happen as the server sends the user's state on watching, // this should never happen as the server sends the user's state on watching,
// but is here as a safety measure. // but is here as a safety measure.
if (replay == null) if (score == null)
return; return;
// rulesetInstance should be guaranteed to be in sync with the score via scoreLock.
Debug.Assert(rulesetInstance != null && rulesetInstance.RulesetInfo.Equals(score.ScoreInfo.Ruleset));
foreach (var frame in data.Frames) foreach (var frame in data.Frames)
{ {
IConvertibleReplayFrame convertibleFrame = rulesetInstance.CreateConvertibleReplayFrame(); IConvertibleReplayFrame convertibleFrame = rulesetInstance.CreateConvertibleReplayFrame();
@ -214,7 +225,8 @@ namespace osu.Game.Screens.Play
var convertedFrame = (ReplayFrame)convertibleFrame; var convertedFrame = (ReplayFrame)convertibleFrame;
convertedFrame.Time = frame.Time; convertedFrame.Time = frame.Time;
replay.Frames.Add(convertedFrame); score.Replay.Frames.Add(convertedFrame);
}
} }
} }
@ -247,10 +259,13 @@ namespace osu.Game.Screens.Play
if (userId != targetUser.Id) if (userId != targetUser.Id)
return; return;
if (replay != null) lock (scoreLock)
{ {
replay.HasReceivedAllFrames = true; if (score != null)
replay = null; {
score.Replay.HasReceivedAllFrames = true;
score = null;
}
} }
Schedule(clearDisplay); Schedule(clearDisplay);
@ -283,14 +298,18 @@ namespace osu.Game.Screens.Play
return; return;
} }
replay ??= new Replay { HasReceivedAllFrames = false }; lock (scoreLock)
{
var scoreInfo = new ScoreInfo score = new Score
{
ScoreInfo = new ScoreInfo
{ {
Beatmap = resolvedBeatmap, Beatmap = resolvedBeatmap,
User = targetUser, User = targetUser,
Mods = state.Mods.Select(m => m.ToMod(resolvedRuleset)).ToArray(), Mods = state.Mods.Select(m => m.ToMod(resolvedRuleset)).ToArray(),
Ruleset = resolvedRuleset.RulesetInfo, Ruleset = resolvedRuleset.RulesetInfo,
},
Replay = new Replay { HasReceivedAllFrames = false },
}; };
ruleset.Value = resolvedRuleset.RulesetInfo; ruleset.Value = resolvedRuleset.RulesetInfo;
@ -299,11 +318,8 @@ namespace osu.Game.Screens.Play
beatmap.Value = beatmaps.GetWorkingBeatmap(resolvedBeatmap); beatmap.Value = beatmaps.GetWorkingBeatmap(resolvedBeatmap);
watchButton.Enabled.Value = true; watchButton.Enabled.Value = true;
this.Push(new SpectatorPlayerLoader(new Score this.Push(new SpectatorPlayerLoader(score));
{ }
ScoreInfo = scoreInfo,
Replay = replay,
}));
} }
private void showBeatmapPanel(SpectatorState state) private void showBeatmapPanel(SpectatorState state)

View File

@ -25,7 +25,7 @@ namespace osu.Game.Skinning
public static SkinInfo Info { get; } = new SkinInfo public static SkinInfo Info { get; } = new SkinInfo
{ {
ID = -1, // this is temporary until database storage is decided upon. ID = SkinInfo.CLASSIC_SKIN, // this is temporary until database storage is decided upon.
Name = "osu!classic", Name = "osu!classic",
Creator = "team osu!" Creator = "team osu!"
}; };

View File

@ -80,7 +80,7 @@ namespace osu.Game.Skinning
Math.Clamp(Clock.ElapsedFrameTime, 0, 200), Math.Clamp(Clock.ElapsedFrameTime, 0, 200),
fill.Width, (float)Current.Value * maxFillWidth, 0, 200, Easing.OutQuint); fill.Width, (float)Current.Value * maxFillWidth, 0, 200, Easing.OutQuint);
marker.Position = fill.Position + new Vector2(fill.DrawWidth, fill.DrawHeight / 2); marker.Position = fill.Position + new Vector2(fill.DrawWidth, isNewStyle ? fill.DrawHeight / 2 : 0);
} }
public void Flash(JudgementResult result) => marker.Flash(result); public void Flash(JudgementResult result) => marker.Flash(result);

View File

@ -10,6 +10,10 @@ namespace osu.Game.Skinning
{ {
public class SkinInfo : IHasFiles<SkinFileInfo>, IEquatable<SkinInfo>, IHasPrimaryKey, ISoftDelete public class SkinInfo : IHasFiles<SkinFileInfo>, IEquatable<SkinInfo>, IHasPrimaryKey, ISoftDelete
{ {
internal const int DEFAULT_SKIN = 0;
internal const int CLASSIC_SKIN = -1;
internal const int RANDOM_SKIN = -2;
public int ID { get; set; } public int ID { get; set; }
public string Name { get; set; } public string Name { get; set; }
@ -26,6 +30,7 @@ namespace osu.Game.Skinning
public static SkinInfo Default { get; } = new SkinInfo public static SkinInfo Default { get; } = new SkinInfo
{ {
ID = DEFAULT_SKIN,
Name = "osu!lazer", Name = "osu!lazer",
Creator = "team osu!" Creator = "team osu!"
}; };

View File

@ -19,6 +19,7 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores; using osu.Framework.IO.Stores;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.IO.Archives; using osu.Game.IO.Archives;
@ -72,7 +73,7 @@ namespace osu.Game.Skinning
/// <summary> /// <summary>
/// Returns a list of all usable <see cref="SkinInfo"/>s. Includes the special default skin plus all skins from <see cref="GetAllUserSkins"/>. /// Returns a list of all usable <see cref="SkinInfo"/>s. Includes the special default skin plus all skins from <see cref="GetAllUserSkins"/>.
/// </summary> /// </summary>
/// <returns>A list of available <see cref="SkinInfo"/>.</returns> /// <returns>A newly allocated list of available <see cref="SkinInfo"/>.</returns>
public List<SkinInfo> GetAllUsableSkins() public List<SkinInfo> GetAllUsableSkins()
{ {
var userSkins = GetAllUserSkins(); var userSkins = GetAllUserSkins();
@ -84,9 +85,23 @@ namespace osu.Game.Skinning
/// <summary> /// <summary>
/// Returns a list of all usable <see cref="SkinInfo"/>s that have been loaded by the user. /// Returns a list of all usable <see cref="SkinInfo"/>s that have been loaded by the user.
/// </summary> /// </summary>
/// <returns>A list of available <see cref="SkinInfo"/>.</returns> /// <returns>A newly allocated list of available <see cref="SkinInfo"/>.</returns>
public List<SkinInfo> GetAllUserSkins() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); public List<SkinInfo> GetAllUserSkins() => ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList();
public void SelectRandomSkin()
{
// choose from only user skins, removing the current selection to ensure a new one is chosen.
var randomChoices = GetAllUsableSkins().Where(s => s.ID > 0 && s.ID != CurrentSkinInfo.Value.ID).ToArray();
if (randomChoices.Length == 0)
{
CurrentSkinInfo.Value = SkinInfo.Default;
return;
}
CurrentSkinInfo.Value = randomChoices.ElementAt(RNG.Next(0, randomChoices.Length));
}
protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name }; protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name };
private const string unknown_creator_string = "Unknown"; private const string unknown_creator_string = "Unknown";

View File

@ -115,11 +115,11 @@ namespace osu.Game.Storyboards.Drawables
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textureStore, Storyboard storyboard) private void load(TextureStore textureStore, Storyboard storyboard)
{ {
for (int frame = 0; frame < Animation.FrameCount; frame++) for (int frameIndex = 0; frameIndex < Animation.FrameCount; frameIndex++)
{ {
string framePath = Animation.Path.Replace(".", frame + "."); string framePath = Animation.Path.Replace(".", frameIndex + ".");
Drawable frame = storyboard.CreateSpriteFromResourcePath(framePath, textureStore) ?? Empty();
AddFrame(storyboard.CreateSpriteFromResourcePath(framePath, textureStore), Animation.FrameDelay); AddFrame(frame, Animation.FrameDelay);
} }
Animation.ApplyTransforms(this); Animation.ApplyTransforms(this);

View File

@ -186,7 +186,7 @@ namespace osu.Game.Users
} }
} }
[JsonProperty(@"rankHistory")] [JsonProperty(@"rank_history")]
private RankHistoryData rankHistory private RankHistoryData rankHistory
{ {
set => statistics.RankHistory = value; set => statistics.RankHistory = value;

View File

@ -3,6 +3,7 @@
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osuTK.Graphics; using osuTK.Graphics;
@ -61,9 +62,21 @@ namespace osu.Game.Users
public override string Status => @"Spectating a game"; public override string Status => @"Spectating a game";
} }
public class SearchingForLobby : UserActivity
{
public override string Status => @"Looking for a lobby";
}
public class InLobby : UserActivity public class InLobby : UserActivity
{ {
public override string Status => @"In a multiplayer lobby"; public override string Status => @"In a multiplayer lobby";
public readonly Room Room;
public InLobby(Room room)
{
Room = room;
}
} }
} }
} }