1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-15 12:42:54 +08:00

Merge remote-tracking branch 'refs/remotes/ppy/master' into accuracy-bar

This commit is contained in:
Andrei Zavatski 2019-08-18 01:14:27 +03:00
commit 94dbd6386f
80 changed files with 2307 additions and 901 deletions

View File

@ -6,7 +6,7 @@ GEM
public_suffix (>= 2.0.2, < 4.0)
atomos (0.1.3)
babosa (1.0.2)
claide (1.0.2)
claide (1.0.3)
colored (1.2)
colored2 (3.1.2)
commander-fastlane (4.4.6)
@ -14,11 +14,11 @@ GEM
declarative (0.0.10)
declarative-option (0.1.0)
digest-crc (0.4.1)
domain_name (0.5.20180417)
domain_name (0.5.20190701)
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.1)
dotenv (2.7.5)
emoji_regex (1.0.1)
excon (0.62.0)
excon (0.66.0)
faraday (0.15.4)
multipart-post (>= 1.2, < 3)
faraday-cookie_jar (0.0.6)
@ -27,7 +27,7 @@ GEM
faraday_middleware (0.13.1)
faraday (>= 0.7.4, < 1.0)
fastimage (2.1.5)
fastlane (2.117.0)
fastlane (2.129.0)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.3, < 3.0.0)
babosa (>= 1.0.2, < 2.0.0)
@ -46,8 +46,8 @@ GEM
google-cloud-storage (>= 1.15.0, < 2.0.0)
highline (>= 1.7.2, < 2.0.0)
json (< 3.0.0)
mini_magick (~> 4.5.1)
multi_json
jwt (~> 2.1.0)
mini_magick (>= 4.9.4, < 5.0.0)
multi_xml (~> 0.5)
multipart-post (~> 2.0.0)
plist (>= 3.1.0, < 4.0.0)
@ -56,15 +56,15 @@ GEM
security (= 0.1.3)
simctl (~> 1.6.3)
slack-notifier (>= 2.0.0, < 3.0.0)
terminal-notifier (>= 1.6.2, < 2.0.0)
terminal-notifier (>= 2.0.0, < 3.0.0)
terminal-table (>= 1.4.5, < 2.0.0)
tty-screen (>= 0.6.3, < 1.0.0)
tty-spinner (>= 0.8.0, < 1.0.0)
word_wrap (~> 1.0.0)
xcodeproj (>= 1.6.0, < 2.0.0)
xcodeproj (>= 1.8.1, < 2.0.0)
xcpretty (~> 0.3.0)
xcpretty-travis-formatter (>= 0.0.3)
fastlane-plugin-clean_testflight_testers (0.2.0)
fastlane-plugin-clean_testflight_testers (0.3.0)
fastlane-plugin-souyuz (0.8.1)
souyuz (>= 0.8.1)
fastlane-plugin-xamarin (0.6.3)
@ -79,7 +79,7 @@ GEM
signet (~> 0.9)
google-cloud-core (1.3.0)
google-cloud-env (~> 1.0)
google-cloud-env (1.0.5)
google-cloud-env (1.2.0)
faraday (~> 0.11)
google-cloud-storage (1.16.0)
digest-crc (~> 0.4)
@ -102,17 +102,17 @@ GEM
memoist (0.16.0)
mime-types (3.2.2)
mime-types-data (~> 3.2015)
mime-types-data (3.2018.0812)
mini_magick (4.5.1)
mime-types-data (3.2019.0331)
mini_magick (4.9.5)
mini_portile2 (2.4.0)
multi_json (1.13.1)
multi_xml (0.6.0)
multipart-post (2.0.0)
nanaimo (0.2.6)
naturally (2.2.0)
nokogiri (1.10.1)
nokogiri (1.10.4)
mini_portile2 (~> 2.4.0)
os (1.0.0)
os (1.0.1)
plist (3.5.0)
public_suffix (2.0.5)
representable (3.0.4)
@ -121,7 +121,7 @@ GEM
uber (< 0.2.0)
retriable (3.1.2)
rouge (2.0.7)
rubyzip (1.2.2)
rubyzip (1.2.3)
security (0.1.3)
signet (0.11.0)
addressable (~> 2.3)
@ -136,20 +136,20 @@ GEM
fastlane (>= 2.29.0)
highline (~> 1.7)
nokogiri (~> 1.7)
terminal-notifier (1.8.0)
terminal-notifier (2.0.0)
terminal-table (1.8.0)
unicode-display_width (~> 1.1, >= 1.1.1)
tty-cursor (0.6.1)
tty-screen (0.6.5)
tty-spinner (0.9.0)
tty-cursor (~> 0.6.0)
tty-cursor (0.7.0)
tty-screen (0.7.0)
tty-spinner (0.9.1)
tty-cursor (~> 0.7)
uber (0.1.0)
unf (0.1.4)
unf_ext
unf_ext (0.0.7.5)
unicode-display_width (1.4.1)
unf_ext (0.0.7.6)
unicode-display_width (1.6.0)
word_wrap (1.0.0)
xcodeproj (1.8.1)
xcodeproj (1.12.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)

View File

@ -19,8 +19,9 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
## Requirements
- A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed.
- When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
- Note that there are **[additional requirements for Windows 7 and Windows 8.1](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** which you may need to manually install if your operating system is not up-to-date.
## Running osu!

View File

@ -1,22 +1,6 @@
update_fastlane
default_platform(:ios)
platform :ios do
lane :testflight_prune_dry do
clean_testflight_testers(days_of_inactivity:45, dry_run: true)
end
# Specify a custom number for what's "inactive"
lane :testflight_prune do
clean_testflight_testers(days_of_inactivity: 45) # 120 days, so about 4 months
end
lane :update_version do |options|
options[:plist_path] = '../osu.iOS/Info.plist'
app_version(options)
end
desc 'Deploy to testflight'
lane :beta do |options|
update_version(options)
@ -62,4 +46,17 @@ platform :ios do
match(options)
end
lane :update_version do |options|
options[:plist_path] = '../osu.iOS/Info.plist'
app_version(options)
end
lane :testflight_prune_dry do
clean_testflight_testers(days_of_inactivity:45, dry_run: true)
end
lane :testflight_prune do
clean_testflight_testers(days_of_inactivity: 45)
end
end

View File

@ -16,21 +16,6 @@ or alternatively using `brew cask install fastlane`
# Available Actions
## iOS
### ios testflight_prune_dry
```
fastlane ios testflight_prune_dry
```
### ios testflight_prune
```
fastlane ios testflight_prune
```
### ios update_version
```
fastlane ios update_version
```
### ios beta
```
fastlane ios beta
@ -46,6 +31,21 @@ Compile the project
fastlane ios provision
```
Install provisioning profiles using match
### ios update_version
```
fastlane ios update_version
```
### ios testflight_prune_dry
```
fastlane ios testflight_prune_dry
```
### ios testflight_prune
```
fastlane ios testflight_prune
```
----

View File

@ -49,9 +49,7 @@
<None Include="$(MSBuildThisFileDirectory)\osu.licenseheader">
<Link>osu.licenseheader</Link>
</None>
<AndroidNativeLibrary Include="$(MSBuildThisFileDirectory)\osu.Android\lib\**\*.so">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</AndroidNativeLibrary>
<AndroidNativeLibrary Include="$(OutputPath)\**\*.so" />
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
@ -62,7 +60,7 @@
<Reference Include="Java.Interop" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.731.1" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.809.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.809.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.816.0" />
</ItemGroup>
</Project>

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -113,13 +113,14 @@ namespace osu.Desktop.Overlays
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, ChangelogOverlay changelog)
private void load(OsuColour colours, ChangelogOverlay changelog, NotificationOverlay notificationOverlay)
{
Icon = FontAwesome.Solid.CheckSquare;
IconBackgound.Colour = colours.BlueDark;
Activated = delegate
{
notificationOverlay.Hide();
changelog.ShowBuild(OsuGameBase.CLIENT_STREAM_NAME, version);
return true;
};

View File

@ -13,14 +13,6 @@
<ItemGroup>
<None Include="Info.plist" />
<None Include="Entitlements.plist" />
<None Include="..\osu.iOS\libbass.a">
<Link>libbass.a</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\osu.iOS\libbass_fx.a">
<Link>libbass_fx.a</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<LinkDescription Include="..\osu.iOS\Linker.xml">
<Link>Linker.xml</Link>
</LinkDescription>

View File

@ -13,14 +13,6 @@
<ItemGroup>
<None Include="Info.plist" />
<None Include="Entitlements.plist" />
<None Include="..\osu.iOS\libbass.a">
<Link>libbass.a</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\osu.iOS\libbass_fx.a">
<Link>libbass_fx.a</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<LinkDescription Include="..\osu.iOS\Linker.xml">
<Link>Linker.xml</Link>
</LinkDescription>

View File

@ -13,14 +13,6 @@
<ItemGroup>
<None Include="Info.plist" />
<None Include="Entitlements.plist" />
<None Include="..\osu.iOS\libbass.a">
<Link>libbass.a</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\osu.iOS\libbass_fx.a">
<Link>libbass_fx.a</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<LinkDescription Include="..\osu.iOS\Linker.xml">
<Link>Linker.xml</Link>
</LinkDescription>

View File

@ -2,13 +2,19 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.StateChanges;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu.Mods
{
public class OsuModAutopilot : Mod
public class OsuModAutopilot : Mod, IApplicableFailOverride, IUpdatableByPlayfield, IApplicableToDrawableRuleset<OsuHitObject>
{
public override string Name => "Autopilot";
public override string Acronym => "AP";
@ -17,5 +23,40 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => @"Automatic cursor movement - just follow the rhythm.";
public override double ScoreMultiplier => 1;
public override Type[] IncompatibleMods => new[] { typeof(OsuModSpunOut), typeof(ModRelax), typeof(ModSuddenDeath), typeof(ModNoFail), typeof(ModAutoplay) };
public bool AllowFail => false;
private OsuInputManager inputManager;
private List<OsuReplayFrame> replayFrames;
private int currentFrame;
public void Update(Playfield playfield)
{
if (currentFrame == replayFrames.Count - 1) return;
double time = playfield.Time.Current;
// Very naive implementation of autopilot based on proximity to replay frames.
// TODO: this needs to be based on user interactions to better match stable (pausing until judgement is registered).
if (Math.Abs(replayFrames[currentFrame + 1].Time - time) <= Math.Abs(replayFrames[currentFrame].Time - time))
{
currentFrame++;
new MousePositionAbsoluteInput { Position = playfield.ToScreenSpace(replayFrames[currentFrame].Position) }.Apply(inputManager.CurrentState, inputManager);
}
// TODO: Implement the functionality to automatically spin spinners
}
public void ApplyToDrawableRuleset(DrawableRuleset<OsuHitObject> drawableRuleset)
{
// Grab the input manager to disable the user's cursor, and for future use
inputManager = (OsuInputManager)drawableRuleset.KeyBindingInputManager;
inputManager.AllowUserCursorMovement = false;
// Generate the replay frames the cursor should follow
replayFrames = new OsuAutoGenerator(drawableRuleset.Beatmap).Generate().Frames.Cast<OsuReplayFrame>().ToList();
}
}
}

View File

@ -18,6 +18,12 @@ namespace osu.Game.Rulesets.Osu
set => ((OsuKeyBindingContainer)KeyBindingContainer).AllowUserPresses = value;
}
/// <summary>
/// Whether the user's cursor movement events should be accepted.
/// Can be used to block only movement while still accepting button input.
/// </summary>
public bool AllowUserCursorMovement { get; set; } = true;
protected override RulesetKeyBindingContainer CreateKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
=> new OsuKeyBindingContainer(ruleset, variant, unique);
@ -26,6 +32,13 @@ namespace osu.Game.Rulesets.Osu
{
}
protected override bool Handle(UIEvent e)
{
if (e is MouseMoveEvent && !AllowUserCursorMovement) return false;
return base.Handle(e);
}
private class OsuKeyBindingContainer : RulesetKeyBindingContainer
{
public bool AllowUserPresses = true;

View File

@ -13,14 +13,6 @@
<ItemGroup>
<None Include="Info.plist" />
<None Include="Entitlements.plist" />
<None Include="..\osu.iOS\libbass.a">
<Link>libbass.a</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\osu.iOS\libbass_fx.a">
<Link>libbass_fx.a</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<LinkDescription Include="..\osu.iOS\Linker.xml">
<Link>Linker.xml</Link>
</LinkDescription>

View File

@ -13,14 +13,6 @@
<ItemGroup>
<None Include="Info.plist" />
<None Include="Entitlements.plist" />
<None Include="..\osu.iOS\libbass.a">
<Link>libbass.a</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="..\osu.iOS\libbass_fx.a">
<Link>libbass_fx.a</Link>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<LinkDescription Include="..\osu.iOS\Linker.xml">
<Link>Linker.xml</Link>
</LinkDescription>

View File

@ -119,14 +119,14 @@ namespace osu.Game.Tests.Visual.Background
{
performFullSetup();
createFakeStoryboard();
AddStep("Storyboard Enabled", () =>
AddStep("Enable Storyboard", () =>
{
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
waitForDim();
AddAssert("Background is invisible, storyboard is visible", () => songSelect.IsBackgroundInvisible() && player.IsStoryboardVisible);
AddStep("Storyboard Disabled", () =>
AddStep("Disable Storyboard", () =>
{
player.ReplacesBackground.Value = false;
player.StoryboardEnabled.Value = false;
@ -149,22 +149,44 @@ namespace osu.Game.Tests.Visual.Background
}
/// <summary>
/// Check if the <see cref="UserDimContainer"/> is properly accepting user-defined visual changes at all.
/// Ensure <see cref="UserDimContainer"/> is properly accepting user-defined visual changes for a background.
/// </summary>
[Test]
public void DisableUserDimTest()
public void DisableUserDimBackgroundTest()
{
performFullSetup();
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
AddStep("EnableUserDim disabled", () => songSelect.DimEnabled.Value = false);
AddStep("Enable user dim", () => songSelect.DimEnabled.Value = false);
waitForDim();
AddAssert("Screen is undimmed and user blur removed", () => songSelect.IsBackgroundUndimmed() && songSelect.IsUserBlurDisabled());
AddStep("EnableUserDim enabled", () => songSelect.DimEnabled.Value = true);
AddStep("Disable user dim", () => songSelect.DimEnabled.Value = true);
waitForDim();
AddAssert("Screen is dimmed and blur applied", () => songSelect.IsBackgroundDimmed() && songSelect.IsUserBlurApplied());
}
/// <summary>
/// Ensure <see cref="UserDimContainer"/> is properly accepting user-defined visual changes for a storyboard.
/// </summary>
[Test]
public void DisableUserDimStoryboardTest()
{
performFullSetup();
createFakeStoryboard();
AddStep("Enable Storyboard", () =>
{
player.ReplacesBackground.Value = true;
player.StoryboardEnabled.Value = true;
});
AddStep("Enable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = true);
AddStep("Set dim level to 1", () => songSelect.DimLevel.Value = 1f);
waitForDim();
AddAssert("Storyboard is invisible", () => !player.IsStoryboardVisible);
AddStep("Disable user dim", () => player.DimmableStoryboard.EnableUserDim.Value = false);
waitForDim();
AddAssert("Storyboard is visible", () => player.IsStoryboardVisible);
}
/// <summary>
/// Check if the visual settings container retains dim and blur when pausing
/// </summary>

View File

@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Gameplay
{
private bool exitAction;
protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms
[BackgroundDependencyLoader]
private void load()
{

View File

@ -21,32 +21,38 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly Container<DrawableStoryboard> storyboardContainer;
private DrawableStoryboard storyboard;
[Cached]
private MusicController musicController = new MusicController();
public TestSceneStoryboard()
{
Clock = new FramedClock();
Add(new Container
AddRange(new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
musicController,
new Container
{
new Box
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
storyboardContainer = new Container<DrawableStoryboard>
{
RelativeSizeAxes = Axes.Both,
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
},
storyboardContainer = new Container<DrawableStoryboard>
{
RelativeSizeAxes = Axes.Both,
},
},
},
});
Add(new MusicController
{
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
State = { Value = Visibility.Visible },
new NowPlayingOverlay
{
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
State = { Value = Visibility.Visible },
}
});
AddStep("Restart", restart);

View File

@ -0,0 +1,15 @@
// 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 NUnit.Framework;
using osu.Framework.Screens;
using osu.Game.Screens.Menu;
namespace osu.Game.Tests.Visual.Menus
{
[TestFixture]
public class TestSceneIntroTriangles : IntroTestScene
{
protected override IScreen CreateScreen() => new IntroTriangles();
}
}

View File

@ -0,0 +1,53 @@
// 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 osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Match.Components;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Game.Audio;
using osu.Framework.Allocation;
namespace osu.Game.Tests.Visual.Multiplayer
{
[Cached(typeof(IPreviewTrackOwner))]
public class TestSceneMatchBeatmapPanel : MultiplayerTestScene, IPreviewTrackOwner
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(MatchBeatmapPanel)
};
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; }
public TestSceneMatchBeatmapPanel()
{
Add(new MatchBeatmapPanel
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1763072 } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2101557 } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1973466 } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 2109801 } });
Room.Playlist.Add(new PlaylistItem { Beatmap = new BeatmapInfo { OnlineBeatmapID = 1922035 } });
}
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("Select random beatmap", () =>
{
Room.CurrentItem.Value = Room.Playlist[RNG.Next(Room.Playlist.Count)];
previewTrackManager.StopAnyPlaying(this);
});
}
}
}

View File

@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
typeof(Info),
typeof(HeaderButton),
typeof(ReadyButton),
typeof(ViewBeatmapButton)
typeof(MatchBeatmapPanel)
};
[BackgroundDependencyLoader]

View File

@ -0,0 +1,36 @@
// 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 osu.Game.Overlays.BeatmapSet;
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Bindables;
using osu.Game.Screens.Select.Leaderboards;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneLeaderboardScopeSelector : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(LeaderboardScopeSelector),
};
public TestSceneLeaderboardScopeSelector()
{
Bindable<BeatmapLeaderboardScope> scope = new Bindable<BeatmapLeaderboardScope>();
Add(new LeaderboardScopeSelector
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Current = { BindTarget = scope }
});
AddStep(@"Select global", () => scope.Value = BeatmapLeaderboardScope.Global);
AddStep(@"Select country", () => scope.Value = BeatmapLeaderboardScope.Country);
AddStep(@"Select friend", () => scope.Value = BeatmapLeaderboardScope.Friend);
}
}
}

View File

@ -0,0 +1,69 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public class TestSceneUserProfilePreviousUsernames : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(PreviousUsernames)
};
[Resolved]
private IAPIProvider api { get; set; }
private readonly Bindable<User> user = new Bindable<User>();
public TestSceneUserProfilePreviousUsernames()
{
Child = new PreviousUsernames
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
User = { BindTarget = user },
};
User[] users =
{
new User { PreviousUsernames = new[] { "username1" } },
new User { PreviousUsernames = new[] { "longusername", "longerusername" } },
new User { PreviousUsernames = new[] { "test", "angelsim", "verylongusername" } },
new User { PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" } },
new User { PreviousUsernames = new string[0] },
null
};
AddStep("single username", () => user.Value = users[0]);
AddStep("two usernames", () => user.Value = users[1]);
AddStep("three usernames", () => user.Value = users[2]);
AddStep("four usernames", () => user.Value = users[3]);
AddStep("no username", () => user.Value = users[4]);
AddStep("null user", () => user.Value = users[5]);
}
protected override void LoadComplete()
{
base.LoadComplete();
AddStep("online user (Angelsim)", () =>
{
var request = new GetUserRequest(1777162);
request.Success += user => this.user.Value = user;
api.Queue(request);
});
}
}
}

View File

@ -3,6 +3,7 @@
using System;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@ -22,30 +23,36 @@ namespace osu.Game.Tests.Visual.UserInterface
[TestFixture]
public class TestSceneBeatSyncedContainer : OsuTestScene
{
private readonly MusicController mc;
private readonly NowPlayingOverlay np;
[Cached]
private MusicController musicController = new MusicController();
public TestSceneBeatSyncedContainer()
{
Clock = new FramedClock();
Clock.ProcessFrame();
Add(new BeatContainer
AddRange(new Drawable[]
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
});
Add(mc = new MusicController
{
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
musicController,
new BeatContainer
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
},
np = new NowPlayingOverlay
{
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
}
});
}
protected override void LoadComplete()
{
base.LoadComplete();
mc.ToggleVisibility();
np.ToggleVisibility();
}
private class BeatContainer : BeatSyncedContainer

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Screens.Menu;
@ -12,7 +13,13 @@ namespace osu.Game.Tests.Visual.UserInterface
{
public class TestSceneHoldToConfirmOverlay : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(ExitConfirmOverlay) };
protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ExitConfirmOverlay),
typeof(HoldToConfirmContainer),
};
public TestSceneHoldToConfirmOverlay()
{

View File

@ -82,14 +82,13 @@ namespace osu.Game.Tests.Visual.UserInterface
var easierMods = instance.GetModsFor(ModType.DifficultyReduction);
var harderMods = instance.GetModsFor(ModType.DifficultyIncrease);
var assistMods = instance.GetModsFor(ModType.Automation);
var noFailMod = easierMods.FirstOrDefault(m => m is OsuModNoFail);
var hiddenMod = harderMods.FirstOrDefault(m => m is OsuModHidden);
var doubleTimeMod = harderMods.OfType<MultiMod>().FirstOrDefault(m => m.Mods.Any(a => a is OsuModDoubleTime));
var autoPilotMod = assistMods.FirstOrDefault(m => m is OsuModAutopilot);
var spunOutMod = easierMods.FirstOrDefault(m => m is OsuModSpunOut);
var easy = easierMods.FirstOrDefault(m => m is OsuModEasy);
var hardRock = harderMods.FirstOrDefault(m => m is OsuModHardRock);
@ -101,7 +100,7 @@ namespace osu.Game.Tests.Visual.UserInterface
testMultiplierTextColour(noFailMod, modSelect.LowMultiplierColour);
testMultiplierTextColour(hiddenMod, modSelect.HighMultiplierColour);
testUnimplementedMod(autoPilotMod);
testUnimplementedMod(spunOutMod);
}
[Test]

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Overlays;
@ -9,22 +10,27 @@ using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
public class TestSceneMusicController : OsuTestScene
public class TestSceneNowPlayingOverlay : OsuTestScene
{
public TestSceneMusicController()
[Cached]
private MusicController musicController = new MusicController();
public TestSceneNowPlayingOverlay()
{
Clock = new FramedClock();
var mc = new MusicController
var np = new NowPlayingOverlay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre
};
Add(mc);
AddStep(@"show", () => mc.Show());
Add(musicController);
Add(np);
AddStep(@"show", () => np.Show());
AddToggleStep(@"toggle beatmap lock", state => Beatmap.Disabled = state);
AddStep(@"show", () => mc.Hide());
AddStep(@"show", () => np.Hide());
}
}
}

View File

@ -7,6 +7,7 @@ using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Graphics;
using osu.Game.Overlays;
using osu.Game.Overlays.OSD;
namespace osu.Game.Tests.Visual.UserInterface
{
@ -22,6 +23,12 @@ namespace osu.Game.Tests.Visual.UserInterface
osd.BeginTracking(this, config);
Add(osd);
AddStep("Display empty osd toast", () => osd.Display(new EmptyToast()));
AddAssert("Toast width is 240", () => osd.Child.Width == 240);
AddStep("Display toast with lengthy text", () => osd.Display(new LengthyToast()));
AddAssert("Toast width is greater than 240", () => osd.Child.Width > 240);
AddRepeatStep("Change toggle (no bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingNoKeybind), 2);
AddRepeatStep("Change toggle (with bind)", () => config.ToggleSetting(TestConfigSetting.ToggleSettingWithKeybind), 2);
AddRepeatStep("Change enum (no bind)", () => config.IncrementEnumSetting(TestConfigSetting.EnumSettingNoKeybind), 3);
@ -86,6 +93,22 @@ namespace osu.Game.Tests.Visual.UserInterface
Setting4
}
private class EmptyToast : Toast
{
public EmptyToast()
: base("", "", "")
{
}
}
private class LengthyToast : Toast
{
public LengthyToast()
: base("Toast with a very very very long text", "A very very very very very very long text also", "A very very very very very long shortcut")
{
}
}
private class TestOnScreenDisplay : OnScreenDisplay
{
protected override void DisplayTemporarily(Drawable toDisplay) => toDisplay.FadeIn().ResizeHeightTo(110);

View File

@ -60,5 +60,7 @@ namespace osu.Game.Beatmaps
public class Beatmap : Beatmap<HitObject>
{
public new Beatmap Clone() => (Beatmap)base.Clone();
public override string ToString() => BeatmapInfo?.ToString() ?? base.ToString();
}
}

View File

@ -36,7 +36,7 @@ namespace osu.Game.Beatmaps.Formats
{
if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
{
Logger.Log($"Unknown section \"{line}\" in {output}");
Logger.Log($"Unknown section \"{line}\" in \"{output}\"");
section = Section.None;
}
@ -49,7 +49,7 @@ namespace osu.Game.Beatmaps.Formats
}
catch (Exception e)
{
Logger.Log($"Failed to process line \"{line}\" into {output}: {e.Message}", LoggingTarget.Runtime, LogLevel.Important);
Logger.Log($"Failed to process line \"{line}\" into \"{output}\": {e.Message}", LoggingTarget.Runtime, LogLevel.Important);
}
}
}

View File

@ -0,0 +1,11 @@
// 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.
namespace osu.Game.Configuration
{
public enum IntroSequence
{
Circles,
Triangles
}
}

View File

@ -105,6 +105,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f);
Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
Set(OsuSetting.IntroSequence, IntroSequence.Triangles);
}
public OsuConfigManager(Storage storage)
@ -167,6 +169,7 @@ namespace osu.Game.Configuration
ScalingPositionY,
ScalingSizeX,
ScalingSizeY,
UIScale
UIScale,
IntroSequence
}
}

View File

@ -12,9 +12,11 @@ namespace osu.Game.Graphics.Containers
{
public Action Action;
private const int activate_delay = 400;
private const int default_activation_delay = 200;
private const int fadeout_delay = 200;
private readonly double activationDelay;
private bool fired;
private bool confirming;
@ -25,13 +27,22 @@ namespace osu.Game.Graphics.Containers
public Bindable<double> Progress = new BindableDouble();
/// <summary>
/// Create a new instance.
/// </summary>
/// <param name="activationDelay">The time requried before an action is confirmed.</param>
protected HoldToConfirmContainer(double activationDelay = default_activation_delay)
{
this.activationDelay = activationDelay;
}
protected void BeginConfirm()
{
if (confirming || (!AllowMultipleFires && fired)) return;
confirming = true;
this.TransformBindableTo(Progress, 1, activate_delay * (1 - Progress.Value), Easing.Out).OnComplete(_ => Confirm());
this.TransformBindableTo(Progress, 1, activationDelay * (1 - Progress.Value), Easing.Out).OnComplete(_ => Confirm());
}
protected virtual void Confirm()

View File

@ -6,7 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Configuration;
using osuTK.Graphics;
namespace osu.Game.Graphics.Containers
{
@ -36,6 +35,8 @@ namespace osu.Game.Graphics.Containers
protected Bindable<bool> ShowStoryboard { get; private set; }
protected double DimLevel => EnableUserDim.Value ? UserDimLevel.Value : 0;
protected override Container<Drawable> Content => dimContent;
private Container dimContent { get; }
@ -78,8 +79,8 @@ namespace osu.Game.Graphics.Containers
{
ContentDisplayed = ShowDimContent;
dimContent.FadeTo((ContentDisplayed) ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint);
dimContent.FadeColour(EnableUserDim.Value ? OsuColour.Gray(1 - (float)UserDimLevel.Value) : Color4.White, BACKGROUND_FADE_DURATION, Easing.OutQuint);
dimContent.FadeTo(ContentDisplayed ? 1 : 0, BACKGROUND_FADE_DURATION, Easing.OutQuint);
dimContent.FadeColour(OsuColour.Gray(1 - (float)DimLevel), BACKGROUND_FADE_DURATION, Easing.OutQuint);
}
}
}

View File

@ -72,17 +72,11 @@ namespace osu.Game.Graphics.UserInterface
Current.DisabledChanged += disabled => labelText.Alpha = Nub.Alpha = disabled ? 0.3f : 1;
}
protected override void LoadComplete()
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
base.LoadComplete();
Current.ValueChanged += enabled =>
{
if (enabled.NewValue)
sampleChecked?.Play();
else
sampleUnchecked?.Play();
};
sampleChecked = audio.Samples.Get(@"UI/check-on");
sampleUnchecked = audio.Samples.Get(@"UI/check-off");
}
protected override bool OnHover(HoverEvent e)
@ -99,11 +93,13 @@ namespace osu.Game.Graphics.UserInterface
base.OnHoverLost(e);
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
protected override void OnUserChange(bool value)
{
sampleChecked = audio.Samples.Get(@"UI/check-on");
sampleUnchecked = audio.Samples.Get(@"UI/check-off");
base.OnUserChange(value);
if (value)
sampleChecked?.Play();
else
sampleUnchecked?.Play();
}
}
}

View File

@ -31,6 +31,11 @@ namespace osu.Game.Graphics.UserInterface
protected virtual float StripWidth() => TabContainer.Children.Sum(c => c.IsPresent ? c.DrawWidth + TabContainer.Spacing.X : 0) - TabContainer.Spacing.X;
protected virtual float StripHeight() => 1;
/// <summary>
/// Whether entries should be automatically populated if <see cref="T"/> is an <see cref="Enum"/> type.
/// </summary>
protected virtual bool AddEnumEntriesAutomatically => true;
private static bool isEnumType => typeof(T).IsEnum;
public OsuTabControl()
@ -45,7 +50,7 @@ namespace osu.Game.Graphics.UserInterface
Colour = Color4.White.Opacity(0),
});
if (isEnumType)
if (isEnumType && AddEnumEntriesAutomatically)
foreach (var val in (T[])Enum.GetValues(typeof(T)))
AddItem(val);
}

View File

@ -24,7 +24,13 @@ namespace osu.Game.Graphics.UserInterface
Height = 30;
}
public class PageTabItem : TabItem<T>
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Yellow;
}
public class PageTabItem : TabItem<T>, IHasAccentColour
{
private const float transition_duration = 100;
@ -32,6 +38,18 @@ namespace osu.Game.Graphics.UserInterface
protected readonly SpriteText Text;
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
accentColour = value;
box.Colour = accentColour;
}
}
public PageTabItem(T value)
: base(value)
{
@ -63,12 +81,6 @@ namespace osu.Game.Graphics.UserInterface
Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Exo, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
box.Colour = colours.Yellow;
}
protected override bool OnHover(HoverEvent e)
{
if (!Active.Value)

View File

@ -20,7 +20,7 @@ namespace osu.Game.Input.Bindings
handler = game;
}
public override IEnumerable<KeyBinding> DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings);
public override IEnumerable<KeyBinding> DefaultKeyBindings => GlobalKeyBindings.Concat(InGameKeyBindings).Concat(AudioControlKeyBindings);
public IEnumerable<KeyBinding> GlobalKeyBindings => new[]
{
@ -32,11 +32,6 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
new KeyBinding(InputKey.Up, GlobalAction.IncreaseVolume),
new KeyBinding(InputKey.MouseWheelUp, GlobalAction.IncreaseVolume),
new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume),
new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume),
new KeyBinding(InputKey.F4, GlobalAction.ToggleMute),
new KeyBinding(InputKey.Escape, GlobalAction.Back),
new KeyBinding(InputKey.ExtraMouseButton1, GlobalAction.Back),
@ -55,6 +50,22 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Minus }, GlobalAction.DecreaseScrollSpeed),
};
public IEnumerable<KeyBinding> AudioControlKeyBindings => new[]
{
new KeyBinding(InputKey.Up, GlobalAction.IncreaseVolume),
new KeyBinding(InputKey.MouseWheelUp, GlobalAction.IncreaseVolume),
new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume),
new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume),
new KeyBinding(InputKey.F4, GlobalAction.ToggleMute),
new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev),
new KeyBinding(InputKey.F1, GlobalAction.MusicPrev),
new KeyBinding(InputKey.TrackNext, GlobalAction.MusicNext),
new KeyBinding(InputKey.F5, GlobalAction.MusicNext),
new KeyBinding(InputKey.PlayPause, GlobalAction.MusicPlay),
new KeyBinding(InputKey.F3, GlobalAction.MusicPlay)
};
protected override IEnumerable<Drawable> KeyBindingInputQueue =>
handler == null ? base.KeyBindingInputQueue : base.KeyBindingInputQueue.Prepend(handler);
}
@ -115,5 +126,15 @@ namespace osu.Game.Input.Bindings
[Description("Quick exit (Hold)")]
QuickExit,
// Game-wide beatmap msi ccotolle keybindings
[Description("Next track")]
MusicNext,
[Description("Previous track")]
MusicPrev,
[Description("Play / pause")]
MusicPlay,
}
}

View File

@ -299,7 +299,7 @@ namespace osu.Game
}, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true);
}
#region Beatmap jukebox progression
#region Beatmap progression
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> beatmap)
{
@ -469,6 +469,8 @@ namespace osu.Game
loadComponentSingleFile(volume = new VolumeOverlay(), leftFloatingOverlayContent.Add);
loadComponentSingleFile(new OnScreenDisplay(), Add, true);
loadComponentSingleFile(musicController = new MusicController(), Add, true);
loadComponentSingleFile(notifications = new NotificationOverlay
{
GetToolbarHeight = () => ToolbarOffset,
@ -495,7 +497,7 @@ namespace osu.Game
Origin = Anchor.TopRight,
}, rightFloatingOverlayContent.Add, true);
loadComponentSingleFile(musicController = new MusicController
loadComponentSingleFile(new NowPlayingOverlay
{
GetToolbarHeight = () => ToolbarOffset,
Anchor = Anchor.TopRight,

View File

@ -0,0 +1,119 @@
// 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 osu.Framework.Graphics.UserInterface;
using osu.Game.Screens.Select.Leaderboards;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Framework.Allocation;
using osuTK.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Input.Events;
namespace osu.Game.Overlays.BeatmapSet
{
public class LeaderboardScopeSelector : PageTabControl<BeatmapLeaderboardScope>
{
protected override bool AddEnumEntriesAutomatically => false;
protected override Dropdown<BeatmapLeaderboardScope> CreateDropdown() => null;
protected override TabItem<BeatmapLeaderboardScope> CreateTabItem(BeatmapLeaderboardScope value) => new ScopeSelectorTabItem(value);
public LeaderboardScopeSelector()
{
RelativeSizeAxes = Axes.X;
AddItem(BeatmapLeaderboardScope.Global);
AddItem(BeatmapLeaderboardScope.Country);
AddItem(BeatmapLeaderboardScope.Friend);
AddInternal(new GradientLine
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
});
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Blue;
}
protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(20, 0),
};
private class ScopeSelectorTabItem : PageTabItem
{
public ScopeSelectorTabItem(BeatmapLeaderboardScope value)
: base(value)
{
Text.Font = OsuFont.GetFont(size: 16);
}
protected override bool OnHover(HoverEvent e)
{
Text.FadeColour(AccentColour);
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
Text.FadeColour(Color4.White);
}
}
private class GradientLine : GridContainer
{
public GradientLine()
{
RelativeSizeAxes = Axes.X;
Size = new Vector2(0.8f, 1.5f);
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(mode: GridSizeMode.Relative, size: 0.4f),
new Dimension(),
};
Content = new[]
{
new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(Color4.Transparent, Color4.Gray),
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Gray,
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(Color4.Gray, Color4.Transparent),
},
}
};
}
}
}
}

View File

@ -30,27 +30,24 @@ namespace osu.Game.Overlays.Chat
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
Child = new OsuContextMenuContainer
{
scroll = new OsuScrollContainer
RelativeSizeAxes = Axes.Both,
Masking = true,
Child = scroll = new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
// Some chat lines have effects that slightly protrude to the bottom,
// which we do not want to mask away, hence the padding.
Padding = new MarginPadding { Bottom = 5 },
Child = new OsuContextMenuContainer
Child = ChatLineFlow = new ChatLineContainer
{
Padding = new MarginPadding { Left = 20, Right = 20 },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = ChatLineFlow = new ChatLineContainer
{
Padding = new MarginPadding { Left = 20, Right = 20 },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
}
},
}
Direction = FillDirection.Vertical,
}
},
};
newMessagesArrived(Channel.Messages);

View File

@ -15,6 +15,7 @@ namespace osu.Game.Overlays.KeyBinding
public GlobalKeyBindingsSection(GlobalActionContainer manager)
{
Add(new DefaultBindingsSubsection(manager));
Add(new AudioControlKeyBindingsSubsection(manager));
Add(new InGameKeyBindingsSubsection(manager));
}
@ -39,5 +40,16 @@ namespace osu.Game.Overlays.KeyBinding
Defaults = manager.InGameKeyBindings;
}
}
private class AudioControlKeyBindingsSubsection : KeyBindingsSubsection
{
protected override string Header => "Audio";
public AudioControlKeyBindingsSubsection(GlobalActionContainer manager)
: base(null)
{
Defaults = manager.AudioControlKeyBindings;
}
}
}
}

View File

@ -1,292 +1,112 @@
// 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.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Framework.Input.Bindings;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Music;
using osu.Game.Input.Bindings;
using osu.Game.Overlays.OSD;
using osu.Game.Rulesets.Mods;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays
{
public class MusicController : OsuFocusedOverlayContainer
/// <summary>
/// Handles playback of the global music track.
/// </summary>
public class MusicController : Component, IKeyBindingHandler<GlobalAction>
{
private const float player_height = 130;
private const float transition_length = 800;
private const float progress_height = 10;
private const float bottom_black_area_height = 55;
private Drawable background;
private ProgressBar progressBar;
private IconButton prevButton;
private IconButton playButton;
private IconButton nextButton;
private IconButton playlistButton;
private SpriteText title, artist;
private PlaylistOverlay playlist;
private BeatmapManager beatmaps;
[Resolved]
private BeatmapManager beatmaps { get; set; }
private List<BeatmapSetInfo> beatmapSets;
private Container dragContainer;
private Container playerContainer;
public bool IsUserPaused { get; private set; }
/// <summary>
/// Fired when the global <see cref="WorkingBeatmap"/> has changed.
/// Includes direction information for display purposes.
/// </summary>
public event Action<WorkingBeatmap, TrackChangeDirection> TrackChanged;
[Resolved]
private Bindable<WorkingBeatmap> beatmap { get; set; }
private IBindable<WorkingBeatmap> beatmap { get; set; }
[Resolved]
private IBindable<IReadOnlyList<Mod>> mods { get; set; }
/// <summary>
/// Provide a source for the toolbar height.
/// </summary>
public Func<float> GetToolbarHeight;
public MusicController()
{
Width = 400;
Margin = new MarginPadding(10);
}
[Resolved(canBeNull: true)]
private OnScreenDisplay onScreenDisplay { get; set; }
[BackgroundDependencyLoader]
private void load(Bindable<WorkingBeatmap> beatmap, BeatmapManager beatmaps, OsuColour colours)
private void load()
{
this.beatmaps = beatmaps;
Children = new Drawable[]
{
dragContainer = new DragContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
playlist = new PlaylistOverlay
{
RelativeSizeAxes = Axes.X,
Y = player_height + 10,
OrderChanged = playlistOrderChanged
},
playerContainer = new Container
{
RelativeSizeAxes = Axes.X,
Height = player_height,
Masking = true,
CornerRadius = 5,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(40),
Radius = 5,
},
Children = new[]
{
background = new Background(),
title = new OsuSpriteText
{
Origin = Anchor.BottomCentre,
Anchor = Anchor.TopCentre,
Position = new Vector2(0, 40),
Font = OsuFont.GetFont(size: 25, italics: true),
Colour = Color4.White,
Text = @"Nothing to play",
},
artist = new OsuSpriteText
{
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Position = new Vector2(0, 45),
Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold, italics: true),
Colour = Color4.White,
Text = @"Nothing to play",
},
new Container
{
Padding = new MarginPadding { Bottom = progress_height },
Height = bottom_black_area_height,
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Children = new Drawable[]
{
new FillFlowContainer<IconButton>
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Children = new[]
{
prevButton = new MusicIconButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = prev,
Icon = FontAwesome.Solid.StepBackward,
},
playButton = new MusicIconButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(1.4f),
IconScale = new Vector2(1.4f),
Action = togglePause,
Icon = FontAwesome.Regular.PlayCircle,
},
nextButton = new MusicIconButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = () => next(),
Icon = FontAwesome.Solid.StepForward,
},
}
},
playlistButton = new MusicIconButton
{
Origin = Anchor.Centre,
Anchor = Anchor.CentreRight,
Position = new Vector2(-bottom_black_area_height / 2, 0),
Icon = FontAwesome.Solid.Bars,
Action = () => playlist.ToggleVisibility(),
},
}
},
progressBar = new ProgressBar
{
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Height = progress_height,
FillColour = colours.Yellow,
OnSeek = attemptSeek
}
},
},
}
}
};
beatmapSets = beatmaps.GetAllUsableBeatmapSets();
beatmaps.ItemAdded += handleBeatmapAdded;
beatmaps.ItemRemoved += handleBeatmapRemoved;
playlist.State.ValueChanged += s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint);
}
private ScheduledDelegate seekDelegate;
private void attemptSeek(double progress)
protected override void LoadComplete()
{
seekDelegate?.Cancel();
seekDelegate = Schedule(() =>
{
if (!beatmap.Disabled)
current?.Track.Seek(progress);
});
beatmap.BindValueChanged(beatmapChanged, true);
mods.BindValueChanged(_ => updateAudioAdjustments(), true);
base.LoadComplete();
}
private void playlistOrderChanged(BeatmapSetInfo beatmapSetInfo, int index)
/// <summary>
/// Change the position of a <see cref="BeatmapSetInfo"/> in the current playlist.
/// </summary>
/// <param name="beatmapSetInfo">The beatmap to move.</param>
/// <param name="index">The new position.</param>
public void ChangeBeatmapSetPosition(BeatmapSetInfo beatmapSetInfo, int index)
{
beatmapSets.Remove(beatmapSetInfo);
beatmapSets.Insert(index, beatmapSetInfo);
}
private void handleBeatmapAdded(BeatmapSetInfo set) => Schedule(() => beatmapSets.Add(set));
/// <summary>
/// Returns whether the current beatmap track is playing.
/// </summary>
public bool IsPlaying => beatmap.Value.Track.IsRunning;
private void handleBeatmapRemoved(BeatmapSetInfo set) => Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID));
private void handleBeatmapAdded(BeatmapSetInfo set) =>
Schedule(() => beatmapSets.Add(set));
protected override void LoadComplete()
private void handleBeatmapRemoved(BeatmapSetInfo set) =>
Schedule(() => beatmapSets.RemoveAll(s => s.ID == set.ID));
private ScheduledDelegate seekDelegate;
public void SeekTo(double position)
{
beatmap.BindValueChanged(beatmapChanged, true);
beatmap.BindDisabledChanged(beatmapDisabledChanged, true);
mods.BindValueChanged(_ => updateAudioAdjustments(), true);
base.LoadComplete();
}
private void beatmapDisabledChanged(bool disabled)
{
if (disabled)
playlist.Hide();
playButton.Enabled.Value = !disabled;
prevButton.Enabled.Value = !disabled;
nextButton.Enabled.Value = !disabled;
playlistButton.Enabled.Value = !disabled;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
Height = dragContainer.Height;
dragContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
}
protected override void Update()
{
base.Update();
if (pendingBeatmapSwitch != null)
seekDelegate?.Cancel();
seekDelegate = Schedule(() =>
{
pendingBeatmapSwitch();
pendingBeatmapSwitch = null;
}
var track = current?.TrackLoaded ?? false ? current.Track : null;
if (track?.IsDummyDevice == false)
{
progressBar.EndTime = track.Length;
progressBar.CurrentTime = track.CurrentTime;
playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
}
else
{
progressBar.CurrentTime = 0;
progressBar.EndTime = 1;
playButton.Icon = FontAwesome.Regular.PlayCircle;
}
if (!beatmap.Disabled)
current?.Track.Seek(position);
});
}
private void togglePause()
/// <summary>
/// Toggle pause / play.
/// </summary>
/// <returns>Whether the operation was successful.</returns>
public bool TogglePause()
{
var track = current?.Track;
if (track == null)
{
if (!beatmap.Disabled)
next(true);
return;
if (beatmap.Disabled)
return false;
next(true);
return true;
}
if (track.IsRunning)
@ -299,48 +119,70 @@ namespace osu.Game.Overlays
track.Start();
IsUserPaused = false;
}
return true;
}
private void prev()
/// <summary>
/// Play the previous track.
/// </summary>
/// <returns>Whether the operation was successful.</returns>
public bool PrevTrack()
{
queuedDirection = TransformDirection.Prev;
queuedDirection = TrackChangeDirection.Prev;
var playable = beatmapSets.TakeWhile(i => i.ID != current.BeatmapSetInfo.ID).LastOrDefault() ?? beatmapSets.LastOrDefault();
if (playable != null)
{
beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
if (beatmap is Bindable<WorkingBeatmap> working)
working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
beatmap.Value.Track.Restart();
return true;
}
return false;
}
private void next(bool instant = false)
/// <summary>
/// Play the next random or playlist track.
/// </summary>
/// <returns>Whether the operation was successful.</returns>
public bool NextTrack() => next();
private bool next(bool instant = false)
{
if (!instant)
queuedDirection = TransformDirection.Next;
queuedDirection = TrackChangeDirection.Next;
var playable = beatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? beatmapSets.FirstOrDefault();
if (playable != null)
{
beatmap.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
if (beatmap is Bindable<WorkingBeatmap> working)
working.Value = beatmaps.GetWorkingBeatmap(playable.Beatmaps.First(), beatmap.Value);
beatmap.Value.Track.Restart();
return true;
}
return false;
}
private WorkingBeatmap current;
private TransformDirection? queuedDirection;
private TrackChangeDirection? queuedDirection;
private void beatmapChanged(ValueChangedEvent<WorkingBeatmap> beatmap)
{
TransformDirection direction = TransformDirection.None;
TrackChangeDirection direction = TrackChangeDirection.None;
if (current != null)
{
bool audioEquals = beatmap.NewValue?.BeatmapInfo?.AudioEquals(current.BeatmapInfo) ?? false;
if (audioEquals)
direction = TransformDirection.None;
direction = TrackChangeDirection.None;
else if (queuedDirection.HasValue)
{
direction = queuedDirection.Value;
@ -352,13 +194,13 @@ namespace osu.Game.Overlays
var last = beatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
var next = beatmap.NewValue == null ? -1 : beatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count();
direction = last > next ? TransformDirection.Prev : TransformDirection.Next;
direction = last > next ? TrackChangeDirection.Prev : TrackChangeDirection.Next;
}
}
progressBar.CurrentTime = 0;
current = beatmap.NewValue;
TrackChanged?.Invoke(current, direction);
updateDisplay(current = beatmap.NewValue, direction);
updateAudioAdjustments();
queuedDirection = null;
@ -376,167 +218,60 @@ namespace osu.Game.Overlays
mod.ApplyToClock(track);
}
private Action pendingBeatmapSwitch;
private void updateDisplay(WorkingBeatmap beatmap, TransformDirection direction)
protected override void Dispose(bool isDisposing)
{
// avoid using scheduler as our scheduler may not be run for a long time, holding references to beatmaps.
pendingBeatmapSwitch = delegate
base.Dispose(isDisposing);
if (beatmaps != null)
{
// todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync()
Task.Run(() =>
{
if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists
{
title.Text = @"Nothing to play";
artist.Text = @"Nothing to play";
}
else
{
BeatmapMetadata metadata = beatmap.Metadata;
title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title));
artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist));
}
});
LoadComponentAsync(new Background(beatmap) { Depth = float.MaxValue }, newBackground =>
{
switch (direction)
{
case TransformDirection.Next:
newBackground.Position = new Vector2(400, 0);
newBackground.MoveToX(0, 500, Easing.OutCubic);
background.MoveToX(-400, 500, Easing.OutCubic);
break;
case TransformDirection.Prev:
newBackground.Position = new Vector2(-400, 0);
newBackground.MoveToX(0, 500, Easing.OutCubic);
background.MoveToX(400, 500, Easing.OutCubic);
break;
}
background.Expire();
background = newBackground;
playerContainer.Add(newBackground);
});
};
}
protected override void PopIn()
{
base.PopIn();
this.FadeIn(transition_length, Easing.OutQuint);
dragContainer.ScaleTo(1, transition_length, Easing.OutElastic);
}
protected override void PopOut()
{
base.PopOut();
this.FadeOut(transition_length, Easing.OutQuint);
dragContainer.ScaleTo(0.9f, transition_length, Easing.OutQuint);
}
private enum TransformDirection
{
None,
Next,
Prev
}
private class MusicIconButton : IconButton
{
public MusicIconButton()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
HoverColour = colours.YellowDark.Opacity(0.6f);
FlashColour = colours.Yellow;
}
protected override void LoadComplete()
{
base.LoadComplete();
// works with AutoSizeAxes above to make buttons autosize with the scale animation.
Content.AutoSizeAxes = Axes.None;
Content.Size = new Vector2(DEFAULT_BUTTON_SIZE);
beatmaps.ItemAdded -= handleBeatmapAdded;
beatmaps.ItemRemoved -= handleBeatmapRemoved;
}
}
private class Background : BufferedContainer
public bool OnPressed(GlobalAction action)
{
private readonly Sprite sprite;
private readonly WorkingBeatmap beatmap;
if (beatmap.Disabled)
return false;
public Background(WorkingBeatmap beatmap = null)
switch (action)
{
this.beatmap = beatmap;
CacheDrawnFrameBuffer = true;
Depth = float.MaxValue;
RelativeSizeAxes = Axes.Both;
case GlobalAction.MusicPlay:
if (TogglePause())
onScreenDisplay?.Display(new MusicControllerToast(IsPlaying ? "Play track" : "Pause track"));
return true;
Children = new Drawable[]
{
sprite = new Sprite
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(150),
FillMode = FillMode.Fill,
},
new Box
{
RelativeSizeAxes = Axes.X,
Height = bottom_black_area_height,
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Colour = Color4.Black.Opacity(0.5f)
}
};
case GlobalAction.MusicNext:
if (NextTrack())
onScreenDisplay?.Display(new MusicControllerToast("Next track"));
return true;
case GlobalAction.MusicPrev:
if (PrevTrack())
onScreenDisplay?.Display(new MusicControllerToast("Previous track"));
return true;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4");
}
return false;
}
private class DragContainer : Container
public bool OnReleased(GlobalAction action) => false;
public class MusicControllerToast : Toast
{
protected override bool OnDragStart(DragStartEvent e)
public MusicControllerToast(string action)
: base("Music Playback", action, string.Empty)
{
return true;
}
protected override bool OnDrag(DragEvent e)
{
Vector2 change = e.MousePosition - e.MouseDownPosition;
// Diminish the drag distance as we go further to simulate "rubber band" feeling.
change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length;
this.MoveTo(change);
return true;
}
protected override bool OnDragEnd(DragEndEvent e)
{
this.MoveTo(Vector2.Zero, 800, Easing.OutElastic);
return base.OnDragEnd(e);
}
}
}
/// <summary>
/// Play the next random or playlist track.
/// </summary>
public void NextTrack() => next();
public enum TrackChangeDirection
{
None,
Next,
Prev
}
}

View File

@ -0,0 +1,404 @@
// 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.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Music;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays
{
public class NowPlayingOverlay : OsuFocusedOverlayContainer
{
private const float player_height = 130;
private const float transition_length = 800;
private const float progress_height = 10;
private const float bottom_black_area_height = 55;
private Drawable background;
private ProgressBar progressBar;
private IconButton prevButton;
private IconButton playButton;
private IconButton nextButton;
private IconButton playlistButton;
private SpriteText title, artist;
private PlaylistOverlay playlist;
private Container dragContainer;
private Container playerContainer;
/// <summary>
/// Provide a source for the toolbar height.
/// </summary>
public Func<float> GetToolbarHeight;
[Resolved]
private MusicController musicController { get; set; }
[Resolved]
private Bindable<WorkingBeatmap> beatmap { get; set; }
public NowPlayingOverlay()
{
Width = 400;
Margin = new MarginPadding(10);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Children = new Drawable[]
{
dragContainer = new DragContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
playlist = new PlaylistOverlay
{
RelativeSizeAxes = Axes.X,
Y = player_height + 10,
OrderChanged = musicController.ChangeBeatmapSetPosition
},
playerContainer = new Container
{
RelativeSizeAxes = Axes.X,
Height = player_height,
Masking = true,
CornerRadius = 5,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(40),
Radius = 5,
},
Children = new[]
{
background = new Background(),
title = new OsuSpriteText
{
Origin = Anchor.BottomCentre,
Anchor = Anchor.TopCentre,
Position = new Vector2(0, 40),
Font = OsuFont.GetFont(size: 25, italics: true),
Colour = Color4.White,
Text = @"Nothing to play",
},
artist = new OsuSpriteText
{
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
Position = new Vector2(0, 45),
Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold, italics: true),
Colour = Color4.White,
Text = @"Nothing to play",
},
new Container
{
Padding = new MarginPadding { Bottom = progress_height },
Height = bottom_black_area_height,
RelativeSizeAxes = Axes.X,
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Children = new Drawable[]
{
new FillFlowContainer<IconButton>
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Children = new[]
{
prevButton = new MusicIconButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = () => musicController.PrevTrack(),
Icon = FontAwesome.Solid.StepBackward,
},
playButton = new MusicIconButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(1.4f),
IconScale = new Vector2(1.4f),
Action = () => musicController.TogglePause(),
Icon = FontAwesome.Regular.PlayCircle,
},
nextButton = new MusicIconButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Action = () => musicController.NextTrack(),
Icon = FontAwesome.Solid.StepForward,
},
}
},
playlistButton = new MusicIconButton
{
Origin = Anchor.Centre,
Anchor = Anchor.CentreRight,
Position = new Vector2(-bottom_black_area_height / 2, 0),
Icon = FontAwesome.Solid.Bars,
Action = () => playlist.ToggleVisibility(),
},
}
},
progressBar = new ProgressBar
{
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Height = progress_height,
FillColour = colours.Yellow,
OnSeek = musicController.SeekTo
}
},
},
}
}
};
playlist.State.ValueChanged += s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint);
}
protected override void LoadComplete()
{
base.LoadComplete();
beatmap.BindDisabledChanged(beatmapDisabledChanged, true);
musicController.TrackChanged += trackChanged;
trackChanged(beatmap.Value);
}
protected override void PopIn()
{
base.PopIn();
this.FadeIn(transition_length, Easing.OutQuint);
dragContainer.ScaleTo(1, transition_length, Easing.OutElastic);
}
protected override void PopOut()
{
base.PopOut();
this.FadeOut(transition_length, Easing.OutQuint);
dragContainer.ScaleTo(0.9f, transition_length, Easing.OutQuint);
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
Height = dragContainer.Height;
dragContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
}
protected override void Update()
{
base.Update();
if (pendingBeatmapSwitch != null)
{
pendingBeatmapSwitch();
pendingBeatmapSwitch = null;
}
var track = beatmap.Value?.TrackLoaded ?? false ? beatmap.Value.Track : null;
if (track?.IsDummyDevice == false)
{
progressBar.EndTime = track.Length;
progressBar.CurrentTime = track.CurrentTime;
playButton.Icon = track.IsRunning ? FontAwesome.Regular.PauseCircle : FontAwesome.Regular.PlayCircle;
}
else
{
progressBar.CurrentTime = 0;
progressBar.EndTime = 1;
playButton.Icon = FontAwesome.Regular.PlayCircle;
}
}
private Action pendingBeatmapSwitch;
private void trackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction = TrackChangeDirection.None)
{
// avoid using scheduler as our scheduler may not be run for a long time, holding references to beatmaps.
pendingBeatmapSwitch = delegate
{
// todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync()
Task.Run(() =>
{
if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists
{
title.Text = @"Nothing to play";
artist.Text = @"Nothing to play";
}
else
{
BeatmapMetadata metadata = beatmap.Metadata;
title.Text = new LocalisedString((metadata.TitleUnicode, metadata.Title));
artist.Text = new LocalisedString((metadata.ArtistUnicode, metadata.Artist));
}
});
LoadComponentAsync(new Background(beatmap) { Depth = float.MaxValue }, newBackground =>
{
switch (direction)
{
case TrackChangeDirection.Next:
newBackground.Position = new Vector2(400, 0);
newBackground.MoveToX(0, 500, Easing.OutCubic);
background.MoveToX(-400, 500, Easing.OutCubic);
break;
case TrackChangeDirection.Prev:
newBackground.Position = new Vector2(-400, 0);
newBackground.MoveToX(0, 500, Easing.OutCubic);
background.MoveToX(400, 500, Easing.OutCubic);
break;
}
background.Expire();
background = newBackground;
playerContainer.Add(newBackground);
});
};
}
private void beatmapDisabledChanged(bool disabled)
{
if (disabled)
playlist.Hide();
playButton.Enabled.Value = !disabled;
prevButton.Enabled.Value = !disabled;
nextButton.Enabled.Value = !disabled;
playlistButton.Enabled.Value = !disabled;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (musicController != null)
musicController.TrackChanged -= trackChanged;
}
private class MusicIconButton : IconButton
{
public MusicIconButton()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
HoverColour = colours.YellowDark.Opacity(0.6f);
FlashColour = colours.Yellow;
}
protected override void LoadComplete()
{
base.LoadComplete();
// works with AutoSizeAxes above to make buttons autosize with the scale animation.
Content.AutoSizeAxes = Axes.None;
Content.Size = new Vector2(DEFAULT_BUTTON_SIZE);
}
}
private class Background : BufferedContainer
{
private readonly Sprite sprite;
private readonly WorkingBeatmap beatmap;
public Background(WorkingBeatmap beatmap = null)
{
this.beatmap = beatmap;
CacheDrawnFrameBuffer = true;
Depth = float.MaxValue;
RelativeSizeAxes = Axes.Both;
Children = new Drawable[]
{
sprite = new Sprite
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(150),
FillMode = FillMode.Fill,
},
new Box
{
RelativeSizeAxes = Axes.X,
Height = bottom_black_area_height,
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Colour = Color4.Black.Opacity(0.5f)
}
};
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
sprite.Texture = beatmap?.Background ?? textures.Get(@"Backgrounds/bg4");
}
}
private class DragContainer : Container
{
protected override bool OnDragStart(DragStartEvent e)
{
return true;
}
protected override bool OnDrag(DragEvent e)
{
Vector2 change = e.MousePosition - e.MouseDownPosition;
// Diminish the drag distance as we go further to simulate "rubber band" feeling.
change *= change.Length <= 0 ? 0 : (float)Math.Pow(change.Length, 0.7f) / change.Length;
this.MoveTo(change);
return true;
}
protected override bool OnDragEnd(DragEndEvent e)
{
this.MoveTo(Vector2.Zero, 800, Easing.OutElastic);
return base.OnDragEnd(e);
}
}
}
}

View File

@ -0,0 +1,84 @@
// 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 osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.OSD
{
public abstract class Toast : Container
{
private const int toast_minimum_width = 240;
private readonly Container content;
protected override Container<Drawable> Content => content;
protected readonly OsuSpriteText ValueText;
protected Toast(string description, string value, string shortcut)
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
// A toast's height is decided (and transformed) by the containing OnScreenDisplay.
RelativeSizeAxes = Axes.Y;
AutoSizeAxes = Axes.X;
InternalChildren = new Drawable[]
{
new Container //this container exists just to set a minimum width for the toast
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = toast_minimum_width
},
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.7f
},
content = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
},
new OsuSpriteText
{
Padding = new MarginPadding(10),
Name = "Description",
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Black),
Spacing = new Vector2(1, 0),
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = description.ToUpperInvariant()
},
ValueText = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light),
Padding = new MarginPadding { Left = 10, Right = 10 },
Name = "Value",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = value
},
new OsuSpriteText
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Name = "Shortcut",
Alpha = 0.3f,
Margin = new MarginPadding { Bottom = 15 },
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
Text = string.IsNullOrEmpty(shortcut) ? "NO KEY BOUND" : shortcut.ToUpperInvariant()
},
};
}
}
}

View File

@ -0,0 +1,147 @@
// 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.Framework.Allocation;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.OSD
{
public class TrackedSettingToast : Toast
{
private const int lights_bottom_margin = 40;
public TrackedSettingToast(SettingDescription description)
: base(description.Name, description.Value, description.Shortcut)
{
FillFlowContainer<OptionLight> optionLights;
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Margin = new MarginPadding { Bottom = lights_bottom_margin },
Children = new Drawable[]
{
optionLights = new FillFlowContainer<OptionLight>
{
Margin = new MarginPadding { Bottom = 5 },
Spacing = new Vector2(5, 0),
Direction = FillDirection.Horizontal,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both
},
}
}
};
int optionCount = 0;
int selectedOption = -1;
switch (description.RawValue)
{
case bool val:
optionCount = 1;
if (val) selectedOption = 0;
break;
case Enum _:
var values = Enum.GetValues(description.RawValue.GetType());
optionCount = values.Length;
selectedOption = Convert.ToInt32(description.RawValue);
break;
}
ValueText.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre;
for (int i = 0; i < optionCount; i++)
optionLights.Add(new OptionLight { Glowing = i == selectedOption });
}
private class OptionLight : Container
{
private Color4 glowingColour, idleColour;
private const float transition_speed = 300;
private const float glow_strength = 0.4f;
private readonly Box fill;
public OptionLight()
{
Children = new[]
{
fill = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 1,
},
};
}
private bool glowing;
public bool Glowing
{
set
{
glowing = value;
if (!IsLoaded) return;
updateGlow();
}
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
fill.Colour = idleColour = Color4.White.Opacity(0.4f);
glowingColour = Color4.White;
Size = new Vector2(25, 5);
Masking = true;
CornerRadius = 3;
EdgeEffect = new EdgeEffectParameters
{
Colour = colours.BlueDark.Opacity(glow_strength),
Type = EdgeEffectType.Glow,
Radius = 8,
};
}
protected override void LoadComplete()
{
updateGlow();
FinishTransforms(true);
}
private void updateGlow()
{
if (glowing)
{
fill.FadeColour(glowingColour, transition_speed, Easing.OutQuint);
FadeEdgeEffectTo(glow_strength, transition_speed, Easing.OutQuint);
}
else
{
FadeEdgeEffectTo(0, transition_speed, Easing.OutQuint);
fill.FadeColour(idleColour, transition_speed, Easing.OutQuint);
}
}
}
}
}

View File

@ -8,34 +8,25 @@ using osu.Framework.Configuration;
using osu.Framework.Configuration.Tracking;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Threading;
using osu.Game.Configuration;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays.OSD;
namespace osu.Game.Overlays
{
/// <summary>
/// An on-screen display which automatically tracks and displays toast notifications for <seealso cref="TrackedSettings"/>.
/// Can also display custom content via <see cref="Display(Toast)"/>
/// </summary>
public class OnScreenDisplay : Container
{
private readonly Container box;
private readonly SpriteText textLine1;
private readonly SpriteText textLine2;
private readonly SpriteText textLine3;
private const float height = 110;
private const float height_notext = 98;
private const float height_contracted = height * 0.9f;
private readonly FillFlowContainer<OptionLight> optionLights;
public OnScreenDisplay()
{
RelativeSizeAxes = Axes.Both;
@ -52,64 +43,6 @@ namespace osu.Game.Overlays
Height = height_contracted,
Alpha = 0,
CornerRadius = 20,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.7f,
},
new Container // purely to add a minimum width
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 240,
RelativeSizeAxes = Axes.Y,
},
textLine1 = new OsuSpriteText
{
Padding = new MarginPadding(10),
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Black),
Spacing = new Vector2(1, 0),
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
},
textLine2 = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 24, weight: FontWeight.Light),
Padding = new MarginPadding { Left = 10, Right = 10 },
Anchor = Anchor.Centre,
Origin = Anchor.BottomCentre,
},
new FillFlowContainer
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
optionLights = new FillFlowContainer<OptionLight>
{
Padding = new MarginPadding { Top = 20, Bottom = 5 },
Spacing = new Vector2(5, 0),
Direction = FillDirection.Horizontal,
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
AutoSizeAxes = Axes.Both
},
textLine3 = new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Margin = new MarginPadding { Bottom = 15 },
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
Alpha = 0.3f,
},
}
}
}
},
};
}
@ -142,7 +75,7 @@ namespace osu.Game.Overlays
return;
configManager.LoadInto(trackedSettings);
trackedSettings.SettingChanged += display;
trackedSettings.SettingChanged += displayTrackedSettingChange;
trackedConfigManagers.Add((source, configManager), trackedSettings);
}
@ -162,56 +95,23 @@ namespace osu.Game.Overlays
return;
existing.Unload();
existing.SettingChanged -= display;
existing.SettingChanged -= displayTrackedSettingChange;
trackedConfigManagers.Remove((source, configManager));
}
private void display(SettingDescription description)
/// <summary>
/// Displays the provided <see cref="Toast"/> temporarily.
/// </summary>
/// <param name="toast"></param>
public void Display(Toast toast)
{
Schedule(() =>
{
textLine1.Text = description.Name.ToUpperInvariant();
textLine2.Text = description.Value;
textLine3.Text = description.Shortcut.ToUpperInvariant();
if (string.IsNullOrEmpty(textLine3.Text))
textLine3.Text = "NO KEY BOUND";
DisplayTemporarily(box);
int optionCount = 0;
int selectedOption = -1;
switch (description.RawValue)
{
case bool val:
optionCount = 1;
if (val) selectedOption = 0;
break;
case Enum _:
var values = Enum.GetValues(description.RawValue.GetType());
optionCount = values.Length;
selectedOption = Convert.ToInt32(description.RawValue);
break;
}
textLine2.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre;
textLine2.Y = optionCount > 0 ? 0 : 5;
if (optionLights.Children.Count != optionCount)
{
optionLights.Clear();
for (int i = 0; i < optionCount; i++)
optionLights.Add(new OptionLight());
}
for (int i = 0; i < optionCount; i++)
optionLights.Children[i].Glowing = i == selectedOption;
});
box.Child = toast;
DisplayTemporarily(box);
}
private void displayTrackedSettingChange(SettingDescription description) => Schedule(() => Display(new TrackedSettingToast(description)));
private TransformSequence<Drawable> fadeIn;
private ScheduledDelegate fadeOut;
@ -236,80 +136,5 @@ namespace osu.Game.Overlays
b => b.ResizeHeightTo(height_contracted, 1500, Easing.InQuint));
}, 500);
}
private class OptionLight : Container
{
private Color4 glowingColour, idleColour;
private const float transition_speed = 300;
private const float glow_strength = 0.4f;
private readonly Box fill;
public OptionLight()
{
Children = new[]
{
fill = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 1,
},
};
}
private bool glowing;
public bool Glowing
{
set
{
glowing = value;
if (!IsLoaded) return;
updateGlow();
}
}
private void updateGlow()
{
if (glowing)
{
fill.FadeColour(glowingColour, transition_speed, Easing.OutQuint);
FadeEdgeEffectTo(glow_strength, transition_speed, Easing.OutQuint);
}
else
{
FadeEdgeEffectTo(0, transition_speed, Easing.OutQuint);
fill.FadeColour(idleColour, transition_speed, Easing.OutQuint);
}
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
fill.Colour = idleColour = Color4.White.Opacity(0.4f);
glowingColour = Color4.White;
Size = new Vector2(25, 5);
Masking = true;
CornerRadius = 3;
EdgeEffect = new EdgeEffectParameters
{
Colour = colours.BlueDark.Opacity(glow_strength),
Type = EdgeEffectType.Glow,
Radius = 8,
};
}
protected override void LoadComplete()
{
updateGlow();
FinishTransforms(true);
}
}
}
}

View File

@ -140,6 +140,9 @@ namespace osu.Game.Overlays.Profile.Header
{
if (string.IsNullOrEmpty(content)) return;
// newlines could be contained in API returned user content.
content = content.Replace("\n", " ");
bottomLinkContainer.AddIcon(icon, text =>
{
text.Font = text.Font.With(size: 10);

View File

@ -0,0 +1,168 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Overlays.Profile.Header.Components
{
public class PreviousUsernames : CompositeDrawable
{
private const int duration = 200;
private const int margin = 10;
private const int width = 310;
private const int move_offset = 15;
public readonly Bindable<User> User = new Bindable<User>();
private readonly TextFlowContainer text;
private readonly Box background;
private readonly SpriteText header;
public PreviousUsernames()
{
HoverIconContainer hoverIcon;
AutoSizeAxes = Axes.Y;
Width = width;
Masking = true;
CornerRadius = 5;
AddRangeInternal(new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
},
new GridContainer
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize)
},
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Distributed)
},
Content = new[]
{
new Drawable[]
{
hoverIcon = new HoverIconContainer(),
header = new SpriteText
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Text = @"formerly known as",
Font = OsuFont.GetFont(size: 10, italics: true)
}
},
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
},
text = new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true))
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Full,
Margin = new MarginPadding { Bottom = margin, Top = margin / 2f }
}
}
}
}
});
hoverIcon.ActivateHover += showContent;
hideContent();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
background.Colour = colours.GreySeafoamDarker;
}
protected override void LoadComplete()
{
base.LoadComplete();
User.BindValueChanged(onUserChanged, true);
}
private void onUserChanged(ValueChangedEvent<User> user)
{
text.Text = string.Empty;
var usernames = user.NewValue?.PreviousUsernames;
if (usernames?.Any() ?? false)
{
text.Text = string.Join(", ", usernames);
Show();
return;
}
Hide();
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
hideContent();
}
private void showContent()
{
text.FadeIn(duration, Easing.OutQuint);
header.FadeIn(duration, Easing.OutQuint);
background.FadeIn(duration, Easing.OutQuint);
this.MoveToY(-move_offset, duration, Easing.OutQuint);
}
private void hideContent()
{
text.FadeOut(duration, Easing.OutQuint);
header.FadeOut(duration, Easing.OutQuint);
background.FadeOut(duration, Easing.OutQuint);
this.MoveToY(0, duration, Easing.OutQuint);
}
private class HoverIconContainer : Container
{
public Action ActivateHover;
public HoverIconContainer()
{
AutoSizeAxes = Axes.Both;
Child = new SpriteIcon
{
Margin = new MarginPadding { Top = 6, Left = margin, Right = margin * 2 },
Size = new Vector2(15),
Icon = FontAwesome.Solid.IdCard,
};
}
protected override bool OnHover(HoverEvent e)
{
ActivateHover?.Invoke();
return base.OnHover(e);
}
}
}
}

View File

@ -196,17 +196,30 @@ namespace osu.Game.Overlays.Profile.Header.Components
}
}
public string TooltipText => Statistics.Value?.Ranks.Global == null ? "" : $"#{ranks[dayIndex].Value:#,##0}|{ranked_days - ranks[dayIndex].Key + 1}";
public object TooltipContent
{
get
{
if (Statistics.Value?.Ranks.Global == null)
return null;
var days = ranked_days - ranks[dayIndex].Key + 1;
return new TooltipDisplayContent
{
Rank = $"#{ranks[dayIndex].Value:#,##0}",
Time = days == 0 ? "now" : $"{days} days ago"
};
}
}
public ITooltip GetCustomTooltip() => new RankGraphTooltip();
public class RankGraphTooltip : VisibilityContainer, ITooltip
private class RankGraphTooltip : VisibilityContainer, ITooltip
{
private readonly OsuSpriteText globalRankingText, timeText;
private readonly Box background;
public string TooltipText { get; set; }
public RankGraphTooltip()
{
AutoSizeAxes = Axes.Both;
@ -260,11 +273,14 @@ namespace osu.Game.Overlays.Profile.Header.Components
background.Colour = colours.GreySeafoamDark;
}
public void Refresh()
public bool SetContent(object content)
{
var info = TooltipText.Split('|');
globalRankingText.Text = info[0];
timeText.Text = info[1] == "0" ? "now" : $"{info[1]} days ago";
if (!(content is TooltipDisplayContent info))
return false;
globalRankingText.Text = info.Rank;
timeText.Text = info.Time;
return true;
}
private bool instantMove = true;
@ -280,9 +296,24 @@ namespace osu.Game.Overlays.Profile.Header.Components
this.MoveTo(pos, 200, Easing.OutQuint);
}
public void Refresh()
{
}
public string TooltipText
{
set => throw new InvalidOperationException();
}
protected override void PopIn() => this.FadeIn(200, Easing.OutQuint);
protected override void PopOut() => this.FadeOut(200, Easing.OutQuint);
}
private class TooltipDisplayContent
{
public string Rank;
public string Time;
}
}
}

View File

@ -1,7 +1,10 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Configuration;
namespace osu.Game.Overlays.Settings.Sections.Audio
@ -13,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Children = new[]
Children = new Drawable[]
{
new SettingsCheckbox
{
@ -25,6 +28,12 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
LabelText = "osu! music theme",
Bindable = config.GetBindable<bool>(OsuSetting.MenuMusic)
},
new SettingsDropdown<IntroSequence>
{
LabelText = "Intro sequence",
Bindable = config.GetBindable<IntroSequence>(OsuSetting.IntroSequence),
Items = Enum.GetValues(typeof(IntroSequence)).Cast<IntroSequence>()
},
};
}
}

View File

@ -14,7 +14,7 @@ namespace osu.Game.Overlays.Toolbar
}
[BackgroundDependencyLoader(true)]
private void load(MusicController music)
private void load(NowPlayingOverlay music)
{
StateContainer = music;
}

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
@ -57,7 +58,12 @@ namespace osu.Game.Rulesets.Edit
this.drawableRuleset = drawableRuleset;
InternalChild = drawableRuleset;
}
[BackgroundDependencyLoader]
private void load()
{
drawableRuleset.FrameStablePlayback = false;
Playfield.DisplayJudgements.Value = false;
}

View File

@ -398,7 +398,7 @@ namespace osu.Game.Rulesets.Scoring
if (rollingMaxBaseScore != 0)
Accuracy.Value = baseScore / rollingMaxBaseScore;
TotalScore.Value = getScore(Mode.Value) * scoreMultiplier;
TotalScore.Value = getScore(Mode.Value);
}
private double getScore(ScoringMode mode)
@ -407,11 +407,11 @@ namespace osu.Game.Rulesets.Scoring
{
default:
case ScoringMode.Standardised:
return max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo.Value / maxHighestCombo) + bonusScore;
return (max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo.Value / maxHighestCombo) + bonusScore) * scoreMultiplier;
case ScoringMode.Classic:
// should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1)
return bonusScore + baseScore * (1 + Math.Max(0, HighestCombo.Value - 1) / 25);
return bonusScore + baseScore * ((1 + Math.Max(0, HighestCombo.Value - 1) * scoreMultiplier) / 25);
}
}

View File

@ -62,6 +62,15 @@ namespace osu.Game.Rulesets.UI
public override GameplayClock FrameStableClock => frameStabilityContainer.GameplayClock;
/// <summary>
/// Whether to enable frame-stable playback.
/// </summary>
internal bool FrameStablePlayback
{
get => frameStabilityContainer.FrameStablePlayback;
set => frameStabilityContainer.FrameStablePlayback = value;
}
/// <summary>
/// Invoked when a <see cref="JudgementResult"/> has been applied by a <see cref="DrawableHitObject"/>.
/// </summary>

View File

@ -24,6 +24,11 @@ namespace osu.Game.Rulesets.UI
/// </summary>
public int MaxCatchUpFrames { get; set; } = 5;
/// <summary>
/// Whether to enable frame-stable playback.
/// </summary>
internal bool FrameStablePlayback = true;
[Cached]
public GameplayClock GameplayClock { get; }
@ -113,7 +118,13 @@ namespace osu.Game.Rulesets.UI
try
{
if (firstConsumption)
if (!FrameStablePlayback)
{
manualClock.CurrentTime = newProposedTime;
requireMoreUpdateLoops = false;
return;
}
else if (firstConsumption)
{
// On the first update, frame-stability seeking would result in unexpected/unwanted behaviour.
// Instead we perform an initial seek to the proposed time.

View File

@ -11,8 +11,11 @@ namespace osu.Game.Screens
{
public abstract class BackgroundScreen : Screen, IEquatable<BackgroundScreen>
{
protected BackgroundScreen()
private readonly bool animateOnEnter;
protected BackgroundScreen(bool animateOnEnter = true)
{
this.animateOnEnter = animateOnEnter;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
}
@ -39,11 +42,14 @@ namespace osu.Game.Screens
public override void OnEntering(IScreen last)
{
this.FadeOut();
this.MoveToX(x_movement_amount);
if (animateOnEnter)
{
this.FadeOut();
this.MoveToX(x_movement_amount);
this.FadeIn(transition_length, Easing.InOutQuart);
this.MoveToX(0, transition_length, Easing.InOutQuart);
this.FadeIn(transition_length, Easing.InOutQuart);
this.MoveToX(0, transition_length, Easing.InOutQuart);
}
base.OnEntering(last);
}

View File

@ -25,6 +25,11 @@ namespace osu.Game.Screens.Backgrounds
private Bindable<User> user;
private Bindable<Skin> skin;
public BackgroundScreenDefault(bool animateOnEnter = true)
: base(animateOnEnter)
{
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api, SkinManager skinManager)
{

View File

@ -9,6 +9,8 @@ using osu.Framework.Graphics.Shaders;
using osu.Game.Screens.Menu;
using osuTK;
using osu.Framework.Screens;
using osu.Game.Configuration;
using IntroSequence = osu.Game.Configuration.IntroSequence;
namespace osu.Game.Screens
{
@ -45,6 +47,8 @@ namespace osu.Game.Screens
private OsuScreen loadableScreen;
private ShaderPrecompiler precompiler;
private IntroSequence introSequence;
protected virtual OsuScreen CreateLoadableScreen()
{
if (showDisclaimer)
@ -53,7 +57,17 @@ namespace osu.Game.Screens
return getIntroSequence();
}
private IntroScreen getIntroSequence() => new IntroCircles();
private IntroScreen getIntroSequence()
{
switch (introSequence)
{
case IntroSequence.Circles:
return new IntroCircles();
default:
return new IntroTriangles();
}
}
protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler();
@ -79,9 +93,10 @@ namespace osu.Game.Screens
}
[BackgroundDependencyLoader]
private void load(OsuGameBase game)
private void load(OsuGameBase game, OsuConfigManager config)
{
showDisclaimer = game.IsDeployedBuild;
introSequence = config.Get<IntroSequence>(OsuSetting.IntroSequence);
}
/// <summary>

View File

@ -0,0 +1,413 @@
// 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.IO;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.Bindables;
using osu.Framework.Screens;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Framework.Graphics.Video;
using osu.Framework.MathUtils;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.IO.Archives;
using osu.Game.Rulesets;
using osu.Game.Screens.Backgrounds;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Menu
{
public class IntroTriangles : IntroScreen
{
private const string menu_music_beatmap_hash = "a1556d0801b3a6b175dda32ef546f0ec812b400499f575c44fccbe9c67f9b1e5";
private SampleChannel welcome;
protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false)
{
Alpha = 0,
};
[Resolved]
private AudioManager audio { get; set; }
private Bindable<bool> menuMusic;
private Track track;
private WorkingBeatmap introBeatmap;
private BackgroundScreenDefault background;
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game)
{
menuMusic = config.GetBindable<bool>(OsuSetting.MenuMusic);
BeatmapSetInfo setInfo = null;
if (!menuMusic.Value)
{
var sets = beatmaps.GetAllUsableBeatmapSets();
if (sets.Count > 0)
setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID);
}
if (setInfo == null)
{
setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == menu_music_beatmap_hash);
if (setInfo == null)
{
// we need to import the default menu background beatmap
setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream(@"Tracks/triangles.osz"), "triangles.osz")).Result;
setInfo.Protected = true;
beatmaps.Update(setInfo);
}
}
introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
track = introBeatmap.Track;
track.Reset();
if (config.Get<bool>(OsuSetting.MenuVoice) && !menuMusic.Value)
// triangles has welcome sound included in the track. only play this if the user doesn't want menu music.
welcome = audio.Samples.Get(@"welcome");
}
protected override void LogoArriving(OsuLogo logo, bool resuming)
{
base.LogoArriving(logo, resuming);
logo.Triangles = true;
if (!resuming)
{
Beatmap.Value = introBeatmap;
introBeatmap = null;
PrepareMenuLoad();
LoadComponentAsync(new TrianglesIntroSequence(logo, background)
{
RelativeSizeAxes = Axes.Both,
Clock = new FramedClock(menuMusic.Value ? track : null),
LoadMenu = LoadMenu
}, t =>
{
AddInternal(t);
welcome?.Play();
// Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu.
if (menuMusic.Value)
track.Start();
});
}
}
public override void OnResuming(IScreen last)
{
base.OnResuming(last);
background.FadeOut(100);
}
public override void OnSuspending(IScreen next)
{
track = null;
base.OnSuspending(next);
}
private class TrianglesIntroSequence : CompositeDrawable
{
private readonly OsuLogo logo;
private readonly BackgroundScreenDefault background;
private OsuSpriteText welcomeText;
private RulesetFlow rulesets;
private Container rulesetsScale;
private Drawable logoContainerSecondary;
private Drawable logoContainer;
private GlitchingTriangles triangles;
public Action LoadMenu;
public TrianglesIntroSequence(OsuLogo logo, BackgroundScreenDefault background)
{
this.logo = logo;
this.background = background;
}
private OsuGameBase game;
[BackgroundDependencyLoader]
private void load(TextureStore textures, OsuGameBase game)
{
this.game = game;
InternalChildren = new[]
{
triangles = new GlitchingTriangles
{
Alpha = 0,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(0.4f, 0.16f)
},
welcomeText = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding { Bottom = 10 },
Font = OsuFont.GetFont(weight: FontWeight.Light, size: 42),
Alpha = 1,
Spacing = new Vector2(5),
},
rulesetsScale = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
rulesets = new RulesetFlow()
}
},
logoContainerSecondary = new Container
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Child = logoContainer = new LazerLogo(textures.GetStream("Menu/logo-triangles.mp4"))
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
},
};
}
private const double text_1 = 200;
private const double text_2 = 400;
private const double text_3 = 700;
private const double text_4 = 900;
private const double text_glitch = 1060;
private const double rulesets_1 = 1450;
private const double rulesets_2 = 1650;
private const double rulesets_3 = 1850;
private const double logo_scale_duration = 920;
private const double logo_1 = 2080;
private const double logo_2 = logo_1 + logo_scale_duration;
protected override void LoadComplete()
{
base.LoadComplete();
const float scale_start = 1.2f;
const float scale_adjust = 0.8f;
rulesets.Hide();
logoContainer.Hide();
background.Hide();
using (BeginAbsoluteSequence(0, true))
{
using (BeginDelayedSequence(text_1, true))
welcomeText.FadeIn().OnComplete(t => t.Text = "wel");
using (BeginDelayedSequence(text_2, true))
welcomeText.FadeIn().OnComplete(t => t.Text = "welcome");
using (BeginDelayedSequence(text_3, true))
welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to");
using (BeginDelayedSequence(text_4, true))
{
welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to osu!");
welcomeText.TransformTo(nameof(welcomeText.Spacing), new Vector2(50, 0), 5000);
}
using (BeginDelayedSequence(text_glitch, true))
triangles.FadeIn();
using (BeginDelayedSequence(rulesets_1, true))
{
rulesetsScale.ScaleTo(0.8f, 1000);
rulesets.FadeIn().ScaleTo(1).TransformSpacingTo(new Vector2(200, 0));
welcomeText.FadeOut();
triangles.FadeOut();
}
using (BeginDelayedSequence(rulesets_2, true))
{
rulesets.ScaleTo(2).TransformSpacingTo(new Vector2(30, 0));
}
using (BeginDelayedSequence(rulesets_3, true))
{
rulesets.ScaleTo(4).TransformSpacingTo(new Vector2(10, 0));
rulesetsScale.ScaleTo(1.3f, 1000);
}
using (BeginDelayedSequence(logo_1, true))
{
rulesets.FadeOut();
// matching flyte curve y = 0.25x^2 + (max(0, x - 0.7) / 0.3) ^ 5
logoContainer.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint);
logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad);
}
using (BeginDelayedSequence(logo_2, true))
{
logoContainer.FadeOut().OnComplete(_ =>
{
logo.FadeIn();
background.FadeIn();
game.Add(new GameWideFlash());
LoadMenu();
});
}
}
}
private class GameWideFlash : Box
{
private const double flash_length = 1000;
public GameWideFlash()
{
Colour = Color4.White;
RelativeSizeAxes = Axes.Both;
Blending = BlendingMode.Additive;
}
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeOutFromOne(flash_length, Easing.Out);
}
}
private class LazerLogo : CompositeDrawable
{
public LazerLogo(Stream videoStream)
{
Size = new Vector2(960);
InternalChild = new VideoSprite(videoStream)
{
RelativeSizeAxes = Axes.Both,
Clock = new FramedOffsetClock(Clock) { Offset = -logo_1 }
};
}
}
private class RulesetFlow : FillFlowContainer
{
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
var modes = new List<Drawable>();
foreach (var ruleset in rulesets.AvailableRulesets)
{
var icon = new ConstrainedIconContainer
{
Icon = ruleset.CreateInstance().CreateIcon(),
Size = new Vector2(30),
};
modes.Add(icon);
}
AutoSizeAxes = Axes.Both;
Children = modes;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
}
}
private class GlitchingTriangles : CompositeDrawable
{
public GlitchingTriangles()
{
RelativeSizeAxes = Axes.Both;
}
private double? lastGenTime;
private const double time_between_triangles = 22;
protected override void Update()
{
base.Update();
if (lastGenTime == null || Time.Current - lastGenTime > time_between_triangles)
{
lastGenTime = (lastGenTime ?? Time.Current) + time_between_triangles;
Drawable triangle = new OutlineTriangle(RNG.NextBool(), (RNG.NextSingle() + 0.2f) * 80)
{
RelativePositionAxes = Axes.Both,
Position = new Vector2(RNG.NextSingle(), RNG.NextSingle()),
};
AddInternal(triangle);
triangle.FadeOutFromOne(120);
}
}
/// <summary>
/// Represents a sprite that is drawn in a triangle shape, instead of a rectangle shape.
/// </summary>
public class OutlineTriangle : BufferedContainer
{
public OutlineTriangle(bool outlineOnly, float size)
{
Size = new Vector2(size);
InternalChildren = new Drawable[]
{
new Triangle { RelativeSizeAxes = Axes.Both },
};
if (outlineOnly)
{
AddInternal(new Triangle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = Color4.Black,
Size = new Vector2(size - 5),
Blending = BlendingMode.None,
});
}
Blending = BlendingMode.Additive;
CacheDrawnFrameBuffer = true;
}
}
}
}
}
}

View File

@ -25,10 +25,14 @@ namespace osu.Game.Screens.Multi.Match.Components
{
public const float HEIGHT = 200;
public MatchTabControl Tabs;
public readonly BindableBool ShowBeatmapPanel = new BindableBool();
public MatchTabControl Tabs { get; private set; }
public Action RequestBeatmapSelection;
private MatchBeatmapPanel beatmapPanel;
public Header()
{
RelativeSizeAxes = Axes.X;
@ -53,8 +57,14 @@ namespace osu.Game.Screens.Multi.Match.Components
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.4f), Color4.Black.Opacity(0.6f)),
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.7f), Color4.Black.Opacity(0.8f)),
},
beatmapPanel = new MatchBeatmapPanel
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Margin = new MarginPadding { Right = 100 },
}
}
},
new Box
@ -114,6 +124,12 @@ namespace osu.Game.Screens.Multi.Match.Components
beatmapButton.Action = () => RequestBeatmapSelection?.Invoke();
}
protected override void LoadComplete()
{
base.LoadComplete();
ShowBeatmapPanel.BindValueChanged(value => beatmapPanel.FadeTo(value.NewValue ? 1 : 0, 200, Easing.OutQuint), true);
}
private class BeatmapSelectButton : HeaderButton
{
[Resolved(typeof(Room), nameof(Room.RoomID))]

View File

@ -28,7 +28,6 @@ namespace osu.Game.Screens.Multi.Match.Components
private void load()
{
ReadyButton readyButton;
ViewBeatmapButton viewBeatmapButton;
HostInfo hostInfo;
InternalChildren = new Drawable[]
@ -80,7 +79,6 @@ namespace osu.Game.Screens.Multi.Match.Components
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
viewBeatmapButton = new ViewBeatmapButton(),
readyButton = new ReadyButton
{
Action = () => OnStart?.Invoke()
@ -91,11 +89,7 @@ namespace osu.Game.Screens.Multi.Match.Components
},
};
CurrentItem.BindValueChanged(item =>
{
viewBeatmapButton.Beatmap.Value = item.NewValue?.Beatmap;
readyButton.Beatmap.Value = item.NewValue?.Beatmap;
}, true);
CurrentItem.BindValueChanged(item => readyButton.Beatmap.Value = item.NewValue?.Beatmap, true);
hostInfo.Host.BindTo(Host);
}

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.Threading;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Direct;
using osu.Game.Rulesets;
namespace osu.Game.Screens.Multi.Match.Components
{
public class MatchBeatmapPanel : MultiplayerComposite
{
[Resolved]
private IAPIProvider api { get; set; }
[Resolved]
private RulesetStore rulesets { get; set; }
private CancellationTokenSource loadCancellation;
private GetBeatmapSetRequest request;
private DirectGridPanel panel;
public MatchBeatmapPanel()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
CurrentItem.BindValueChanged(item => loadNewPanel(item.NewValue?.Beatmap), true);
}
private void loadNewPanel(BeatmapInfo beatmap)
{
loadCancellation?.Cancel();
request?.Cancel();
panel?.FadeOut(200);
panel?.Expire();
panel = null;
if (beatmap?.OnlineBeatmapID == null)
return;
loadCancellation = new CancellationTokenSource();
request = new GetBeatmapSetRequest(beatmap.OnlineBeatmapID.Value, BeatmapSetLookupType.BeatmapId);
request.Success += res => Schedule(() =>
{
panel = new DirectGridPanel(res.ToBeatmapSet(rulesets));
LoadComponentAsync(panel, AddInternal, loadCancellation.Token);
});
api.Queue(request);
}
}
}

View File

@ -1,46 +0,0 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osuTK;
namespace osu.Game.Screens.Multi.Match.Components
{
public class ViewBeatmapButton : HeaderButton
{
public readonly Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
[Resolved(CanBeNull = true)]
private OsuGame osuGame { get; set; }
public ViewBeatmapButton()
{
RelativeSizeAxes = Axes.Y;
Size = new Vector2(200, 1);
Text = "View beatmap";
}
[BackgroundDependencyLoader]
private void load()
{
if (osuGame != null)
Beatmap.BindValueChanged(beatmap => updateAction(beatmap.NewValue), true);
}
private void updateAction(BeatmapInfo beatmap)
{
if (beatmap == null)
{
Enabled.Value = false;
return;
}
Action = () => osuGame.ShowBeatmap(beatmap.OnlineBeatmapID ?? 0);
Enabled.Value = true;
}
}
}

View File

@ -7,6 +7,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.GameTypes;
@ -18,7 +19,8 @@ using PlaylistItem = osu.Game.Online.Multiplayer.PlaylistItem;
namespace osu.Game.Screens.Multi.Match
{
public class MatchSubScreen : MultiplayerSubScreen
[Cached(typeof(IPreviewTrackOwner))]
public class MatchSubScreen : MultiplayerSubScreen, IPreviewTrackOwner
{
public override bool DisallowExternalBeatmapRulesetChanges => true;
@ -44,6 +46,9 @@ namespace osu.Game.Screens.Multi.Match
[Resolved]
private BeatmapManager beatmapManager { get; set; }
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; }
[Resolved(CanBeNull = true)]
private OsuGame game { get; set; }
@ -146,18 +151,12 @@ namespace osu.Game.Screens.Multi.Match
{
const float fade_duration = 500;
if (tab.NewValue is SettingsMatchPage)
{
settings.Show();
info.FadeOut(fade_duration, Easing.OutQuint);
bottomRow.FadeOut(fade_duration, Easing.OutQuint);
}
else
{
settings.Hide();
info.FadeIn(fade_duration, Easing.OutQuint);
bottomRow.FadeIn(fade_duration, Easing.OutQuint);
}
var settingsDisplayed = tab.NewValue is SettingsMatchPage;
header.ShowBeatmapPanel.Value = !settingsDisplayed;
settings.State.Value = settingsDisplayed ? Visibility.Visible : Visibility.Hidden;
info.FadeTo(settingsDisplayed ? 0 : 1, fade_duration, Easing.OutQuint);
bottomRow.FadeTo(settingsDisplayed ? 0 : 1, fade_duration, Easing.OutQuint);
}, true);
chat.Exit += () =>
@ -179,8 +178,8 @@ namespace osu.Game.Screens.Multi.Match
public override bool OnExiting(IScreen next)
{
RoomManager?.PartRoom();
Mods.Value = Array.Empty<Mod>();
previewTrackManager.StopAnyPlaying(this);
return base.OnExiting(next);
}
@ -198,6 +197,8 @@ namespace osu.Game.Screens.Multi.Match
if (e.NewValue?.Ruleset != null)
Ruleset.Value = e.NewValue.Ruleset;
previewTrackManager.StopAnyPlaying(this);
}
/// <summary>
@ -223,6 +224,8 @@ namespace osu.Game.Screens.Multi.Match
private void onStart()
{
previewTrackManager.StopAnyPlaying(this);
switch (type.Value)
{
default:

View File

@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play
base.LoadComplete();
}
protected override bool ShowDimContent => ShowStoryboard.Value && UserDimLevel.Value < 1;
protected override bool ShowDimContent => ShowStoryboard.Value && DimLevel < 1;
private void initializeStoryboard(bool async)
{

View File

@ -24,7 +24,7 @@ using osu.Game.Screens.Select.Carousel;
namespace osu.Game.Screens.Select
{
public class BeatmapCarousel : OsuScrollContainer
public class BeatmapCarousel : CompositeDrawable
{
private const float bleed_top = FilterControl.HEIGHT;
private const float bleed_bottom = Footer.HEIGHT;
@ -61,6 +61,8 @@ namespace osu.Game.Screens.Select
/// </summary>
public bool BeatmapSetsLoaded { get; private set; }
private readonly OsuScrollContainer scroll;
private IEnumerable<CarouselBeatmapSet> beatmapSets => root.Children.OfType<CarouselBeatmapSet>();
public IEnumerable<BeatmapSetInfo> BeatmapSets
@ -110,13 +112,17 @@ namespace osu.Game.Screens.Select
public BeatmapCarousel()
{
root = new CarouselRoot(this);
Child = new OsuContextMenuContainer
InternalChild = new OsuContextMenuContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = scrollableContent = new Container<DrawableCarouselItem>
RelativeSizeAxes = Axes.Both,
Child = scroll = new CarouselScrollContainer
{
RelativeSizeAxes = Axes.X,
Masking = false,
RelativeSizeAxes = Axes.Both,
Child = scrollableContent = new Container<DrawableCarouselItem>
{
RelativeSizeAxes = Axes.X,
}
}
};
}
@ -127,7 +133,7 @@ namespace osu.Game.Screens.Select
config.BindWith(OsuSetting.RandomSelectAlgorithm, RandomAlgorithm);
config.BindWith(OsuSetting.SongSelectRightMouseScroll, RightClickScrollingEnabled);
RightClickScrollingEnabled.ValueChanged += enabled => RightMouseScrollbar = enabled.NewValue;
RightClickScrollingEnabled.ValueChanged += enabled => scroll.RightMouseScrollbar = enabled.NewValue;
RightClickScrollingEnabled.TriggerChange();
loadBeatmapSets(beatmaps.GetAllUsableBeatmapSetsEnumerable());
@ -351,12 +357,12 @@ namespace osu.Game.Screens.Select
/// <summary>
/// The position of the lower visible bound with respect to the current scroll position.
/// </summary>
private float visibleBottomBound => Current + DrawHeight + bleed_bottom;
private float visibleBottomBound => scroll.Current + DrawHeight + bleed_bottom;
/// <summary>
/// The position of the upper visible bound with respect to the current scroll position.
/// </summary>
private float visibleUpperBound => Current - bleed_top;
private float visibleUpperBound => scroll.Current - bleed_top;
public void FlushPendingFilterOperations()
{
@ -628,7 +634,7 @@ namespace osu.Game.Screens.Select
private void updateScrollPosition()
{
if (scrollTarget != null) ScrollTo(scrollTarget.Value);
if (scrollTarget != null) scroll.ScrollTo(scrollTarget.Value);
scrollPositionCache.Validate();
}
@ -688,5 +694,35 @@ namespace osu.Game.Screens.Select
base.PerformSelection();
}
}
private class CarouselScrollContainer : OsuScrollContainer
{
private bool rightMouseScrollBlocked;
protected override bool OnMouseDown(MouseDownEvent e)
{
if (e.Button == MouseButton.Right)
{
// we need to block right click absolute scrolling when hovering a carousel item so context menus can display.
// this can be reconsidered when we have an alternative to right click scrolling.
if (GetContainingInputManager().HoveredDrawables.OfType<DrawableCarouselItem>().Any())
{
rightMouseScrollBlocked = true;
return false;
}
}
rightMouseScrollBlocked = false;
return base.OnMouseDown(e);
}
protected override bool OnDragStart(DragStartEvent e)
{
if (rightMouseScrollBlocked)
return false;
return base.OnDragStart(e);
}
}
}
}

View File

@ -1,13 +1,22 @@
// 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.ComponentModel;
namespace osu.Game.Screens.Select.Leaderboards
{
public enum BeatmapLeaderboardScope
{
[Description("Local Ranking")]
Local,
[Description("Country Ranking")]
Country,
[Description("Global Ranking")]
Global,
[Description("Friend Ranking")]
Friend,
}
}

View File

@ -157,7 +157,6 @@ namespace osu.Game.Screens.Select
},
Child = Carousel = new BeatmapCarousel
{
Masking = false,
RelativeSizeAxes = Axes.Both,
Size = new Vector2(1 - wedged_container_size.X, 1),
Anchor = Anchor.CentreRight,

View File

@ -20,6 +20,9 @@ namespace osu.Game.Users
[JsonProperty(@"username")]
public string Username;
[JsonProperty(@"previous_usernames")]
public string[] PreviousUsernames;
[JsonProperty(@"country")]
public Country Country;

View File

@ -14,8 +14,8 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.2" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.731.1" />
<PackageReference Include="ppy.osu.Framework" Version="2019.809.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.809.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.816.0" />
<PackageReference Include="SharpCompress" Version="0.23.0" />
<PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />

View File

@ -3,7 +3,8 @@
<ProjectTypeGuids>{FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<IPhoneResourcePrefix>Resources</IPhoneResourcePrefix>
<RestoreProjectStyle>PackageReference</RestoreProjectStyle>
<DefaultMtouchExtraArgs>--nolinkaway -gcc_flags "-lstdc++ -framework AudioToolbox -framework SystemConfiguration -framework CFNetwork -framework Accelerate</DefaultMtouchExtraArgs>
<DefaultMtouchExtraArgs>--nolinkaway</DefaultMtouchExtraArgs>
<DefaultMtouchGccFlags>-lstdc++ -lbz2 -framework AudioToolbox -framework AVFoundation -framework CoreMedia -framework VideoToolbox -framework SystemConfiguration -framework CFNetwork -framework Accelerate</DefaultMtouchGccFlags>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
<DebugSymbols>true</DebugSymbols>
@ -24,7 +25,7 @@
<DeviceSpecificBuild>false</DeviceSpecificBuild>
<MtouchVerbosity></MtouchVerbosity>
<LangVersion>Default</LangVersion>
<MtouchExtraArgs>$(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a"</MtouchExtraArgs>
<MtouchExtraArgs>$(DefaultMtouchExtraArgs) -gcc_flags "$(DefaultMtouchGccFlags)"</MtouchExtraArgs>
<OptimizePNGs>false</OptimizePNGs>
<MtouchI18n>cjk,mideast,other,rare,west</MtouchI18n>
</PropertyGroup>
@ -44,7 +45,7 @@
<MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler>
<MtouchVerbosity></MtouchVerbosity>
<LangVersion>Default</LangVersion>
<MtouchExtraArgs>$(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a"</MtouchExtraArgs>
<MtouchExtraArgs>$(DefaultMtouchExtraArgs) -gcc_flags "$(DefaultMtouchGccFlags)"</MtouchExtraArgs>
<OptimizePNGs>false</OptimizePNGs>
<MtouchI18n>cjk,mideast,other,rare,west</MtouchI18n>
</PropertyGroup>
@ -62,7 +63,7 @@
<MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler>
<MtouchVerbosity></MtouchVerbosity>
<LangVersion>Default</LangVersion>
<MtouchExtraArgs>$(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a"</MtouchExtraArgs>
<MtouchExtraArgs>$(DefaultMtouchExtraArgs) -gcc_flags "$(DefaultMtouchGccFlags)"</MtouchExtraArgs>
<OptimizePNGs>false</OptimizePNGs>
<MtouchI18n>cjk,mideast,other,rare,west</MtouchI18n>
</PropertyGroup>
@ -86,10 +87,22 @@
<MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler>
<MtouchVerbosity></MtouchVerbosity>
<LangVersion>Default</LangVersion>
<MtouchExtraArgs>$(DefaultMtouchExtraArgs) -force_load $(OutputPath)\libbass.a -force_load $(OutputPath)\libbass_fx.a"</MtouchExtraArgs>
<MtouchExtraArgs>$(DefaultMtouchExtraArgs) -gcc_flags "$(DefaultMtouchGccFlags)"</MtouchExtraArgs>
<OptimizePNGs>false</OptimizePNGs>
<MtouchI18n>cjk,mideast,other,rare,west</MtouchI18n>
</PropertyGroup>
<ItemGroup>
<NativeReference Include="$(OutputPath)\libbass.a;$(OutputPath)\libbass_fx.a">
<Kind>Static</Kind>
<SmartLink>False</SmartLink>
<ForceLoad>True</ForceLoad>
</NativeReference>
<NativeReference Include="$(OutputPath)\libavcodec.a;$(OutputPath)\libavdevice.a;$(OutputPath)\libavfilter.a;$(OutputPath)\libavformat.a;$(OutputPath)\libavutil.a;$(OutputPath)\libswresample.a;$(OutputPath)\libswscale.a">
<Kind>Static</Kind>
<SmartLink>False</SmartLink>
<ForceLoad>True</ForceLoad>
</NativeReference>
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Xml" />
@ -104,13 +117,13 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.1" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.1" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.731.1" />
<PackageReference Include="ppy.osu.Framework" Version="2019.809.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.809.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.809.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.816.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.816.0" />
<PackageReference Include="SharpCompress" Version="0.22.0" />
<PackageReference Include="NUnit" Version="3.11.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="4.5.0" />
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2019.307.0" ExcludeAssets="all" />
<PackageReference Include="ppy.osu.Framework.NativeLibs" Version="2019.813.0" ExcludeAssets="all" />
</ItemGroup>
</Project>

View File

@ -31,6 +31,10 @@
<true/>
<key>UIStatusBarHidden</key>
<true/>
<key>NSCameraUsageDescription</key>
<string>We don't really use the camera.</string>
<key>NSMicrophoneUsageDescription</key>
<string>We don't really use the microphone.</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>

Binary file not shown.

Binary file not shown.

View File

@ -19,12 +19,6 @@
<ItemGroup>
<None Include="Info.plist" />
<None Include="Entitlements.plist" />
<None Include="libbass_fx.a">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Include="libbass.a">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Compile Include="Application.cs" />