2019-05-15 08:36:29 +00:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2019-01-24 08:43:03 +00:00
// See the LICENCE file in the repository root for full licence text.
2018-04-13 09:19:50 +00:00
2022-06-17 07:37:17 +00:00
#nullable disable
2016-10-06 12:10:01 +00:00
using System ;
2018-01-26 10:30:29 +00:00
using System.Collections.Generic ;
2019-02-28 08:17:51 +00:00
using System.Diagnostics ;
2017-02-04 21:03:39 +00:00
using System.Linq ;
2017-12-25 09:22:58 +00:00
using System.Threading ;
2017-02-24 09:10:37 +00:00
using System.Threading.Tasks ;
2020-07-24 05:10:05 +00:00
using Humanizer ;
2020-05-09 10:13:18 +00:00
using JetBrains.Annotations ;
2022-06-03 06:37:17 +00:00
using osu.Framework ;
2021-11-22 08:40:43 +00:00
using osu.Framework.Allocation ;
2018-01-31 09:11:38 +00:00
using osu.Framework.Audio ;
2019-02-21 10:04:31 +00:00
using osu.Framework.Bindables ;
2021-11-22 08:40:43 +00:00
using osu.Framework.Configuration ;
2018-07-13 11:32:22 +00:00
using osu.Framework.Extensions.IEnumerableExtensions ;
2022-05-12 03:06:51 +00:00
using osu.Framework.Extensions.TypeExtensions ;
2021-11-22 08:40:43 +00:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
2019-03-27 10:29:27 +00:00
using osu.Framework.Graphics.Sprites ;
2018-02-22 01:08:22 +00:00
using osu.Framework.Input ;
2017-08-11 07:11:46 +00:00
using osu.Framework.Input.Bindings ;
2021-09-16 09:26:12 +00:00
using osu.Framework.Input.Events ;
2022-06-28 07:29:19 +00:00
using osu.Framework.Localisation ;
2021-11-22 08:40:43 +00:00
using osu.Framework.Logging ;
using osu.Framework.Screens ;
2017-03-04 12:35:12 +00:00
using osu.Framework.Threading ;
2018-07-10 16:32:10 +00:00
using osu.Game.Beatmaps ;
2020-09-04 18:52:07 +00:00
using osu.Game.Collections ;
2021-11-22 08:40:43 +00:00
using osu.Game.Configuration ;
using osu.Game.Database ;
using osu.Game.Extensions ;
2017-03-04 10:02:36 +00:00
using osu.Game.Graphics ;
2019-01-04 04:29:37 +00:00
using osu.Game.Graphics.Containers ;
2019-06-25 07:55:49 +00:00
using osu.Game.Graphics.UserInterface ;
2018-11-19 17:48:59 +00:00
using osu.Game.Input ;
2017-08-11 07:11:46 +00:00
using osu.Game.Input.Bindings ;
2021-11-22 08:40:43 +00:00
using osu.Game.IO ;
using osu.Game.Localisation ;
2022-06-20 18:04:21 +00:00
using osu.Game.Online ;
2021-11-22 08:40:43 +00:00
using osu.Game.Online.API.Requests.Responses ;
2018-09-14 03:06:04 +00:00
using osu.Game.Online.Chat ;
2021-11-22 08:40:43 +00:00
using osu.Game.Overlays ;
2020-09-04 07:22:37 +00:00
using osu.Game.Overlays.Music ;
2021-11-22 08:40:43 +00:00
using osu.Game.Overlays.Notifications ;
using osu.Game.Overlays.Toolbar ;
2018-03-03 18:08:35 +00:00
using osu.Game.Overlays.Volume ;
2021-11-22 08:40:43 +00:00
using osu.Game.Performance ;
2019-12-26 05:52:08 +00:00
using osu.Game.Rulesets.Mods ;
2018-11-28 07:12:57 +00:00
using osu.Game.Scoring ;
2021-11-22 08:40:43 +00:00
using osu.Game.Screens ;
using osu.Game.Screens.Menu ;
2020-06-15 11:23:35 +00:00
using osu.Game.Screens.Play ;
using osu.Game.Screens.Ranking ;
2018-07-10 16:32:10 +00:00
using osu.Game.Screens.Select ;
2021-11-22 08:40:43 +00:00
using osu.Game.Skinning ;
2021-04-29 08:20:22 +00:00
using osu.Game.Skinning.Editor ;
2021-11-22 08:40:43 +00:00
using osu.Game.Updater ;
2021-11-05 04:53:00 +00:00
using osu.Game.Users ;
2021-11-22 08:40:43 +00:00
using osu.Game.Utils ;
using osuTK.Graphics ;
2022-05-11 03:55:15 +00:00
using Sentry ;
using Logger = osu . Framework . Logging . Logger ;
2018-04-13 09:19:50 +00:00
2016-08-26 03:28:23 +00:00
namespace osu.Game
{
2018-04-08 03:58:34 +00:00
/// <summary>
/// The full osu! experience. Builds on top of <see cref="OsuGameBase"/> to add menus and binding logic
/// for initial components that are generally retrieved via DI.
/// </summary>
2022-06-20 18:04:21 +00:00
public class OsuGame : OsuGameBase , IKeyBindingHandler < GlobalAction > , ILocalUserPlayInfo , IPerformFromScreenRunner , IOverlayManager , ILinkHandler
2016-08-26 03:28:23 +00:00
{
2021-08-13 07:29:36 +00:00
/// <summary>
/// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
/// </summary>
2021-08-13 07:35:22 +00:00
protected const float SIDE_OVERLAY_OFFSET_RATIO = 0.05f ;
2021-08-06 19:36:40 +00:00
2016-10-01 09:01:52 +00:00
public Toolbar Toolbar ;
2018-04-13 09:19:50 +00:00
2022-05-30 08:54:09 +00:00
private ChatOverlay chatOverlay ;
2018-11-23 02:00:17 +00:00
private ChannelManager channelManager ;
2018-04-13 09:19:50 +00:00
2020-07-19 02:37:38 +00:00
[NotNull]
2021-08-06 19:36:40 +00:00
protected readonly NotificationOverlay Notifications = new NotificationOverlay ( ) ;
2018-04-13 09:19:50 +00:00
2020-04-21 07:00:00 +00:00
private BeatmapListingOverlay beatmapListing ;
2020-01-11 19:43:51 +00:00
2020-04-16 09:05:51 +00:00
private DashboardOverlay dashboard ;
2018-04-13 09:19:50 +00:00
2020-07-16 11:48:40 +00:00
private NewsOverlay news ;
2018-04-13 09:19:50 +00:00
2017-06-15 09:03:33 +00:00
private UserProfileOverlay userProfile ;
2018-04-13 09:19:50 +00:00
2017-09-25 09:58:03 +00:00
private BeatmapSetOverlay beatmapSetOverlay ;
2018-04-13 09:19:50 +00:00
2021-04-22 09:16:12 +00:00
private WikiOverlay wikiOverlay ;
2021-10-12 02:41:59 +00:00
private ChangelogOverlay changelogOverlay ;
2021-05-03 06:15:50 +00:00
private SkinEditorOverlay skinEditor ;
2021-05-06 05:17:30 +00:00
private Container overlayContent ;
private Container rightFloatingOverlayContent ;
private Container leftFloatingOverlayContent ;
private Container topMostOverlayContent ;
2021-12-13 03:48:15 +00:00
protected ScalingContainer ScreenContainer { get ; private set ; }
2021-05-06 05:17:30 +00:00
2021-08-07 15:52:27 +00:00
protected Container ScreenOffsetContainer { get ; private set ; }
2021-05-06 05:17:30 +00:00
2021-08-29 03:13:01 +00:00
private Container overlayOffsetContainer ;
2021-05-06 05:17:30 +00:00
[Resolved]
private FrameworkConfigManager frameworkConfig { get ; set ; }
2020-12-22 05:28:26 +00:00
[Cached]
private readonly DifficultyRecommender difficultyRecommender = new DifficultyRecommender ( ) ;
2021-05-09 15:12:58 +00:00
[Cached]
2021-11-25 08:12:15 +00:00
private readonly LegacyImportManager legacyImportManager = new LegacyImportManager ( ) ;
2021-05-09 15:12:58 +00:00
2018-10-02 01:12:07 +00:00
[Cached]
private readonly ScreenshotManager screenshotManager = new ScreenshotManager ( ) ;
2018-04-13 12:13:09 +00:00
2019-11-12 14:08:16 +00:00
protected SentryLogger SentryLogger ;
2018-08-03 10:25:55 +00:00
2021-01-24 18:46:10 +00:00
public virtual StableStorage GetStorageForStableInstall ( ) = > null ;
2018-04-13 09:19:50 +00:00
2021-08-29 03:13:01 +00:00
private float toolbarOffset = > ( Toolbar ? . Position . Y ? ? 0 ) + ( Toolbar ? . DrawHeight ? ? 0 ) ;
2018-04-13 09:19:50 +00:00
2018-11-19 17:48:59 +00:00
private IdleTracker idleTracker ;
2020-09-02 18:55:26 +00:00
/// <summary>
/// Whether overlays should be able to be opened game-wide. Value is sourced from the current active screen.
/// </summary>
2020-08-27 18:07:24 +00:00
public readonly IBindable < OverlayActivation > OverlayActivationMode = new Bindable < OverlayActivation > ( ) ;
2018-04-13 09:19:50 +00:00
2020-10-06 12:09:35 +00:00
/// <summary>
2020-10-07 05:44:49 +00:00
/// Whether the local user is currently interacting with the game in a way that should not be interrupted.
2020-10-06 12:09:35 +00:00
/// </summary>
2020-10-08 09:25:40 +00:00
/// <remarks>
/// This is exclusively managed by <see cref="Player"/>. If other components are mutating this state, a more
/// resilient method should be used to ensure correct state.
/// </remarks>
public Bindable < bool > LocalUserPlaying = new BindableBool ( ) ;
2018-04-13 09:19:50 +00:00
2019-07-29 05:30:46 +00:00
protected OsuScreenStack ScreenStack ;
2019-07-31 10:47:41 +00:00
2019-07-29 05:30:46 +00:00
protected BackButton BackButton ;
2020-05-12 03:49:35 +00:00
protected SettingsOverlay Settings ;
2019-07-31 10:47:41 +00:00
2022-06-15 00:34:08 +00:00
protected FirstRunSetupOverlay FirstRunOverlay { get ; private set ; }
2022-04-06 08:42:10 +00:00
2018-03-03 18:08:35 +00:00
private VolumeOverlay volume ;
2022-02-18 07:06:38 +00:00
2019-01-23 11:52:00 +00:00
private OsuLogo osuLogo ;
private MainMenu menuScreen ;
2019-07-09 08:59:40 +00:00
2022-01-15 18:42:38 +00:00
private VersionManager versionManager ;
2020-05-09 10:13:18 +00:00
[CanBeNull]
2019-07-09 08:59:40 +00:00
private IntroScreen introScreen ;
2018-04-13 09:19:50 +00:00
2021-11-22 08:40:43 +00:00
private Bindable < string > configRuleset ;
2018-04-13 09:19:50 +00:00
2021-10-28 04:09:03 +00:00
private Bindable < float > uiScale ;
2021-11-23 07:04:55 +00:00
private Bindable < string > configSkin ;
2018-04-13 09:19:50 +00:00
2017-03-23 04:41:50 +00:00
private readonly string [ ] args ;
2018-04-13 09:19:50 +00:00
2021-08-31 21:29:16 +00:00
private readonly List < OsuFocusedOverlayContainer > focusedOverlays = new List < OsuFocusedOverlayContainer > ( ) ;
2022-05-04 19:53:04 +00:00
private readonly List < OverlayContainer > externalOverlays = new List < OverlayContainer > ( ) ;
2018-06-06 07:17:51 +00:00
2019-03-01 03:20:31 +00:00
private readonly List < OverlayContainer > visibleBlockingOverlays = new List < OverlayContainer > ( ) ;
2018-04-13 09:19:50 +00:00
2016-10-21 09:25:22 +00:00
public OsuGame ( string [ ] args = null )
2016-10-10 20:56:01 +00:00
{
this . args = args ;
2018-06-21 05:43:38 +00:00
forwardLoggedErrorsToNotifications ( ) ;
2018-08-03 10:25:55 +00:00
2019-11-12 14:08:16 +00:00
SentryLogger = new SentryLogger ( this ) ;
2016-10-10 20:56:01 +00:00
}
2018-04-13 09:19:50 +00:00
2022-05-05 13:47:10 +00:00
#region IOverlayManager
IBindable < OverlayActivation > IOverlayManager . OverlayActivationMode = > OverlayActivationMode ;
2019-03-01 03:20:31 +00:00
private void updateBlockingOverlayFade ( ) = >
2021-12-13 03:48:15 +00:00
ScreenContainer . FadeColour ( visibleBlockingOverlays . Any ( ) ? OsuColour . Gray ( 0.5f ) : Color4 . White , 500 , Easing . OutQuint ) ;
2019-03-01 03:20:31 +00:00
2022-05-05 13:47:10 +00:00
IDisposable IOverlayManager . RegisterBlockingOverlay ( OverlayContainer overlayContainer )
2022-05-04 19:53:04 +00:00
{
if ( overlayContainer . Parent ! = null )
2022-05-05 13:47:10 +00:00
throw new ArgumentException ( $@"Overlays registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} should not be added to the scene graph." ) ;
2022-05-04 19:53:04 +00:00
if ( externalOverlays . Contains ( overlayContainer ) )
2022-05-05 13:47:10 +00:00
throw new ArgumentException ( $@"{overlayContainer} has already been registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} once." ) ;
2022-05-04 19:53:04 +00:00
externalOverlays . Add ( overlayContainer ) ;
overlayContent . Add ( overlayContainer ) ;
2022-05-07 20:50:10 +00:00
if ( overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer )
focusedOverlays . Add ( focusedOverlayContainer ) ;
2022-05-04 19:53:04 +00:00
return new InvokeOnDisposal ( ( ) = > unregisterBlockingOverlay ( overlayContainer ) ) ;
}
2022-05-05 13:47:10 +00:00
void IOverlayManager . ShowBlockingOverlay ( OverlayContainer overlay )
2019-03-01 03:20:31 +00:00
{
if ( ! visibleBlockingOverlays . Contains ( overlay ) )
visibleBlockingOverlays . Add ( overlay ) ;
updateBlockingOverlayFade ( ) ;
}
2022-05-05 13:47:10 +00:00
void IOverlayManager . HideBlockingOverlay ( OverlayContainer overlay ) = > Schedule ( ( ) = >
2019-03-01 03:20:31 +00:00
{
2019-03-01 04:29:02 +00:00
visibleBlockingOverlays . Remove ( overlay ) ;
2019-03-01 03:20:31 +00:00
updateBlockingOverlayFade ( ) ;
2021-01-04 08:49:11 +00:00
} ) ;
2019-03-01 03:20:31 +00:00
2022-05-04 19:53:04 +00:00
/// <summary>
/// Unregisters a blocking <see cref="OverlayContainer"/> that was not created by <see cref="OsuGame"/> itself.
/// </summary>
2022-06-27 16:19:20 +00:00
private void unregisterBlockingOverlay ( OverlayContainer overlayContainer ) = > Schedule ( ( ) = >
2022-05-04 19:53:04 +00:00
{
externalOverlays . Remove ( overlayContainer ) ;
2022-05-07 20:50:10 +00:00
if ( overlayContainer is OsuFocusedOverlayContainer focusedOverlayContainer )
focusedOverlays . Remove ( focusedOverlayContainer ) ;
2022-05-04 19:53:04 +00:00
overlayContainer . Expire ( ) ;
2022-06-27 16:19:20 +00:00
} ) ;
2022-05-04 19:53:04 +00:00
2022-05-05 13:47:10 +00:00
#endregion
2018-06-06 07:17:51 +00:00
/// <summary>
/// Close all game-wide overlays.
/// </summary>
2019-11-08 14:04:18 +00:00
/// <param name="hideToolbar">Whether the toolbar should also be hidden.</param>
public void CloseAllOverlays ( bool hideToolbar = true )
2018-06-06 07:17:51 +00:00
{
2022-06-27 16:19:20 +00:00
foreach ( var overlay in focusedOverlays )
2019-06-11 05:28:52 +00:00
overlay . Hide ( ) ;
2019-05-12 13:34:36 +00:00
2019-11-08 14:04:18 +00:00
if ( hideToolbar ) Toolbar . Hide ( ) ;
2018-06-06 07:17:51 +00:00
}
2017-07-21 17:03:43 +00:00
private DependencyContainer dependencies ;
2018-04-13 09:19:50 +00:00
2018-07-11 08:07:14 +00:00
protected override IReadOnlyDependencyContainer CreateChildDependencies ( IReadOnlyDependencyContainer parent ) = >
dependencies = new DependencyContainer ( base . CreateChildDependencies ( parent ) ) ;
2018-04-13 09:19:50 +00:00
2016-11-12 10:44:16 +00:00
[BackgroundDependencyLoader]
2020-02-14 13:14:00 +00:00
private void load ( )
2016-08-26 03:28:23 +00:00
{
2018-01-29 06:05:07 +00:00
dependencies . CacheAs ( this ) ;
2018-04-13 09:19:50 +00:00
2022-05-10 05:25:10 +00:00
SentryLogger . AttachUser ( API . LocalUser ) ;
2019-01-31 10:22:29 +00:00
dependencies . Cache ( osuLogo = new OsuLogo { Alpha = 0 } ) ;
2019-01-23 11:52:00 +00:00
2018-02-22 07:29:05 +00:00
// bind config int to database RulesetInfo
2021-11-22 08:40:43 +00:00
configRuleset = LocalConfig . GetBindable < string > ( OsuSetting . Ruleset ) ;
2021-10-28 04:09:03 +00:00
uiScale = LocalConfig . GetBindable < float > ( OsuSetting . UIScale ) ;
2021-07-01 10:03:55 +00:00
2021-11-22 08:40:43 +00:00
var preferredRuleset = int . TryParse ( configRuleset . Value , out int rulesetId )
// int parsing can be removed 20220522
? RulesetStore . GetRuleset ( rulesetId )
: RulesetStore . GetRuleset ( configRuleset . Value ) ;
2021-07-01 10:03:55 +00:00
try
{
Ruleset . Value = preferredRuleset ? ? RulesetStore . AvailableRulesets . First ( ) ;
}
catch ( Exception e )
{
// on startup, a ruleset may be selected which has compatibility issues.
Logger . Error ( e , $@"Failed to switch to preferred ruleset {preferredRuleset}." ) ;
Ruleset . Value = RulesetStore . AvailableRulesets . First ( ) ;
}
2021-11-22 08:40:43 +00:00
Ruleset . ValueChanged + = r = > configRuleset . Value = r . NewValue . ShortName ;
2018-04-13 09:19:50 +00:00
2018-02-22 07:29:05 +00:00
// bind config int to database SkinInfo
2021-11-23 07:04:55 +00:00
configSkin = LocalConfig . GetBindable < string > ( OsuSetting . Skin ) ;
SkinManager . CurrentSkinInfo . ValueChanged + = skin = > configSkin . Value = skin . NewValue . ID . ToString ( ) ;
2019-08-29 07:38:39 +00:00
configSkin . ValueChanged + = skinId = >
{
2022-01-26 04:37:33 +00:00
Live < SkinInfo > skinInfo = null ;
2021-11-23 07:04:55 +00:00
if ( Guid . TryParse ( skinId . NewValue , out var guid ) )
skinInfo = SkinManager . Query ( s = > s . ID = = guid ) ;
2019-08-29 07:38:39 +00:00
if ( skinInfo = = null )
{
2021-11-23 07:04:55 +00:00
if ( guid = = SkinInfo . CLASSIC_SKIN )
2021-12-14 05:21:23 +00:00
skinInfo = DefaultLegacySkin . CreateInfo ( ) . ToLiveUnmanaged ( ) ;
2019-08-29 07:38:39 +00:00
}
2021-12-14 05:21:23 +00:00
SkinManager . CurrentSkinInfo . Value = skinInfo ? ? DefaultSkin . CreateInfo ( ) . ToLiveUnmanaged ( ) ;
2019-08-29 07:38:39 +00:00
} ;
2018-02-22 07:29:05 +00:00
configSkin . TriggerChange ( ) ;
2018-04-13 09:19:50 +00:00
2019-02-22 11:13:38 +00:00
IsActive . BindValueChanged ( active = > updateActiveState ( active . NewValue ) , true ) ;
2019-06-17 16:32:52 +00:00
Audio . AddAdjustment ( AdjustableProperty . Volume , inactiveVolumeFade ) ;
2019-06-21 12:09:12 +00:00
2019-12-26 05:52:08 +00:00
SelectedMods . BindValueChanged ( modsChanged ) ;
2019-06-20 14:40:25 +00:00
Beatmap . BindValueChanged ( beatmapChanged , true ) ;
2016-11-12 17:34:36 +00:00
}
2018-04-13 09:19:50 +00:00
2018-11-01 20:52:07 +00:00
private ExternalLinkOpener externalLinkOpener ;
2019-01-04 04:29:37 +00:00
2019-11-01 02:22:32 +00:00
/// <summary>
/// Handle an arbitrary URL. Displays via in-game overlays where possible.
/// This can be called from a non-thread-safe non-game-loaded state.
/// </summary>
/// <param name="url">The URL to load.</param>
2019-11-03 04:16:54 +00:00
public void HandleLink ( string url ) = > HandleLink ( MessageFormatter . GetLinkDetails ( url ) ) ;
2019-11-01 02:40:51 +00:00
2019-11-01 02:22:32 +00:00
/// <summary>
/// Handle a specific <see cref="LinkDetails"/>.
/// This can be called from a non-thread-safe non-game-loaded state.
/// </summary>
/// <param name="link">The link to load.</param>
2019-11-03 04:16:54 +00:00
public void HandleLink ( LinkDetails link ) = > Schedule ( ( ) = >
2019-11-01 02:40:51 +00:00
{
2021-11-08 05:17:47 +00:00
string argString = link . Argument . ToString ( ) ;
2019-11-01 02:40:51 +00:00
switch ( link . Action )
{
case LinkAction . OpenBeatmap :
// TODO: proper query params handling
2021-11-08 05:17:47 +00:00
if ( int . TryParse ( argString . Contains ( '?' ) ? argString . Split ( '?' ) [ 0 ] : argString , out int beatmapId ) )
2019-11-01 02:40:51 +00:00
ShowBeatmap ( beatmapId ) ;
break ;
case LinkAction . OpenBeatmapSet :
2021-11-08 05:17:47 +00:00
if ( int . TryParse ( argString , out int setId ) )
2019-11-01 02:40:51 +00:00
ShowBeatmapSet ( setId ) ;
break ;
case LinkAction . OpenChannel :
2021-11-08 05:17:47 +00:00
ShowChannel ( argString ) ;
2019-11-01 02:40:51 +00:00
break ;
2020-01-30 22:41:50 +00:00
case LinkAction . SearchBeatmapSet :
2021-11-08 05:17:47 +00:00
SearchBeatmapSet ( argString ) ;
2020-01-30 04:30:25 +00:00
break ;
2019-11-01 02:40:51 +00:00
case LinkAction . OpenEditorTimestamp :
case LinkAction . JoinMultiplayerMatch :
case LinkAction . Spectate :
2021-08-06 19:36:40 +00:00
waitForReady ( ( ) = > Notifications , _ = > Notifications . Post ( new SimpleNotification
2019-11-01 02:22:32 +00:00
{
Text = @"This link type is not yet supported!" ,
Icon = FontAwesome . Solid . LifeRing ,
} ) ) ;
2019-11-01 02:40:51 +00:00
break ;
case LinkAction . External :
2021-11-08 05:17:47 +00:00
OpenUrlExternally ( argString ) ;
2019-11-01 02:40:51 +00:00
break ;
case LinkAction . OpenUserProfile :
2021-11-08 05:17:47 +00:00
if ( ! ( link . Argument is IUser user ) )
{
user = int . TryParse ( argString , out int userId )
? new APIUser { Id = userId }
: new APIUser { Username = argString } ;
}
ShowUser ( user ) ;
2021-08-30 12:22:12 +00:00
2019-11-01 02:40:51 +00:00
break ;
2021-05-16 17:43:59 +00:00
case LinkAction . OpenWiki :
2021-11-08 05:17:47 +00:00
ShowWiki ( argString ) ;
2021-05-16 17:43:59 +00:00
break ;
2021-10-12 02:43:32 +00:00
case LinkAction . OpenChangelog :
2021-11-08 05:17:47 +00:00
if ( string . IsNullOrEmpty ( argString ) )
2021-10-12 02:43:32 +00:00
ShowChangelogListing ( ) ;
else
{
2021-11-08 05:17:47 +00:00
string [ ] changelogArgs = argString . Split ( "/" ) ;
2021-10-12 02:43:32 +00:00
ShowChangelogBuild ( changelogArgs [ 0 ] , changelogArgs [ 1 ] ) ;
}
break ;
2019-11-01 02:40:51 +00:00
default :
throw new NotImplementedException ( $"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action." ) ;
}
2019-11-03 04:16:54 +00:00
} ) ;
2019-11-01 02:40:51 +00:00
2022-01-27 05:53:11 +00:00
public void OpenUrlExternally ( string url , bool bypassExternalUrlWarning = false ) = > waitForReady ( ( ) = > externalLinkOpener , _ = >
2018-12-06 03:17:08 +00:00
{
2020-10-16 09:27:02 +00:00
if ( url . StartsWith ( '/' ) )
2020-12-24 09:11:40 +00:00
url = $"{API.APIEndpointUrl}{url}" ;
2018-12-06 03:17:08 +00:00
2022-01-27 05:53:11 +00:00
externalLinkOpener . OpenUrlExternally ( url , bypassExternalUrlWarning ) ;
2019-11-01 02:22:32 +00:00
} ) ;
/// <summary>
/// Open a specific channel in chat.
/// </summary>
/// <param name="channel">The channel to display.</param>
public void ShowChannel ( string channel ) = > waitForReady ( ( ) = > channelManager , _ = >
{
try
{
channelManager . OpenChannel ( channel ) ;
}
catch ( ChannelNotFoundException )
{
Logger . Log ( $"The requested channel \" { channel } \ " does not exist" ) ;
}
} ) ;
2018-11-01 20:52:07 +00:00
2018-01-17 11:32:26 +00:00
/// <summary>
/// Show a beatmap set as an overlay.
/// </summary>
/// <param name="setId">The set to display.</param>
2019-11-01 02:22:32 +00:00
public void ShowBeatmapSet ( int setId ) = > waitForReady ( ( ) = > beatmapSetOverlay , _ = > beatmapSetOverlay . FetchAndShowBeatmapSet ( setId ) ) ;
2018-04-13 09:19:50 +00:00
2019-02-25 03:58:58 +00:00
/// <summary>
/// Show a user's profile as an overlay.
/// </summary>
2021-11-05 04:53:00 +00:00
/// <param name="user">The user to display.</param>
public void ShowUser ( IUser user ) = > waitForReady ( ( ) = > userProfile , _ = > userProfile . ShowUser ( user ) ) ;
2021-08-29 18:19:55 +00:00
2019-02-25 03:58:58 +00:00
/// <summary>
/// Show a beatmap's set as an overlay, displaying the given beatmap.
/// </summary>
/// <param name="beatmapId">The beatmap to show.</param>
2019-11-01 02:22:32 +00:00
public void ShowBeatmap ( int beatmapId ) = > waitForReady ( ( ) = > beatmapSetOverlay , _ = > beatmapSetOverlay . FetchAndShowBeatmap ( beatmapId ) ) ;
2019-02-25 03:58:58 +00:00
2020-01-30 04:30:25 +00:00
/// <summary>
2021-07-03 13:22:03 +00:00
/// Shows the beatmap listing overlay, with the given <paramref name="query"/> in the search box.
2020-01-30 04:30:25 +00:00
/// </summary>
2020-01-30 22:41:50 +00:00
/// <param name="query">The query to search for.</param>
2021-07-01 10:41:30 +00:00
public void SearchBeatmapSet ( string query ) = > waitForReady ( ( ) = > beatmapListing , _ = > beatmapListing . ShowWithSearch ( query ) ) ;
2021-05-16 17:43:59 +00:00
/// <summary>
/// Show a wiki's page as an overlay
/// </summary>
/// <param name="path">The wiki page to show</param>
public void ShowWiki ( string path ) = > waitForReady ( ( ) = > wikiOverlay , _ = > wikiOverlay . ShowPage ( path ) ) ;
2020-01-30 04:30:25 +00:00
2018-07-10 16:32:10 +00:00
/// <summary>
2021-10-12 02:42:29 +00:00
/// Show changelog listing overlay
/// </summary>
public void ShowChangelogListing ( ) = > waitForReady ( ( ) = > changelogOverlay , _ = > changelogOverlay . ShowListing ( ) ) ;
/// <summary>
/// Show changelog's build as an overlay
/// </summary>
/// <param name="updateStream">The update stream name</param>
/// <param name="version">The build version of the update stream</param>
public void ShowChangelogBuild ( string updateStream , string version ) = > waitForReady ( ( ) = > changelogOverlay , _ = > changelogOverlay . ShowBuild ( updateStream , version ) ) ;
2018-07-10 16:32:10 +00:00
/// <summary>
2019-02-26 03:28:49 +00:00
/// Present a beatmap at song select immediately.
2019-02-25 09:24:06 +00:00
/// The user should have already requested this interactively.
2018-07-10 16:32:10 +00:00
/// </summary>
/// <param name="beatmap">The beatmap to select.</param>
2020-11-21 12:26:09 +00:00
/// <param name="difficultyCriteria">Optional predicate used to narrow the set of difficulties to select from when presenting.</param>
/// <remarks>
/// Among items satisfying the predicate, the order of preference is:
/// <list type="bullet">
/// <item>beatmap with recommended difficulty, as provided by <see cref="DifficultyRecommender"/>,</item>
/// <item>first beatmap from the current ruleset,</item>
/// <item>first beatmap from any ruleset.</item>
/// </list>
/// </remarks>
2021-10-29 08:40:12 +00:00
public void PresentBeatmap ( IBeatmapSetInfo beatmap , Predicate < BeatmapInfo > difficultyCriteria = null )
2018-07-10 16:32:10 +00:00
{
2022-06-27 09:41:27 +00:00
Logger . Log ( $"Beginning {nameof(PresentBeatmap)} with beatmap {beatmap}" ) ;
2022-01-26 04:37:33 +00:00
Live < BeatmapSetInfo > databasedSet = null ;
2021-10-29 08:40:12 +00:00
if ( beatmap . OnlineID > 0 )
2021-11-12 08:50:31 +00:00
databasedSet = BeatmapManager . QueryBeatmapSet ( s = > s . OnlineID = = beatmap . OnlineID ) ;
2021-10-29 08:40:12 +00:00
if ( beatmap is BeatmapSetInfo localBeatmap )
databasedSet ? ? = BeatmapManager . QueryBeatmapSet ( s = > s . Hash = = localBeatmap . Hash ) ;
2019-02-25 03:58:58 +00:00
if ( databasedSet = = null )
2019-01-23 11:52:00 +00:00
{
2019-02-25 03:58:58 +00:00
Logger . Log ( "The requested beatmap could not be loaded." , LoggingTarget . Information ) ;
2019-01-23 11:52:00 +00:00
return ;
}
2022-01-10 07:34:32 +00:00
var detachedSet = databasedSet . PerformRead ( s = > s . Detach ( ) ) ;
2020-01-30 14:34:04 +00:00
PerformFromScreen ( screen = >
2018-07-13 12:08:41 +00:00
{
2022-01-10 07:34:32 +00:00
// Find beatmaps that match our predicate.
var beatmaps = detachedSet . Beatmaps . Where ( b = > difficultyCriteria ? . Invoke ( b ) ? ? true ) . ToList ( ) ;
2019-02-24 03:08:27 +00:00
2022-01-10 07:34:32 +00:00
// Use all beatmaps if predicate matched nothing
if ( beatmaps . Count = = 0 )
beatmaps = detachedSet . Beatmaps . ToList ( ) ;
2019-04-02 15:57:31 +00:00
2022-01-10 07:34:32 +00:00
// Prefer recommended beatmap if recommendations are available, else fallback to a sane selection.
var selection = difficultyRecommender . GetRecommendedBeatmap ( beatmaps )
? ? beatmaps . FirstOrDefault ( b = > b . Ruleset . Equals ( Ruleset . Value ) )
? ? beatmaps . First ( ) ;
2019-02-24 03:08:27 +00:00
2022-01-10 07:34:32 +00:00
if ( screen is IHandlePresentBeatmap presentableScreen )
{
presentableScreen . PresentBeatmap ( BeatmapManager . GetWorkingBeatmap ( selection ) , selection . Ruleset ) ;
}
else
{
2022-06-27 09:41:27 +00:00
Logger . Log ( $"Completing {nameof(PresentBeatmap)} with beatmap {beatmap} ruleset {selection.Ruleset}" ) ;
2022-01-10 07:34:32 +00:00
Ruleset . Value = selection . Ruleset ;
Beatmap . Value = BeatmapManager . GetWorkingBeatmap ( selection ) ;
}
2021-03-03 05:04:00 +00:00
} , validScreens : new [ ] { typeof ( SongSelect ) , typeof ( IHandlePresentBeatmap ) } ) ;
2018-07-10 16:32:10 +00:00
}
2018-02-28 09:49:27 +00:00
/// <summary>
2019-02-25 09:24:06 +00:00
/// Present a score's replay immediately.
/// The user should have already requested this interactively.
2018-02-28 09:49:27 +00:00
/// </summary>
2021-12-06 13:47:00 +00:00
public void PresentScore ( IScoreInfo score , ScorePresentType presentType = ScorePresentType . Results )
2017-03-04 10:02:36 +00:00
{
2022-06-27 09:41:27 +00:00
Logger . Log ( $"Beginning {nameof(PresentScore)} with score {score}" ) ;
2019-06-29 10:40:16 +00:00
// The given ScoreInfo may have missing properties if it was retrieved from online data. Re-retrieve it from the database
// to ensure all the required data for presenting a replay are present.
2021-06-15 05:06:17 +00:00
ScoreInfo databasedScoreInfo = null ;
2021-12-10 06:28:41 +00:00
if ( score . OnlineID > 0 )
2022-01-07 15:40:14 +00:00
databasedScoreInfo = ScoreManager . Query ( s = > s . OnlineID = = score . OnlineID ) ;
2021-06-15 05:06:17 +00:00
2021-12-13 08:09:13 +00:00
if ( score is ScoreInfo scoreInfo )
2022-01-07 15:40:14 +00:00
databasedScoreInfo ? ? = ScoreManager . Query ( s = > s . Hash = = scoreInfo . Hash ) ;
2019-07-29 21:31:45 +00:00
if ( databasedScoreInfo = = null )
{
Logger . Log ( "The requested score could not be found locally." , LoggingTarget . Information ) ;
return ;
}
2019-06-29 10:38:48 +00:00
var databasedScore = ScoreManager . GetScore ( databasedScoreInfo ) ;
2019-04-01 03:16:05 +00:00
2018-11-30 09:31:54 +00:00
if ( databasedScore . Replay = = null )
2018-11-29 04:06:48 +00:00
{
Logger . Log ( "The loaded score has no replay data." , LoggingTarget . Information ) ;
return ;
}
2018-11-28 11:41:48 +00:00
2021-10-04 08:35:53 +00:00
var databasedBeatmap = BeatmapManager . QueryBeatmap ( b = > b . ID = = databasedScoreInfo . BeatmapInfo . ID ) ;
2019-04-01 03:16:05 +00:00
2018-11-30 09:31:54 +00:00
if ( databasedBeatmap = = null )
{
Logger . Log ( "Tried to load a score for a beatmap we don't have!" , LoggingTarget . Information ) ;
return ;
}
2020-01-30 14:34:04 +00:00
PerformFromScreen ( screen = >
2019-02-25 03:58:58 +00:00
{
2022-06-27 09:41:27 +00:00
Logger . Log ( $"{nameof(PresentScore)} updating beatmap ({databasedBeatmap}) and ruleset ({databasedScore.ScoreInfo.Ruleset} to match score" ) ;
2020-06-15 11:23:35 +00:00
Ruleset . Value = databasedScore . ScoreInfo . Ruleset ;
2019-02-25 03:58:58 +00:00
Beatmap . Value = BeatmapManager . GetWorkingBeatmap ( databasedBeatmap ) ;
2020-06-15 11:23:35 +00:00
switch ( presentType )
{
case ScorePresentType . Gameplay :
screen . Push ( new ReplayPlayerLoader ( databasedScore ) ) ;
break ;
case ScorePresentType . Results :
2020-11-20 05:35:44 +00:00
screen . Push ( new SoloResultsScreen ( databasedScore . ScoreInfo , false ) ) ;
2020-06-15 11:23:35 +00:00
break ;
}
2020-02-03 07:28:38 +00:00
} , validScreens : new [ ] { typeof ( PlaySongSelect ) } ) ;
2019-02-25 03:58:58 +00:00
}
2020-12-16 13:28:16 +00:00
public override Task Import ( params ImportTask [ ] imports )
2020-12-09 12:32:59 +00:00
{
2020-12-14 09:03:01 +00:00
// encapsulate task as we don't want to begin the import process until in a ready state.
2021-07-02 05:43:48 +00:00
// ReSharper disable once AsyncVoidLambda
// TODO: This is bad because `new Task` doesn't have a Func<Task?> override.
// Only used for android imports and a bit of a mess. Probably needs rethinking overall.
2021-03-08 03:57:16 +00:00
var importTask = new Task ( async ( ) = > await base . Import ( imports ) . ConfigureAwait ( false ) ) ;
2020-12-14 09:03:01 +00:00
2020-12-14 08:59:04 +00:00
waitForReady ( ( ) = > this , _ = > importTask . Start ( ) ) ;
2020-12-14 09:03:01 +00:00
2020-12-12 16:12:15 +00:00
return importTask ;
2019-02-25 03:58:58 +00:00
}
2019-09-25 13:13:49 +00:00
protected virtual Loader CreateLoader ( ) = > new Loader ( ) ;
2020-03-05 04:34:04 +00:00
protected virtual UpdateManager CreateUpdateManager ( ) = > new UpdateManager ( ) ;
2021-06-16 11:53:48 +00:00
protected virtual HighPerformanceSession CreateHighPerformanceSession ( ) = > new HighPerformanceSession ( ) ;
2019-11-11 04:58:35 +00:00
protected override Container CreateScalingContainer ( ) = > new ScalingContainer ( ScalingMode . Everything ) ;
2019-08-13 03:06:57 +00:00
#region Beatmap progression
2019-06-20 14:40:25 +00:00
private void beatmapChanged ( ValueChangedEvent < WorkingBeatmap > beatmap )
{
2020-03-04 10:09:52 +00:00
beatmap . OldValue ? . CancelAsyncLoad ( ) ;
2020-08-05 12:21:08 +00:00
beatmap . NewValue ? . BeginAsyncLoad ( ) ;
2019-06-20 14:40:25 +00:00
}
2019-12-26 05:52:08 +00:00
private void modsChanged ( ValueChangedEvent < IReadOnlyList < Mod > > mods )
{
2021-02-05 07:46:21 +00:00
// a lease may be taken on the mods bindable, at which point we can't really ensure valid mods.
if ( SelectedMods . Disabled )
return ;
2021-02-01 11:20:19 +00:00
if ( ! ModUtils . CheckValidForGameplay ( mods . NewValue , out var invalid ) )
{
// ensure we always have a valid set of mods.
SelectedMods . Value = mods . NewValue . Except ( invalid ) . ToArray ( ) ;
}
2019-12-26 05:52:08 +00:00
}
2019-06-20 14:40:25 +00:00
#endregion
2020-11-11 05:45:50 +00:00
private PerformFromMenuRunner performFromMainMenuTask ;
2019-02-25 03:58:58 +00:00
2020-02-11 13:37:38 +00:00
public void PerformFromScreen ( Action < IScreen > action , IEnumerable < Type > validScreens = null )
2019-02-25 03:58:58 +00:00
{
2019-02-25 05:01:51 +00:00
performFromMainMenuTask ? . Cancel ( ) ;
2020-11-11 05:45:50 +00:00
Add ( performFromMainMenuTask = new PerformFromMenuRunner ( action , validScreens , ( ) = > ScreenStack . CurrentScreen ) ) ;
2017-03-04 10:02:36 +00:00
}
2018-04-13 09:19:50 +00:00
2022-06-19 03:34:14 +00:00
public override void AttemptExit ( )
2022-06-18 13:59:19 +00:00
{
// Using PerformFromScreen gives the user a chance to interrupt the exit process if needed.
PerformFromScreen ( menu = > menu . Exit ( ) ) ;
}
2019-11-01 02:22:32 +00:00
/// <summary>
/// Wait for the game (and target component) to become loaded and then run an action.
/// </summary>
/// <param name="retrieveInstance">A function to retrieve a (potentially not-yet-constructed) target instance.</param>
/// <param name="action">The action to perform on the instance when load is confirmed.</param>
/// <typeparam name="T">The type of the target instance.</typeparam>
private void waitForReady < T > ( Func < T > retrieveInstance , Action < T > action )
where T : Drawable
{
var instance = retrieveInstance ( ) ;
if ( ScreenStack = = null | | ScreenStack . CurrentScreen is StartupScreen | | instance ? . IsLoaded ! = true )
Schedule ( ( ) = > waitForReady ( retrieveInstance , action ) ) ;
else
action ( instance ) ;
}
2018-08-03 10:25:55 +00:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
2019-11-12 14:08:16 +00:00
SentryLogger . Dispose ( ) ;
2018-08-03 10:25:55 +00:00
}
2021-03-24 04:37:37 +00:00
protected override IDictionary < FrameworkSetting , object > GetFrameworkConfigDefaults ( )
2022-06-03 06:37:17 +00:00
{
return new Dictionary < FrameworkSetting , object >
2021-03-24 04:37:37 +00:00
{
2022-06-03 06:37:17 +00:00
// General expectation that osu! starts in fullscreen by default (also gives the most predictable performance).
// However, macOS is bound to have issues when using exclusive fullscreen as it takes full control away from OS, therefore borderless is default there.
2022-06-03 07:32:43 +00:00
{ FrameworkSetting . WindowMode , RuntimeInfo . OS = = RuntimeInfo . Platform . macOS ? WindowMode . Borderless : WindowMode . Fullscreen }
2021-03-24 04:37:37 +00:00
} ;
2022-06-03 06:37:17 +00:00
}
2021-03-24 04:37:37 +00:00
2016-11-12 17:34:36 +00:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
2018-04-13 09:19:50 +00:00
2022-06-28 07:29:19 +00:00
var languages = Enum . GetValues ( typeof ( Language ) ) . OfType < Language > ( ) ;
var mappings = languages . Select ( language = >
2021-04-20 08:06:01 +00:00
{
2022-04-19 05:30:45 +00:00
#if DEBUG
2022-04-19 07:49:41 +00:00
if ( language = = Language . debug )
2022-06-28 07:29:19 +00:00
return new LocaleMapping ( "debug" , new DebugLocalisationStore ( ) ) ;
2022-04-19 05:30:45 +00:00
#endif
2021-10-27 04:04:41 +00:00
string cultureCode = language . ToCultureCode ( ) ;
2021-06-19 06:00:36 +00:00
try
{
2022-06-28 07:29:19 +00:00
return new LocaleMapping ( new ResourceManagerLocalisationStore ( cultureCode ) ) ;
2021-06-19 06:00:36 +00:00
}
catch ( Exception ex )
{
Logger . Error ( ex , $"Could not load localisations for language \" { cultureCode } \ "" ) ;
2022-06-28 07:29:19 +00:00
return null ;
2021-06-19 06:00:36 +00:00
}
2022-06-28 07:29:19 +00:00
} ) . Where ( m = > m ! = null ) ;
Localisation . AddLocaleMappings ( mappings ) ;
2021-04-20 08:06:01 +00:00
2019-02-15 07:55:39 +00:00
// The next time this is updated is in UpdateAfterChildren, which occurs too late and results
// in the cursor being shown for a few frames during the intro.
// This prevents the cursor from showing until we have a screen with CursorVisible = true
MenuCursorContainer . CanShowCursor = menuScreen ? . CursorVisible ? ? false ;
2018-08-31 09:28:53 +00:00
// todo: all archive managers should be able to be looped here.
2021-08-06 19:36:40 +00:00
SkinManager . PostNotification = n = > Notifications . Post ( n ) ;
2018-04-13 09:19:50 +00:00
2021-08-06 19:36:40 +00:00
BeatmapManager . PostNotification = n = > Notifications . Post ( n ) ;
2022-06-20 09:21:37 +00:00
BeatmapManager . PresentImport = items = > PresentBeatmap ( items . First ( ) . Value ) ;
2018-08-31 09:28:53 +00:00
2021-11-25 08:23:46 +00:00
BeatmapDownloader . PostNotification = n = > Notifications . Post ( n ) ;
2021-11-25 08:21:05 +00:00
ScoreDownloader . PostNotification = n = > Notifications . Post ( n ) ;
2021-08-06 19:36:40 +00:00
ScoreManager . PostNotification = n = > Notifications . Post ( n ) ;
2022-06-20 09:21:37 +00:00
ScoreManager . PresentImport = items = > PresentScore ( items . First ( ) . Value ) ;
2018-04-13 09:19:50 +00:00
2020-11-11 03:19:01 +00:00
// make config aware of how to lookup skins for on-screen display purposes.
// if this becomes a more common thing, tracked settings should be reconsidered to allow local DI.
2021-11-29 08:15:26 +00:00
LocalConfig . LookupSkinName = id = > SkinManager . Query ( s = > s . ID = = id ) ? . ToString ( ) ? ? "Unknown" ;
2020-11-11 03:19:01 +00:00
2020-11-11 03:54:39 +00:00
LocalConfig . LookupKeyBindings = l = >
{
2021-06-18 08:01:51 +00:00
var combinations = KeyBindingStore . GetReadableKeyCombinationsFor ( l ) ;
2020-11-11 03:54:39 +00:00
2021-06-18 08:01:51 +00:00
if ( combinations . Count = = 0 )
2021-10-11 08:02:26 +00:00
return ToastStrings . NoKeyBound ;
2020-11-11 03:54:39 +00:00
2021-10-11 08:11:41 +00:00
return string . Join ( " / " , combinations ) ;
2020-11-11 03:54:39 +00:00
} ;
2019-01-23 11:52:00 +00:00
Container logoContainer ;
2019-07-30 03:00:04 +00:00
BackButton . Receptor receptor ;
2019-01-23 11:52:00 +00:00
2019-05-13 08:10:25 +00:00
dependencies . CacheAs ( idleTracker = new GameIdleTracker ( 6000 ) ) ;
2021-04-19 02:30:55 +00:00
var sessionIdleTracker = new GameIdleTracker ( 300000 ) ;
sessionIdleTracker . IsIdle . BindValueChanged ( idle = >
2021-04-16 09:53:27 +00:00
{
2021-04-19 02:30:55 +00:00
if ( idle . NewValue )
2021-12-21 06:34:32 +00:00
SessionStatics . ResetAfterInactivity ( ) ;
2021-04-16 09:53:27 +00:00
} ) ;
2019-05-13 08:10:25 +00:00
2021-04-19 05:06:26 +00:00
Add ( sessionIdleTracker ) ;
2021-04-19 02:30:55 +00:00
2017-10-23 04:08:58 +00:00
AddRange ( new Drawable [ ]
{
2016-10-26 09:45:48 +00:00
new VolumeControlReceptor
{
RelativeSizeAxes = Axes . Both ,
2018-06-27 09:43:29 +00:00
ActionRequested = action = > volume . Adjust ( action ) ,
2018-07-05 07:50:04 +00:00
ScrollActionRequested = ( action , amount , isPrecise ) = > volume . Adjust ( action , amount , isPrecise ) ,
2016-10-26 09:45:48 +00:00
} ,
2021-08-06 19:36:40 +00:00
ScreenOffsetContainer = new Container
2019-01-04 04:29:37 +00:00
{
RelativeSizeAxes = Axes . Both ,
2019-01-31 09:25:25 +00:00
Children = new Drawable [ ]
{
2021-12-13 03:48:15 +00:00
ScreenContainer = new ScalingContainer ( ScalingMode . ExcludeOverlays )
2019-06-25 08:16:38 +00:00
{
2021-04-30 04:03:54 +00:00
RelativeSizeAxes = Axes . Both ,
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
Children = new Drawable [ ]
2019-06-25 08:17:29 +00:00
{
2021-04-30 04:03:54 +00:00
receptor = new BackButton . Receptor ( ) ,
ScreenStack = new OsuScreenStack { RelativeSizeAxes = Axes . Both } ,
BackButton = new BackButton ( receptor )
{
Anchor = Anchor . BottomLeft ,
Origin = Anchor . BottomLeft ,
Action = ( ) = >
{
2021-06-08 08:38:12 +00:00
if ( ! ( ScreenStack . CurrentScreen is IOsuScreen currentScreen ) )
return ;
2021-04-30 04:03:54 +00:00
2021-06-08 09:39:52 +00:00
if ( ! ( ( Drawable ) currentScreen ) . IsLoaded | | ( currentScreen . AllowBackButton & & ! currentScreen . OnBackButton ( ) ) )
2021-04-30 04:03:54 +00:00
ScreenStack . Exit ( ) ;
}
} ,
logoContainer = new Container { RelativeSizeAxes = Axes . Both } ,
2019-06-25 08:17:29 +00:00
}
2019-06-25 08:16:38 +00:00
} ,
2019-01-31 09:25:25 +00:00
}
2019-01-04 04:29:37 +00:00
} ,
2021-08-29 03:13:01 +00:00
overlayOffsetContainer = new Container
{
RelativeSizeAxes = Axes . Both ,
Children = new Drawable [ ]
{
overlayContent = new Container { RelativeSizeAxes = Axes . Both } ,
rightFloatingOverlayContent = new Container { RelativeSizeAxes = Axes . Both } ,
leftFloatingOverlayContent = new Container { RelativeSizeAxes = Axes . Both } ,
}
} ,
2019-03-21 18:16:10 +00:00
topMostOverlayContent = new Container { RelativeSizeAxes = Axes . Both } ,
2020-08-16 12:22:39 +00:00
idleTracker ,
2020-10-07 07:41:47 +00:00
new ConfineMouseTracker ( )
2016-09-30 09:45:55 +00:00
} ) ;
2018-04-13 09:19:50 +00:00
2019-07-29 05:30:46 +00:00
ScreenStack . ScreenPushed + = screenPushed ;
ScreenStack . ScreenExited + = screenExited ;
2019-01-23 11:52:00 +00:00
2022-01-16 14:20:22 +00:00
if ( ! args ? . Any ( a = > a = = @"--no-version-overlay" ) ? ? true )
loadComponentSingleFile ( versionManager = new VersionManager { Depth = int . MinValue } , ScreenContainer . Add ) ;
2019-03-12 07:03:25 +00:00
loadComponentSingleFile ( osuLogo , logo = >
2016-11-01 14:24:14 +00:00
{
2019-03-12 07:03:25 +00:00
logoContainer . Add ( logo ) ;
2019-01-23 11:52:00 +00:00
2019-03-24 07:21:43 +00:00
// Loader has to be created after the logo has finished loading as Loader performs logo transformations on entering.
2019-07-31 07:03:05 +00:00
ScreenStack . Push ( CreateLoader ( ) . With ( l = > l . RelativeSizeAxes = Axes . Both ) ) ;
2019-03-12 07:03:25 +00:00
} ) ;
2018-04-13 09:19:50 +00:00
2017-10-22 16:20:12 +00:00
loadComponentSingleFile ( Toolbar = new Toolbar
{
OnHome = delegate
{
2018-06-06 07:17:51 +00:00
CloseAllOverlays ( false ) ;
2019-01-23 11:52:00 +00:00
menuScreen ? . MakeCurrent ( ) ;
2017-10-22 16:20:12 +00:00
} ,
2019-11-08 14:04:18 +00:00
} , topMostOverlayContent . Add ) ;
2018-04-13 09:19:50 +00:00
2019-09-15 15:15:52 +00:00
loadComponentSingleFile ( volume = new VolumeOverlay ( ) , leftFloatingOverlayContent . Add , true ) ;
2019-09-15 14:31:40 +00:00
2020-11-11 04:51:20 +00:00
var onScreenDisplay = new OnScreenDisplay ( ) ;
onScreenDisplay . BeginTracking ( this , frameworkConfig ) ;
onScreenDisplay . BeginTracking ( this , LocalConfig ) ;
2018-04-13 12:13:09 +00:00
2020-11-11 04:51:20 +00:00
loadComponentSingleFile ( onScreenDisplay , Add , true ) ;
2019-08-13 05:29:58 +00:00
2022-04-18 10:59:57 +00:00
loadComponentSingleFile < INotificationOverlay > ( Notifications . With ( d = >
2019-03-29 05:53:40 +00:00
{
2020-07-19 02:37:38 +00:00
d . Anchor = Anchor . TopRight ;
d . Origin = Anchor . TopRight ;
} ) , rightFloatingOverlayContent . Add , true ) ;
2019-03-29 05:53:40 +00:00
2020-09-09 06:39:15 +00:00
loadComponentSingleFile ( new CollectionManager ( Storage )
{
2021-08-06 19:36:40 +00:00
PostNotification = n = > Notifications . Post ( n ) ,
2020-09-09 06:39:15 +00:00
} , Add , true ) ;
2021-11-25 08:12:15 +00:00
loadComponentSingleFile ( legacyImportManager , Add ) ;
2019-03-29 05:53:40 +00:00
2018-04-13 12:13:09 +00:00
loadComponentSingleFile ( screenshotManager , Add ) ;
2018-04-13 09:19:50 +00:00
2020-05-07 23:09:16 +00:00
// dependency on notification overlay, dependent by settings overlay
2020-05-07 21:04:18 +00:00
loadComponentSingleFile ( CreateUpdateManager ( ) , Add , true ) ;
2020-05-05 01:31:11 +00:00
// overlay elements
2022-06-15 00:34:08 +00:00
loadComponentSingleFile ( FirstRunOverlay = new FirstRunSetupOverlay ( ) , overlayContent . Add , true ) ;
2020-09-07 12:08:48 +00:00
loadComponentSingleFile ( new ManageCollectionsDialog ( ) , overlayContent . Add , true ) ;
2020-04-21 07:00:00 +00:00
loadComponentSingleFile ( beatmapListing = new BeatmapListingOverlay ( ) , overlayContent . Add , true ) ;
2020-04-16 09:05:51 +00:00
loadComponentSingleFile ( dashboard = new DashboardOverlay ( ) , overlayContent . Add , true ) ;
2020-07-16 11:48:40 +00:00
loadComponentSingleFile ( news = new NewsOverlay ( ) , overlayContent . Add , true ) ;
2020-02-13 11:26:35 +00:00
var rankingsOverlay = loadComponentSingleFile ( new RankingsOverlay ( ) , overlayContent . Add , true ) ;
2022-06-08 09:54:23 +00:00
loadComponentSingleFile ( channelManager = new ChannelManager ( API ) , AddInternal , true ) ;
2022-05-30 08:54:09 +00:00
loadComponentSingleFile ( chatOverlay = new ChatOverlay ( ) , overlayContent . Add , true ) ;
2021-06-11 07:28:53 +00:00
loadComponentSingleFile ( new MessageNotifier ( ) , AddInternal , true ) ;
2021-08-29 03:13:01 +00:00
loadComponentSingleFile ( Settings = new SettingsOverlay ( ) , leftFloatingOverlayContent . Add , true ) ;
2021-10-12 02:41:59 +00:00
loadComponentSingleFile ( changelogOverlay = new ChangelogOverlay ( ) , overlayContent . Add , true ) ;
2019-05-13 08:10:25 +00:00
loadComponentSingleFile ( userProfile = new UserProfileOverlay ( ) , overlayContent . Add , true ) ;
loadComponentSingleFile ( beatmapSetOverlay = new BeatmapSetOverlay ( ) , overlayContent . Add , true ) ;
2021-04-22 09:16:12 +00:00
loadComponentSingleFile ( wikiOverlay = new WikiOverlay ( ) , overlayContent . Add , true ) ;
2021-12-13 03:48:15 +00:00
loadComponentSingleFile ( skinEditor = new SkinEditorOverlay ( ScreenContainer ) , overlayContent . Add , true ) ;
2019-05-13 08:10:25 +00:00
2019-11-12 06:03:58 +00:00
loadComponentSingleFile ( new LoginOverlay
2017-01-30 13:00:23 +00:00
{
2017-01-30 13:53:50 +00:00
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
2019-05-13 08:10:25 +00:00
} , rightFloatingOverlayContent . Add , true ) ;
2018-04-13 09:19:50 +00:00
2020-08-06 08:17:24 +00:00
loadComponentSingleFile ( new NowPlayingOverlay
2017-02-10 07:26:43 +00:00
{
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
2019-11-08 14:04:18 +00:00
} , rightFloatingOverlayContent . Add , true ) ;
2018-04-13 09:19:50 +00:00
2019-05-13 08:10:25 +00:00
loadComponentSingleFile ( new AccountCreationOverlay ( ) , topMostOverlayContent . Add , true ) ;
2022-04-18 09:36:22 +00:00
loadComponentSingleFile < IDialogOverlay > ( new DialogOverlay ( ) , topMostOverlayContent . Add , true ) ;
2018-12-06 02:55:58 +00:00
2021-06-16 11:53:48 +00:00
loadComponentSingleFile ( CreateHighPerformanceSession ( ) , Add ) ;
2018-12-06 02:55:58 +00:00
2022-01-13 08:31:49 +00:00
chatOverlay . State . BindValueChanged ( _ = > updateChatPollRate ( ) ) ;
// Multiplayer modes need to increase poll rate temporarily.
API . Activity . BindValueChanged ( _ = > updateChatPollRate ( ) , true ) ;
void updateChatPollRate ( )
{
channelManager . HighPollRate . Value =
chatOverlay . State . Value = = Visibility . Visible
| | API . Activity . Value is UserActivity . InLobby
2022-02-25 07:03:46 +00:00
| | API . Activity . Value is UserActivity . InMultiplayerGame
| | API . Activity . Value is UserActivity . SpectatingMultiplayerGame ;
2022-01-13 08:31:49 +00:00
}
2018-12-10 12:08:14 +00:00
2021-06-16 02:48:41 +00:00
Add ( difficultyRecommender ) ;
2018-10-23 20:03:00 +00:00
Add ( externalLinkOpener = new ExternalLinkOpener ( ) ) ;
2020-09-08 09:10:14 +00:00
Add ( new MusicKeyBindingHandler ( ) ) ;
2018-10-23 20:03:00 +00:00
2019-11-12 06:03:58 +00:00
// side overlays which cancel each other.
2022-06-15 00:34:08 +00:00
var singleDisplaySideOverlays = new OverlayContainer [ ] { Settings , Notifications , FirstRunOverlay } ;
2018-06-06 07:17:51 +00:00
2019-11-12 06:04:51 +00:00
foreach ( var overlay in singleDisplaySideOverlays )
2017-08-25 01:51:28 +00:00
{
2019-06-11 05:28:52 +00:00
overlay . State . ValueChanged + = state = >
2017-08-25 01:51:28 +00:00
{
2019-06-11 05:28:52 +00:00
if ( state . NewValue = = Visibility . Hidden ) return ;
2019-02-28 04:31:40 +00:00
2018-07-13 11:32:22 +00:00
singleDisplaySideOverlays . Where ( o = > o ! = overlay ) . ForEach ( o = > o . Hide ( ) ) ;
2017-08-25 01:51:28 +00:00
} ;
}
2018-04-13 09:19:50 +00:00
2018-07-13 11:32:22 +00:00
// eventually informational overlays should be displayed in a stack, but for now let's only allow one to stay open at a time.
2019-05-31 04:23:50 +00:00
var informationalOverlays = new OverlayContainer [ ] { beatmapSetOverlay , userProfile } ;
2018-06-06 07:17:51 +00:00
2018-07-13 11:32:22 +00:00
foreach ( var overlay in informationalOverlays )
2018-03-22 10:49:31 +00:00
{
2019-06-11 05:28:52 +00:00
overlay . State . ValueChanged + = state = >
2018-03-22 10:49:31 +00:00
{
2020-08-11 14:04:00 +00:00
if ( state . NewValue ! = Visibility . Hidden )
showOverlayAboveOthers ( overlay , informationalOverlays ) ;
2018-03-22 10:49:31 +00:00
} ;
}
2018-04-13 09:19:50 +00:00
2018-07-13 11:32:22 +00:00
// ensure only one of these overlays are open at once.
2022-06-15 00:34:08 +00:00
var singleDisplayOverlays = new OverlayContainer [ ] { FirstRunOverlay , chatOverlay , news , dashboard , beatmapListing , changelogOverlay , rankingsOverlay , wikiOverlay } ;
2018-06-06 07:17:51 +00:00
2018-07-13 11:32:22 +00:00
foreach ( var overlay in singleDisplayOverlays )
2017-11-15 09:48:37 +00:00
{
2019-06-11 05:28:52 +00:00
overlay . State . ValueChanged + = state = >
2017-11-15 09:48:37 +00:00
{
2018-07-13 11:32:22 +00:00
// informational overlays should be dismissed on a show or hide of a full overlay.
informationalOverlays . ForEach ( o = > o . Hide ( ) ) ;
2020-08-11 14:04:00 +00:00
if ( state . NewValue ! = Visibility . Hidden )
showOverlayAboveOthers ( overlay , singleDisplayOverlays ) ;
2017-11-15 09:48:37 +00:00
} ;
}
2018-04-13 09:19:50 +00:00
2019-02-22 08:51:39 +00:00
OverlayActivationMode . ValueChanged + = mode = >
2018-06-06 07:17:51 +00:00
{
2019-02-22 08:51:39 +00:00
if ( mode . NewValue ! = OverlayActivation . All ) CloseAllOverlays ( ) ;
2018-06-06 07:17:51 +00:00
} ;
2021-10-13 03:18:56 +00:00
// Importantly, this should be run after binding PostNotification to the import handlers so they can present the import after game startup.
handleStartupImport ( ) ;
}
private void handleStartupImport ( )
{
if ( args ? . Length > 0 )
{
2021-10-27 04:04:41 +00:00
string [ ] paths = args . Where ( a = > ! a . StartsWith ( '-' ) ) . ToArray ( ) ;
2022-02-18 07:06:38 +00:00
2021-10-13 03:18:56 +00:00
if ( paths . Length > 0 )
2022-02-18 07:06:38 +00:00
{
string firstPath = paths . First ( ) ;
if ( firstPath . StartsWith ( OSU_PROTOCOL , StringComparison . Ordinal ) )
{
2022-02-23 08:02:39 +00:00
HandleLink ( firstPath ) ;
2022-02-18 07:06:38 +00:00
}
else
{
Task . Run ( ( ) = > Import ( paths ) ) ;
}
}
}
}
2020-08-11 14:04:00 +00:00
private void showOverlayAboveOthers ( OverlayContainer overlay , OverlayContainer [ ] otherOverlays )
2018-12-26 09:39:57 +00:00
{
2020-08-11 14:04:00 +00:00
otherOverlays . Where ( o = > o ! = overlay ) . ForEach ( o = > o . Hide ( ) ) ;
2018-12-26 09:39:57 +00:00
2021-03-19 11:09:12 +00:00
// Partially visible so leave it at the current depth.
if ( overlay . IsPresent )
2021-03-19 07:47:39 +00:00
return ;
2019-01-23 11:37:56 +00:00
2021-03-19 11:09:12 +00:00
// Show above all other overlays.
if ( overlay . IsLoaded )
2020-08-11 14:04:00 +00:00
overlayContent . ChangeChildDepth ( overlay , ( float ) - Clock . CurrentTime ) ;
2021-03-19 11:09:12 +00:00
else
overlay . Depth = ( float ) - Clock . CurrentTime ;
2018-12-26 09:39:57 +00:00
}
2017-12-25 08:12:21 +00:00
private void forwardLoggedErrorsToNotifications ( )
{
2018-06-21 05:43:38 +00:00
int recentLogCount = 0 ;
2018-04-13 09:19:50 +00:00
2019-07-26 04:48:29 +00:00
const double debounce = 60000 ;
2018-04-13 09:19:50 +00:00
2017-12-25 08:12:21 +00:00
Logger . NewEntry + = entry = >
{
2018-06-21 05:43:38 +00:00
if ( entry . Level < LogLevel . Important | | entry . Target = = null ) return ;
2018-04-13 09:19:50 +00:00
2018-06-21 05:50:42 +00:00
const int short_term_display_limit = 3 ;
2018-06-21 05:43:38 +00:00
2018-06-21 05:50:42 +00:00
if ( recentLogCount < short_term_display_limit )
{
2021-08-06 19:36:40 +00:00
Schedule ( ( ) = > Notifications . Post ( new SimpleErrorNotification
2017-12-25 08:12:21 +00:00
{
2019-04-02 10:55:24 +00:00
Icon = entry . Level = = LogLevel . Important ? FontAwesome . Solid . ExclamationCircle : FontAwesome . Solid . Bomb ,
2020-07-24 05:10:05 +00:00
Text = entry . Message . Truncate ( 256 ) + ( entry . Exception ! = null & & IsDeployedBuild ? "\n\nThis error has been automatically reported to the devs." : string . Empty ) ,
2018-06-21 05:50:42 +00:00
} ) ) ;
}
else if ( recentLogCount = = short_term_display_limit )
{
2021-10-27 04:04:41 +00:00
string logFile = $@"{entry.Target.ToString().ToLowerInvariant()}.log" ;
2021-10-17 10:17:38 +00:00
2021-08-06 19:36:40 +00:00
Schedule ( ( ) = > Notifications . Post ( new SimpleNotification
2018-06-21 05:50:42 +00:00
{
2019-04-02 10:55:24 +00:00
Icon = FontAwesome . Solid . EllipsisH ,
2018-06-21 05:50:42 +00:00
Text = "Subsequent messages have been logged. Click to view log files." ,
2017-12-25 08:12:21 +00:00
Activated = ( ) = >
{
2021-10-24 10:59:19 +00:00
Storage . GetStorageForDirectory ( @"logs" ) . PresentFileExternally ( logFile ) ;
2017-12-25 08:12:21 +00:00
return true ;
}
2018-06-21 05:43:38 +00:00
} ) ) ;
2017-12-25 08:12:21 +00:00
}
2018-04-13 09:19:50 +00:00
2018-06-21 05:43:38 +00:00
Interlocked . Increment ( ref recentLogCount ) ;
Scheduler . AddDelayed ( ( ) = > Interlocked . Decrement ( ref recentLogCount ) , debounce ) ;
2017-12-25 08:12:21 +00:00
} ;
}
2018-04-13 09:19:50 +00:00
2017-10-22 16:20:12 +00:00
private Task asyncLoadStream ;
2018-04-13 09:19:50 +00:00
2020-05-12 03:49:35 +00:00
/// <summary>
2020-05-13 02:09:17 +00:00
/// Queues loading the provided component in sequential fashion.
/// This operation is limited to a single thread to avoid saturating all cores.
2020-05-12 03:49:35 +00:00
/// </summary>
2020-05-13 02:09:17 +00:00
/// <param name="component">The component to load.</param>
/// <param name="loadCompleteAction">An action to invoke on load completion (generally to add the component to the hierarchy).</param>
2020-05-12 03:49:35 +00:00
/// <param name="cache">Whether to cache the component as type <typeparamref name="T"/> into the game dependencies before any scheduling.</param>
2022-04-18 09:36:22 +00:00
private T loadComponentSingleFile < T > ( T component , Action < Drawable > loadCompleteAction , bool cache = false )
where T : class
2017-10-22 16:20:12 +00:00
{
2019-05-13 08:10:25 +00:00
if ( cache )
2020-05-13 02:09:17 +00:00
dependencies . CacheAs ( component ) ;
2019-05-13 08:10:25 +00:00
2022-04-18 11:04:19 +00:00
var drawableComponent = component as Drawable ? ? throw new ArgumentException ( $"Component must be a {nameof(Drawable)}" , nameof ( component ) ) ;
2022-04-18 09:36:22 +00:00
2021-08-31 21:29:16 +00:00
if ( component is OsuFocusedOverlayContainer overlay )
focusedOverlays . Add ( overlay ) ;
2019-11-12 06:03:58 +00:00
2017-10-24 02:50:18 +00:00
// schedule is here to ensure that all component loads are done after LoadComplete is run (and thus all dependencies are cached).
// with some better organisation of LoadComplete to do construction and dependency caching in one step, followed by calls to loadComponentSingleFile,
// we could avoid the need for scheduling altogether.
2018-08-20 07:06:12 +00:00
Schedule ( ( ) = >
2018-08-20 05:42:37 +00:00
{
2018-08-29 06:11:02 +00:00
var previousLoadStream = asyncLoadStream ;
2020-05-05 01:31:11 +00:00
// chain with existing load stream
2018-08-29 06:11:02 +00:00
asyncLoadStream = Task . Run ( async ( ) = >
2018-08-20 05:42:37 +00:00
{
2018-08-29 06:11:02 +00:00
if ( previousLoadStream ! = null )
2021-03-08 03:57:16 +00:00
await previousLoadStream . ConfigureAwait ( false ) ;
2018-08-29 06:11:02 +00:00
try
2018-08-20 07:06:12 +00:00
{
2021-07-22 05:45:56 +00:00
Logger . Log ( $"Loading {component}..." ) ;
2019-02-28 03:24:56 +00:00
2019-02-28 08:17:51 +00:00
// Since this is running in a separate thread, it is possible for OsuGame to be disposed after LoadComponentAsync has been called
// throwing an exception. To avoid this, the call is scheduled on the update thread, which does not run if IsDisposed = true
Task task = null ;
2022-04-18 09:36:22 +00:00
var del = new ScheduledDelegate ( ( ) = > task = LoadComponentAsync ( drawableComponent , loadCompleteAction ) ) ;
2019-02-28 08:17:51 +00:00
Scheduler . Add ( del ) ;
// The delegate won't complete if OsuGame has been disposed in the meantime
while ( ! IsDisposed & & ! del . Completed )
2021-03-08 03:57:16 +00:00
await Task . Delay ( 10 ) . ConfigureAwait ( false ) ;
2019-02-28 08:17:51 +00:00
// Either we're disposed or the load process has started successfully
2019-02-28 03:24:56 +00:00
if ( IsDisposed )
return ;
2019-02-28 08:17:51 +00:00
Debug . Assert ( task ! = null ) ;
2021-03-08 03:57:16 +00:00
await task . ConfigureAwait ( false ) ;
2019-02-28 08:17:51 +00:00
2021-07-22 05:45:56 +00:00
Logger . Log ( $"Loaded {component}!" ) ;
2018-08-29 06:11:02 +00:00
}
catch ( OperationCanceledException )
{
}
} ) ;
2018-08-20 07:06:12 +00:00
} ) ;
2019-05-15 08:36:29 +00:00
2020-05-13 02:09:17 +00:00
return component ;
2020-03-02 09:56:09 +00:00
}
2021-09-16 09:26:12 +00:00
public bool OnPressed ( KeyBindingPressEvent < GlobalAction > e )
2016-10-07 15:43:06 +00:00
{
2021-11-18 03:35:47 +00:00
if ( e . Repeat )
return false ;
2019-01-23 11:52:00 +00:00
if ( introScreen = = null ) return false ;
2018-04-13 09:19:50 +00:00
2021-09-16 09:26:12 +00:00
switch ( e . Action )
2016-10-07 15:43:06 +00:00
{
2022-03-21 07:52:06 +00:00
case GlobalAction . ToggleSkinEditor :
skinEditor . ToggleVisibility ( ) ;
return true ;
2017-08-10 10:28:24 +00:00
case GlobalAction . ResetInputSettings :
2021-03-12 09:44:10 +00:00
Host . ResetInputHandlers ( ) ;
2018-02-22 01:08:22 +00:00
frameworkConfig . GetBindable < ConfineMouseMode > ( FrameworkSetting . ConfineMouseMode ) . SetDefault ( ) ;
2017-08-10 10:28:24 +00:00
return true ;
2019-04-01 03:16:05 +00:00
2018-05-02 10:42:03 +00:00
case GlobalAction . ToggleGameplayMouseButtons :
2021-03-15 05:47:05 +00:00
var mouseDisableButtons = LocalConfig . GetBindable < bool > ( OsuSetting . MouseDisableButtons ) ;
mouseDisableButtons . Value = ! mouseDisableButtons . Value ;
2017-08-10 10:28:24 +00:00
return true ;
2019-04-01 03:16:05 +00:00
2020-11-11 04:05:03 +00:00
case GlobalAction . RandomSkin :
2022-04-01 05:22:26 +00:00
// Don't allow random skin selection while in the skin editor.
// This is mainly to stop many "osu! default (modified)" skins being created via the SkinManager.EnsureMutableSkin() path.
// If people want this to work we can potentially avoid selecting default skins when the editor is open, or allow a maximum of one mutable skin somehow.
if ( skinEditor . State . Value = = Visibility . Visible )
return false ;
2020-11-11 04:05:03 +00:00
SkinManager . SelectRandomSkin ( ) ;
2018-05-02 10:37:47 +00:00
return true ;
2016-11-08 10:27:37 +00:00
}
2018-04-13 09:19:50 +00:00
2017-02-27 15:18:12 +00:00
return false ;
2016-10-07 15:43:06 +00:00
}
2018-04-13 09:19:50 +00:00
2021-10-10 19:19:56 +00:00
public override bool OnPressed ( KeyBindingPressEvent < PlatformAction > e )
{
2021-10-28 04:09:03 +00:00
const float adjustment_increment = 0.05f ;
2021-10-10 19:19:56 +00:00
switch ( e . Action )
{
case PlatformAction . ZoomIn :
2021-10-28 04:09:03 +00:00
uiScale . Value + = adjustment_increment ;
2021-10-10 19:19:56 +00:00
return true ;
case PlatformAction . ZoomOut :
2021-10-28 04:09:03 +00:00
uiScale . Value - = adjustment_increment ;
2021-10-10 19:19:56 +00:00
return true ;
case PlatformAction . ZoomDefault :
2021-10-28 04:09:03 +00:00
uiScale . SetDefault ( ) ;
2021-10-10 19:19:56 +00:00
return true ;
}
return base . OnPressed ( e ) ;
}
2019-06-17 14:25:16 +00:00
#region Inactive audio dimming
2019-06-17 14:24:52 +00:00
private readonly BindableDouble inactiveVolumeFade = new BindableDouble ( ) ;
2018-04-13 09:19:50 +00:00
2019-02-19 10:16:03 +00:00
private void updateActiveState ( bool isActive )
2018-01-31 09:11:38 +00:00
{
2019-02-19 10:16:03 +00:00
if ( isActive )
2019-06-17 16:32:52 +00:00
this . TransformBindableTo ( inactiveVolumeFade , 1 , 400 , Easing . OutQuint ) ;
2019-02-19 10:16:03 +00:00
else
2019-06-17 16:32:52 +00:00
this . TransformBindableTo ( inactiveVolumeFade , LocalConfig . Get < double > ( OsuSetting . VolumeInactive ) , 4000 , Easing . OutQuint ) ;
2018-01-31 09:11:38 +00:00
}
2018-04-13 09:19:50 +00:00
2019-06-17 14:25:16 +00:00
#endregion
2021-09-16 09:26:12 +00:00
public void OnReleased ( KeyBindingReleaseEvent < GlobalAction > e )
2020-01-22 04:22:34 +00:00
{
}
2018-04-13 09:19:50 +00:00
2019-01-24 11:13:29 +00:00
protected override bool OnExiting ( )
{
2019-07-29 05:30:46 +00:00
if ( ScreenStack . CurrentScreen is Loader )
2019-01-24 11:13:29 +00:00
return false ;
2020-05-09 10:11:51 +00:00
if ( introScreen ? . DidLoadMenu = = true & & ! ( ScreenStack . CurrentScreen is IntroScreen ) )
2019-01-24 11:13:29 +00:00
{
Scheduler . Add ( introScreen . MakeCurrent ) ;
return true ;
}
return base . OnExiting ( ) ;
}
2017-02-08 10:32:55 +00:00
protected override void UpdateAfterChildren ( )
{
base . UpdateAfterChildren ( ) ;
2018-04-13 09:19:50 +00:00
2021-08-29 03:13:01 +00:00
ScreenOffsetContainer . Padding = new MarginPadding { Top = toolbarOffset } ;
overlayOffsetContainer . Padding = new MarginPadding { Top = toolbarOffset } ;
2018-04-13 09:19:50 +00:00
2021-10-27 04:04:41 +00:00
float horizontalOffset = 0f ;
2021-08-07 16:33:22 +00:00
2021-08-21 12:39:57 +00:00
// Content.ToLocalSpace() is used instead of this.ToLocalSpace() to correctly calculate the offset with scaling modes active.
// Content is a child of a scaling container with ScalingMode.Everything set, while the game itself is never scaled.
// this avoids a visible jump in the positioning of the screen offset container.
2021-08-12 11:14:54 +00:00
if ( Settings . IsLoaded & & Settings . IsPresent )
2021-08-21 01:15:54 +00:00
horizontalOffset + = Content . ToLocalSpace ( Settings . ScreenSpaceDrawQuad . TopRight ) . X * SIDE_OVERLAY_OFFSET_RATIO ;
2021-08-12 11:14:54 +00:00
if ( Notifications . IsLoaded & & Notifications . IsPresent )
2021-08-21 01:15:54 +00:00
horizontalOffset + = ( Content . ToLocalSpace ( Notifications . ScreenSpaceDrawQuad . TopLeft ) . X - Content . DrawWidth ) * SIDE_OVERLAY_OFFSET_RATIO ;
2021-08-12 09:14:39 +00:00
ScreenOffsetContainer . X = horizontalOffset ;
2022-05-07 05:02:07 +00:00
overlayContent . X = horizontalOffset * 1.2f ;
2021-08-06 15:38:48 +00:00
2019-07-29 05:30:46 +00:00
MenuCursorContainer . CanShowCursor = ( ScreenStack . CurrentScreen as IOsuScreen ) ? . CursorVisible ? ? false ;
2017-02-08 10:32:55 +00:00
}
2018-04-13 09:19:50 +00:00
2022-03-11 10:54:13 +00:00
private void screenChanged ( IScreen current , IScreen newScreen )
2016-10-06 12:10:01 +00:00
{
2022-05-11 03:55:15 +00:00
SentrySdk . ConfigureScope ( scope = >
{
scope . Contexts [ @"screen stack" ] = new
{
2022-05-12 03:06:51 +00:00
Current = newScreen ? . GetType ( ) . ReadableName ( ) ,
Previous = current ? . GetType ( ) . ReadableName ( ) ,
2022-05-11 03:55:15 +00:00
} ;
2022-05-16 06:50:15 +00:00
scope . SetTag ( @"screen" , newScreen ? . GetType ( ) . ReadableName ( ) ? ? @"none" ) ;
2022-05-11 03:55:15 +00:00
} ) ;
2019-01-23 11:52:00 +00:00
switch ( newScreen )
{
2019-07-09 08:59:40 +00:00
case IntroScreen intro :
2019-01-23 11:52:00 +00:00
introScreen = intro ;
2022-01-15 18:42:38 +00:00
versionManager ? . Show ( ) ;
2019-01-23 11:52:00 +00:00
break ;
2019-04-01 03:16:05 +00:00
2019-01-23 11:52:00 +00:00
case MainMenu menu :
menuScreen = menu ;
2022-01-15 18:42:38 +00:00
versionManager ? . Show ( ) ;
break ;
default :
versionManager ? . Hide ( ) ;
2019-01-23 11:52:00 +00:00
break ;
}
2019-01-28 06:41:54 +00:00
2020-10-08 09:29:19 +00:00
// reset on screen change for sanity.
LocalUserPlaying . Value = false ;
2020-08-27 17:29:18 +00:00
if ( current is IOsuScreen currentOsuScreen )
2020-11-08 11:29:52 +00:00
{
2020-08-27 17:29:18 +00:00
OverlayActivationMode . UnbindFrom ( currentOsuScreen . OverlayActivationMode ) ;
2020-11-08 11:29:52 +00:00
API . Activity . UnbindFrom ( currentOsuScreen . Activity ) ;
}
2020-08-27 17:29:18 +00:00
2019-01-28 06:41:54 +00:00
if ( newScreen is IOsuScreen newOsuScreen )
{
2020-08-27 17:29:18 +00:00
OverlayActivationMode . BindTo ( newOsuScreen . OverlayActivationMode ) ;
2020-12-18 06:16:36 +00:00
API . Activity . BindTo ( newOsuScreen . Activity ) ;
2019-01-28 06:41:54 +00:00
if ( newOsuScreen . HideOverlaysOnEnter )
CloseAllOverlays ( ) ;
else
2019-06-11 05:28:52 +00:00
Toolbar . Show ( ) ;
2019-06-25 07:55:49 +00:00
2019-06-25 09:38:14 +00:00
if ( newOsuScreen . AllowBackButton )
2019-07-29 05:30:46 +00:00
BackButton . Show ( ) ;
2019-06-25 09:30:43 +00:00
else
2019-07-29 05:30:46 +00:00
BackButton . Hide ( ) ;
2019-01-28 06:41:54 +00:00
}
2022-03-11 14:08:40 +00:00
2022-03-15 09:10:30 +00:00
skinEditor . SetTarget ( ( OsuScreen ) newScreen ) ;
2018-12-27 10:18:27 +00:00
}
2022-03-11 10:54:13 +00:00
private void screenPushed ( IScreen lastScreen , IScreen newScreen ) = > screenChanged ( lastScreen , newScreen ) ;
2018-04-13 09:19:50 +00:00
2019-01-23 11:52:00 +00:00
private void screenExited ( IScreen lastScreen , IScreen newScreen )
2016-10-06 12:10:01 +00:00
{
2022-03-11 10:54:13 +00:00
screenChanged ( lastScreen , newScreen ) ;
2018-04-13 09:19:50 +00:00
2017-12-26 07:09:40 +00:00
if ( newScreen = = null )
Exit ( ) ;
2016-08-26 03:28:23 +00:00
}
2021-08-17 07:13:45 +00:00
IBindable < bool > ILocalUserPlayInfo . IsPlaying = > LocalUserPlaying ;
2016-08-26 03:28:23 +00:00
}
}