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
using System ;
using System.Collections.Generic ;
2019-02-28 08:17:51 +00:00
using System.Diagnostics ;
2018-04-13 09:19:50 +00:00
using osu.Framework.Configuration ;
using osu.Framework.Screens ;
using osu.Game.Configuration ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Game.Overlays ;
using osu.Framework.Logging ;
using osu.Framework.Allocation ;
using osu.Game.Overlays.Toolbar ;
using osu.Game.Screens ;
using osu.Game.Screens.Menu ;
using System.Linq ;
using System.Threading ;
using System.Threading.Tasks ;
using osu.Framework.Audio ;
2019-02-21 10:04:31 +00:00
using osu.Framework.Bindables ;
2019-07-23 04:38:05 +00:00
using osu.Framework.Development ;
2018-07-13 11:32:22 +00:00
using osu.Framework.Extensions.IEnumerableExtensions ;
2019-03-27 10:29:27 +00:00
using osu.Framework.Graphics.Sprites ;
2018-04-13 09:19:50 +00:00
using osu.Framework.Input ;
using osu.Framework.Input.Bindings ;
using osu.Framework.Platform ;
using osu.Framework.Threading ;
2018-07-10 16:32:10 +00:00
using osu.Game.Beatmaps ;
2018-04-13 09:19:50 +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 ;
2018-04-13 09:19:50 +00:00
using osu.Game.Overlays.Notifications ;
using osu.Game.Screens.Play ;
using osu.Game.Input.Bindings ;
2018-09-14 03:06:04 +00:00
using osu.Game.Online.Chat ;
2018-04-13 09:19:50 +00:00
using osu.Game.Skinning ;
2018-11-20 07:51:59 +00:00
using osuTK.Graphics ;
2018-04-13 09:19:50 +00:00
using osu.Game.Overlays.Volume ;
2018-11-28 07:12:57 +00:00
using osu.Game.Scoring ;
2018-07-10 16:32:10 +00:00
using osu.Game.Screens.Select ;
2018-08-03 10:25:55 +00:00
using osu.Game.Utils ;
using LogLevel = osu . Framework . Logging . LogLevel ;
2018-04-13 09:19:50 +00:00
namespace osu.Game
{
/// <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>
public class OsuGame : OsuGameBase , IKeyBindingHandler < GlobalAction >
{
public Toolbar Toolbar ;
2018-11-23 02:00:17 +00:00
private ChatOverlay chatOverlay ;
private ChannelManager channelManager ;
2018-04-13 09:19:50 +00:00
private NotificationOverlay notifications ;
private DirectOverlay direct ;
private SocialOverlay social ;
private UserProfileOverlay userProfile ;
private BeatmapSetOverlay beatmapSetOverlay ;
2018-10-02 01:12:07 +00:00
[Cached]
private readonly ScreenshotManager screenshotManager = new ScreenshotManager ( ) ;
2018-04-13 12:13:09 +00:00
2018-08-03 10:25:55 +00:00
protected RavenLogger RavenLogger ;
2018-04-13 09:19:50 +00:00
public virtual Storage GetStorageForStableInstall ( ) = > null ;
public float ToolbarOffset = > Toolbar . Position . Y + Toolbar . DrawHeight ;
2018-11-19 17:48:59 +00:00
private IdleTracker idleTracker ;
2018-06-06 06:49:27 +00:00
public readonly Bindable < OverlayActivation > OverlayActivationMode = new Bindable < OverlayActivation > ( ) ;
2018-04-13 09:19:50 +00:00
2019-03-12 08:33:16 +00:00
private OsuScreenStack screenStack ;
2018-04-13 09:19:50 +00:00
private VolumeOverlay volume ;
2019-01-23 11:52:00 +00:00
private OsuLogo osuLogo ;
2019-06-25 07:55:49 +00:00
private BackButton backButton ;
2019-01-23 11:52:00 +00:00
private MainMenu menuScreen ;
2019-07-09 08:59:40 +00:00
private IntroScreen introScreen ;
2018-04-13 09:19:50 +00:00
private Bindable < int > configRuleset ;
private Bindable < int > configSkin ;
private readonly string [ ] args ;
2019-05-14 01:45:05 +00:00
private SettingsPanel settings ;
2018-04-13 09:19:50 +00:00
2018-06-06 07:17:51 +00:00
private readonly List < OverlayContainer > overlays = new List < OverlayContainer > ( ) ;
2019-05-14 09:34:25 +00:00
private readonly List < OverlayContainer > toolbarElements = new List < OverlayContainer > ( ) ;
2019-03-01 03:20:31 +00:00
private readonly List < OverlayContainer > visibleBlockingOverlays = new List < OverlayContainer > ( ) ;
2018-04-13 09:19:50 +00:00
public OsuGame ( string [ ] args = null )
{
this . args = args ;
2018-06-21 05:43:38 +00:00
forwardLoggedErrorsToNotifications ( ) ;
2018-08-03 10:25:55 +00:00
2018-09-12 17:34:52 +00:00
RavenLogger = new RavenLogger ( this ) ;
2018-04-13 09:19:50 +00:00
}
2019-03-01 03:20:31 +00:00
private void updateBlockingOverlayFade ( ) = >
screenContainer . FadeColour ( visibleBlockingOverlays . Any ( ) ? OsuColour . Gray ( 0.5f ) : Color4 . White , 500 , Easing . OutQuint ) ;
public void AddBlockingOverlay ( OverlayContainer overlay )
{
if ( ! visibleBlockingOverlays . Contains ( overlay ) )
visibleBlockingOverlays . Add ( overlay ) ;
updateBlockingOverlayFade ( ) ;
}
public void RemoveBlockingOverlay ( OverlayContainer overlay )
{
2019-03-01 04:29:02 +00:00
visibleBlockingOverlays . Remove ( overlay ) ;
2019-03-01 03:20:31 +00:00
updateBlockingOverlayFade ( ) ;
}
2018-06-06 07:17:51 +00:00
/// <summary>
/// Close all game-wide overlays.
/// </summary>
2019-05-12 13:34:36 +00:00
/// <param name="hideToolbarElements">Whether the toolbar (and accompanying controls) should also be hidden.</param>
public void CloseAllOverlays ( bool hideToolbarElements = true )
2018-06-06 07:17:51 +00:00
{
2018-08-29 22:04:51 +00:00
foreach ( var overlay in overlays )
2019-06-11 05:28:52 +00:00
overlay . Hide ( ) ;
2019-05-12 13:34:36 +00:00
if ( hideToolbarElements )
{
2019-05-14 09:34:25 +00:00
foreach ( var overlay in toolbarElements )
2019-06-11 05:28:52 +00:00
overlay . Hide ( ) ;
2019-05-12 13:34:36 +00:00
}
2018-06-06 07:17:51 +00:00
}
2018-04-13 09:19:50 +00:00
private DependencyContainer dependencies ;
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
[BackgroundDependencyLoader]
private void load ( FrameworkConfigManager frameworkConfig )
{
this . frameworkConfig = frameworkConfig ;
2019-07-23 04:38:05 +00:00
if ( ! Host . IsPrimaryInstance & & ! DebugUtils . IsDebugBuild )
2018-04-13 09:19:50 +00:00
{
Logger . Log ( @"osu! does not support multiple running instances." , LoggingTarget . Runtime , LogLevel . Error ) ;
Environment . Exit ( 0 ) ;
}
if ( args ? . Length > 0 )
{
2018-08-16 13:01:04 +00:00
var paths = args . Where ( a = > ! a . StartsWith ( @"-" ) ) . ToArray ( ) ;
if ( paths . Length > 0 )
Task . Run ( ( ) = > Import ( paths ) ) ;
2018-04-13 09:19:50 +00:00
}
dependencies . CacheAs ( this ) ;
2018-08-03 10:25:55 +00:00
dependencies . Cache ( RavenLogger ) ;
2019-01-31 10:22:29 +00:00
dependencies . Cache ( osuLogo = new OsuLogo { Alpha = 0 } ) ;
2019-01-23 11:52:00 +00:00
2018-04-13 09:19:50 +00:00
// bind config int to database RulesetInfo
configRuleset = LocalConfig . GetBindable < int > ( OsuSetting . Ruleset ) ;
2019-05-15 04:00:11 +00:00
Ruleset . Value = RulesetStore . GetRuleset ( configRuleset . Value ) ? ? RulesetStore . AvailableRulesets . First ( ) ;
Ruleset . ValueChanged + = r = > configRuleset . Value = r . NewValue . ID ? ? 0 ;
2018-04-13 09:19:50 +00:00
// bind config int to database SkinInfo
configSkin = LocalConfig . GetBindable < int > ( OsuSetting . Skin ) ;
2019-02-22 08:51:39 +00:00
SkinManager . CurrentSkinInfo . ValueChanged + = skin = > configSkin . Value = skin . NewValue . ID ;
2019-08-29 07:38:39 +00:00
configSkin . ValueChanged + = skinId = >
{
var skinInfo = SkinManager . Query ( s = > s . ID = = skinId . NewValue ) ;
if ( skinInfo = = null )
{
switch ( skinId . NewValue )
{
case - 1 :
skinInfo = DefaultLegacySkin . Info ;
break ;
default :
skinInfo = SkinInfo . Default ;
break ;
}
}
SkinManager . CurrentSkinInfo . Value = skinInfo ;
} ;
2018-04-13 09:19:50 +00:00
configSkin . TriggerChange ( ) ;
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-06-20 14:40:25 +00:00
Beatmap . BindValueChanged ( beatmapChanged , true ) ;
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
2018-12-06 03:17:08 +00:00
public void OpenUrlExternally ( string url )
{
if ( url . StartsWith ( "/" ) )
url = $"{API.Endpoint}{url}" ;
externalLinkOpener . OpenUrlExternally ( url ) ;
}
2018-11-01 20:52:07 +00:00
2018-04-13 09:19:50 +00:00
/// <summary>
/// Show a beatmap set as an overlay.
/// </summary>
/// <param name="setId">The set to display.</param>
2018-04-18 06:58:45 +00:00
public void ShowBeatmapSet ( int setId ) = > 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>
/// <param name="userId">The user to display.</param>
public void ShowUser ( long userId ) = > userProfile . ShowUser ( userId ) ;
/// <summary>
/// Show a beatmap's set as an overlay, displaying the given beatmap.
/// </summary>
/// <param name="beatmapId">The beatmap to show.</param>
public void ShowBeatmap ( int beatmapId ) = > beatmapSetOverlay . FetchAndShowBeatmap ( beatmapId ) ;
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>
public void PresentBeatmap ( BeatmapSetInfo beatmap )
{
2019-02-25 03:58:58 +00:00
var databasedSet = beatmap . OnlineBeatmapSetID ! = null
? BeatmapManager . QueryBeatmapSet ( s = > s . OnlineBeatmapSetID = = beatmap . OnlineBeatmapSetID )
: BeatmapManager . QueryBeatmapSet ( s = > s . Hash = = beatmap . Hash ) ;
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 ;
}
2019-02-25 09:24:06 +00:00
performFromMainMenu ( ( ) = >
2018-07-13 12:08:41 +00:00
{
2019-02-25 09:24:06 +00:00
// we might already be at song select, so a check is required before performing the load to solo.
if ( menuScreen . IsCurrentScreen ( ) )
menuScreen . LoadToSolo ( ) ;
2019-02-24 03:08:27 +00:00
2019-04-02 15:57:31 +00:00
// we might even already be at the song
2019-04-03 13:49:33 +00:00
if ( Beatmap . Value . BeatmapSetInfo . Hash = = databasedSet . Hash )
2019-04-02 15:57:31 +00:00
{
return ;
}
2019-02-25 03:58:58 +00:00
// Use first beatmap available for current ruleset, else switch ruleset.
2019-07-15 06:53:19 +00:00
var first = databasedSet . Beatmaps . Find ( b = > b . Ruleset . Equals ( Ruleset . Value ) ) ? ? databasedSet . Beatmaps . First ( ) ;
2019-02-24 03:08:27 +00:00
2019-05-15 04:00:11 +00:00
Ruleset . Value = first . Ruleset ;
2019-02-25 03:58:58 +00:00
Beatmap . Value = BeatmapManager . GetWorkingBeatmap ( first ) ;
2019-02-25 09:24:06 +00:00
} , $"load {beatmap}" , bypassScreenAllowChecks : true , targetScreen : typeof ( PlaySongSelect ) ) ;
2018-07-10 16:32:10 +00:00
}
2018-04-13 09:19:50 +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-04-13 09:19:50 +00:00
/// </summary>
2019-02-25 03:58:58 +00:00
public void PresentScore ( ScoreInfo score )
2018-04-13 09:19:50 +00:00
{
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.
2019-07-29 21:31:45 +00:00
var databasedScoreInfo = score . OnlineScoreID ! = null
? ScoreManager . Query ( s = > s . OnlineScoreID = = score . OnlineScoreID )
: ScoreManager . Query ( s = > s . Hash = = score . Hash ) ;
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
2018-11-30 09:31:54 +00:00
var databasedBeatmap = BeatmapManager . QueryBeatmap ( b = > b . ID = = databasedScoreInfo . Beatmap . 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 ;
}
2019-02-25 03:58:58 +00:00
performFromMainMenu ( ( ) = >
{
Beatmap . Value = BeatmapManager . GetWorkingBeatmap ( databasedBeatmap ) ;
2019-07-08 07:13:03 +00:00
menuScreen . Push ( new ReplayPlayerLoader ( databasedScore ) ) ;
2019-02-25 09:42:08 +00:00
} , $"watch {databasedScoreInfo}" , bypassScreenAllowChecks : true ) ;
2019-02-25 03:58:58 +00:00
}
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 )
{
var nextBeatmap = beatmap . NewValue ;
if ( nextBeatmap ? . Track ! = null )
nextBeatmap . Track . Completed + = currentTrackCompleted ;
2019-06-24 08:10:50 +00:00
2019-08-01 07:04:04 +00:00
using ( var oldBeatmap = beatmap . OldValue )
if ( oldBeatmap ? . Track ! = null )
oldBeatmap . Track . Completed - = currentTrackCompleted ;
2019-07-02 13:25:03 +00:00
2019-06-24 08:10:50 +00:00
nextBeatmap ? . LoadBeatmapAsync ( ) ;
2019-06-20 14:40:25 +00:00
}
private void currentTrackCompleted ( )
{
if ( ! Beatmap . Value . Track . Looping & & ! Beatmap . Disabled )
musicController . NextTrack ( ) ;
}
#endregion
2019-02-25 03:58:58 +00:00
private ScheduledDelegate performFromMainMenuTask ;
/// <summary>
/// Perform an action only after returning to the main menu.
/// Eagerly tries to exit the current screen until it succeeds.
/// </summary>
/// <param name="action">The action to perform once we are in the correct state.</param>
/// <param name="taskName">The task name to display in a notification (if we can't immediately reach the main menu state).</param>
2019-02-25 05:01:51 +00:00
/// <param name="targetScreen">An optional target screen type. If this screen is already current we can immediately perform the action without returning to the menu.</param>
/// <param name="bypassScreenAllowChecks">Whether checking <see cref="IOsuScreen.AllowExternalScreenChange"/> should be bypassed.</param>
private void performFromMainMenu ( Action action , string taskName , Type targetScreen = null , bool bypassScreenAllowChecks = false )
2019-02-25 03:58:58 +00:00
{
2019-02-25 05:01:51 +00:00
performFromMainMenuTask ? . Cancel ( ) ;
// if the current screen does not allow screen changing, give the user an option to try again later.
if ( ! bypassScreenAllowChecks & & ( screenStack . CurrentScreen as IOsuScreen ) ? . AllowExternalScreenChange = = false )
2018-04-13 09:19:50 +00:00
{
2018-11-29 08:18:59 +00:00
notifications . Post ( new SimpleNotification
{
2019-02-25 03:58:58 +00:00
Text = $"Click here to {taskName}" ,
2018-11-29 08:18:59 +00:00
Activated = ( ) = >
{
2019-02-25 05:01:51 +00:00
performFromMainMenu ( action , taskName , targetScreen , true ) ;
2018-11-29 08:18:59 +00:00
return true ;
}
} ) ;
2018-04-13 09:19:50 +00:00
return ;
}
2019-02-25 03:58:58 +00:00
CloseAllOverlays ( false ) ;
2018-11-29 08:18:59 +00:00
2019-02-25 05:01:51 +00:00
// we may already be at the target screen type.
if ( targetScreen ! = null & & screenStack . CurrentScreen ? . GetType ( ) = = targetScreen )
2019-02-25 03:58:58 +00:00
{
2019-02-25 05:01:51 +00:00
action ( ) ;
2019-02-25 03:58:58 +00:00
return ;
2018-11-29 08:18:59 +00:00
}
2019-02-25 03:58:58 +00:00
2019-02-25 05:01:51 +00:00
// all conditions have been met to continue with the action.
if ( menuScreen ? . IsCurrentScreen ( ) = = true & & ! Beatmap . Disabled )
{
action ( ) ;
return ;
}
// menuScreen may not be initialised yet (null check required).
menuScreen ? . MakeCurrent ( ) ;
performFromMainMenuTask = Schedule ( ( ) = > performFromMainMenu ( action , taskName ) ) ;
2018-04-13 09:19:50 +00:00
}
2018-08-03 10:25:55 +00:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
RavenLogger . Dispose ( ) ;
}
2018-04-13 09:19:50 +00:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
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.
2018-04-13 09:19:50 +00:00
SkinManager . PostNotification = n = > notifications ? . Post ( n ) ;
2018-08-31 09:28:53 +00:00
SkinManager . GetStableStorage = GetStorageForStableInstall ;
2018-04-13 09:19:50 +00:00
2018-08-31 09:28:53 +00:00
BeatmapManager . PostNotification = n = > notifications ? . Post ( n ) ;
2018-04-13 09:19:50 +00:00
BeatmapManager . GetStableStorage = GetStorageForStableInstall ;
2019-02-25 09:24:06 +00:00
BeatmapManager . PresentImport = items = > PresentBeatmap ( items . First ( ) ) ;
2018-08-31 09:28:53 +00:00
2019-02-25 09:24:06 +00:00
ScoreManager . PostNotification = n = > notifications ? . Post ( n ) ;
2019-06-19 16:33:51 +00:00
ScoreManager . GetStableStorage = GetStorageForStableInstall ;
2019-02-25 09:24:06 +00:00
ScoreManager . PresentImport = items = > PresentScore ( items . First ( ) ) ;
2018-04-13 09:19:50 +00:00
2019-01-23 11:52:00 +00:00
Container logoContainer ;
2019-05-13 08:10:25 +00:00
dependencies . CacheAs ( idleTracker = new GameIdleTracker ( 6000 ) ) ;
2018-04-13 09:19:50 +00:00
AddRange ( new Drawable [ ]
{
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 ) ,
2018-04-13 09:19:50 +00:00
} ,
2019-01-04 04:29:37 +00:00
screenContainer = new ScalingContainer ( ScalingMode . ExcludeOverlays )
{
RelativeSizeAxes = Axes . Both ,
2019-01-31 09:25:25 +00:00
Children = new Drawable [ ]
{
2019-03-24 07:21:43 +00:00
screenStack = new OsuScreenStack { RelativeSizeAxes = Axes . Both } ,
2019-06-25 08:16:38 +00:00
backButton = new BackButton
{
Anchor = Anchor . BottomLeft ,
Origin = Anchor . BottomLeft ,
2019-06-25 08:17:29 +00:00
Action = ( ) = >
{
if ( ( screenStack . CurrentScreen as IOsuScreen ) ? . AllowBackButton = = true )
screenStack . Exit ( ) ;
}
2019-06-25 08:16:38 +00:00
} ,
2019-01-31 09:25:25 +00:00
logoContainer = new Container { RelativeSizeAxes = Axes . Both } ,
}
2019-01-04 04:29:37 +00:00
} ,
2019-03-20 03:51:43 +00:00
overlayContent = new Container { RelativeSizeAxes = Axes . Both } ,
2019-04-05 07:00:21 +00:00
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 } ,
2019-05-13 08:10:25 +00:00
idleTracker
2018-04-13 09:19:50 +00:00
} ) ;
2019-01-23 11:52:00 +00:00
screenStack . ScreenPushed + = screenPushed ;
screenStack . ScreenExited + = screenExited ;
2019-03-12 07:03:25 +00:00
loadComponentSingleFile ( osuLogo , logo = >
2018-04-13 09:19:50 +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-03-12 07:03:25 +00:00
screenStack . Push ( new Loader
{
RelativeSizeAxes = Axes . Both
} ) ;
} ) ;
2018-04-13 09:19:50 +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 ( ) ;
2018-04-13 09:19:50 +00:00
} ,
2019-05-14 09:34:25 +00:00
} , d = >
{
topMostOverlayContent . Add ( d ) ;
toolbarElements . Add ( d ) ;
} ) ;
2018-04-13 09:19:50 +00:00
2019-04-05 07:05:42 +00:00
loadComponentSingleFile ( volume = new VolumeOverlay ( ) , leftFloatingOverlayContent . Add ) ;
2019-08-13 05:38:49 +00:00
loadComponentSingleFile ( new OnScreenDisplay ( ) , Add , true ) ;
2018-04-13 12:13:09 +00:00
2019-08-13 05:29:58 +00:00
loadComponentSingleFile ( musicController = new MusicController ( ) , Add , true ) ;
2019-04-06 15:40:48 +00:00
loadComponentSingleFile ( notifications = new NotificationOverlay
2019-03-29 05:53:40 +00:00
{
GetToolbarHeight = ( ) = > ToolbarOffset ,
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
2019-05-13 08:10:25 +00:00
} , rightFloatingOverlayContent . Add , true ) ;
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
//overlay elements
2019-05-13 08:10:25 +00:00
loadComponentSingleFile ( direct = new DirectOverlay ( ) , overlayContent . Add , true ) ;
loadComponentSingleFile ( social = new SocialOverlay ( ) , overlayContent . Add , true ) ;
loadComponentSingleFile ( channelManager = new ChannelManager ( ) , AddInternal , true ) ;
loadComponentSingleFile ( chatOverlay = new ChatOverlay ( ) , overlayContent . Add , true ) ;
2019-05-14 01:45:05 +00:00
loadComponentSingleFile ( settings = new SettingsOverlay { GetToolbarHeight = ( ) = > ToolbarOffset } , leftFloatingOverlayContent . Add , true ) ;
2019-05-31 04:23:50 +00:00
var changelogOverlay = loadComponentSingleFile ( 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 ) ;
loadComponentSingleFile ( new LoginOverlay
2018-04-13 09:19:50 +00:00
{
2019-03-20 04:30:24 +00:00
GetToolbarHeight = ( ) = > ToolbarOffset ,
2018-04-13 09:19: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
2019-08-13 05:29:58 +00:00
loadComponentSingleFile ( new NowPlayingOverlay
2018-04-13 09:19:50 +00:00
{
2019-03-28 03:27:26 +00:00
GetToolbarHeight = ( ) = > ToolbarOffset ,
2018-04-13 09:19:50 +00:00
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
2019-05-14 09:34:25 +00:00
} , d = >
{
rightFloatingOverlayContent . Add ( d ) ;
toolbarElements . Add ( d ) ;
} , true ) ;
2018-04-13 09:19:50 +00:00
2019-05-13 08:10:25 +00:00
loadComponentSingleFile ( new AccountCreationOverlay ( ) , topMostOverlayContent . Add , true ) ;
loadComponentSingleFile ( new DialogOverlay ( ) , topMostOverlayContent . Add , true ) ;
2019-03-21 18:16:10 +00:00
loadComponentSingleFile ( externalLinkOpener = new ExternalLinkOpener ( ) , topMostOverlayContent . Add ) ;
2018-12-06 02:55:58 +00:00
2019-06-11 05:28:52 +00:00
chatOverlay . State . ValueChanged + = state = > channelManager . HighPollRate . Value = state . NewValue = = Visibility . Visible ;
2018-12-10 12:08:14 +00:00
2018-10-23 20:03:00 +00:00
Add ( externalLinkOpener = new ExternalLinkOpener ( ) ) ;
2018-07-13 11:32:22 +00:00
var singleDisplaySideOverlays = new OverlayContainer [ ] { settings , notifications } ;
overlays . AddRange ( singleDisplaySideOverlays ) ;
2018-06-06 07:17:51 +00:00
2018-07-13 11:32:22 +00:00
foreach ( var overlay in singleDisplaySideOverlays )
2018-04-13 09:19:50 +00:00
{
2019-06-11 05:28:52 +00:00
overlay . State . ValueChanged + = state = >
2018-04-13 09:19:50 +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 ( ) ) ;
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-07-13 11:32:22 +00:00
overlays . AddRange ( informationalOverlays ) ;
2018-06-06 07:17:51 +00:00
2018-07-13 11:32:22 +00:00
foreach ( var overlay in informationalOverlays )
2018-04-13 09:19:50 +00:00
{
2019-06-11 05:28:52 +00:00
overlay . State . ValueChanged + = state = >
2018-04-13 09:19:50 +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
informationalOverlays . Where ( o = > o ! = overlay ) . ForEach ( o = > o . Hide ( ) ) ;
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.
2019-05-31 04:23:50 +00:00
var singleDisplayOverlays = new OverlayContainer [ ] { chatOverlay , social , direct , changelogOverlay } ;
2018-07-13 11:32:22 +00:00
overlays . AddRange ( singleDisplayOverlays ) ;
2018-06-06 07:17:51 +00:00
2018-07-13 11:32:22 +00:00
foreach ( var overlay in singleDisplayOverlays )
2018-04-13 09:19:50 +00:00
{
2019-06-11 05:28:52 +00:00
overlay . State . ValueChanged + = state = >
2018-04-13 09:19:50 +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 ( ) ) ;
2019-06-11 05:28:52 +00:00
if ( state . NewValue = = Visibility . Hidden ) return ;
2018-04-13 09:19:50 +00:00
2018-07-13 11:32:22 +00:00
singleDisplayOverlays . Where ( o = > o ! = overlay ) . ForEach ( o = > o . Hide ( ) ) ;
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
} ;
2018-04-13 09:19:50 +00:00
void updateScreenOffset ( )
{
float offset = 0 ;
2019-06-11 05:28:52 +00:00
if ( settings . State . Value = = Visibility . Visible )
2018-04-13 09:19:50 +00:00
offset + = ToolbarButton . WIDTH / 2 ;
2019-06-11 05:28:52 +00:00
if ( notifications . State . Value = = Visibility . Visible )
2018-04-13 09:19:50 +00:00
offset - = ToolbarButton . WIDTH / 2 ;
2019-05-14 01:45:05 +00:00
screenContainer . MoveToX ( offset , SettingsPanel . TRANSITION_LENGTH , Easing . OutQuint ) ;
2018-04-13 09:19:50 +00:00
}
2019-06-11 05:28:52 +00:00
settings . State . ValueChanged + = _ = > updateScreenOffset ( ) ;
notifications . State . ValueChanged + = _ = > updateScreenOffset ( ) ;
2018-05-28 11:43:47 +00:00
}
2018-04-13 09:19:50 +00:00
2018-12-26 09:39:57 +00:00
public class GameIdleTracker : IdleTracker
{
2019-01-23 11:37:56 +00:00
private InputManager inputManager ;
2018-12-26 09:39:57 +00:00
public GameIdleTracker ( int time )
: base ( time )
{
}
2019-01-23 11:37:56 +00:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
inputManager = GetContainingInputManager ( ) ;
}
protected override bool AllowIdle = > inputManager . FocusedDrawable = = null ;
2018-12-26 09:39:57 +00:00
}
2018-04-13 09:19:50 +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
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 )
{
2018-06-21 05:43:38 +00:00
Schedule ( ( ) = > notifications . Post ( new SimpleNotification
2018-04-13 09:19:50 +00:00
{
2019-04-02 10:55:24 +00:00
Icon = entry . Level = = LogLevel . Important ? FontAwesome . Solid . ExclamationCircle : FontAwesome . Solid . Bomb ,
2018-08-17 04:18:48 +00:00
Text = entry . Message + ( 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 )
{
Schedule ( ( ) = > notifications . Post ( new SimpleNotification
{
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." ,
2018-04-13 09:19:50 +00:00
Activated = ( ) = >
{
Host . Storage . GetStorageForDirectory ( "logs" ) . OpenInNativeExplorer ( ) ;
return true ;
}
2018-06-21 05:43:38 +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 ) ;
2018-04-13 09:19:50 +00:00
} ;
}
private Task asyncLoadStream ;
2019-05-15 08:36:29 +00:00
private T loadComponentSingleFile < T > ( T d , Action < T > add , bool cache = false )
2018-04-13 09:19:50 +00:00
where T : Drawable
{
2019-05-13 08:10:25 +00:00
if ( cache )
dependencies . Cache ( d ) ;
2018-04-13 09:19:50 +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 ;
//chain with existing load stream
asyncLoadStream = Task . Run ( async ( ) = >
2018-08-20 05:42:37 +00:00
{
2018-08-29 06:11:02 +00:00
if ( previousLoadStream ! = null )
await previousLoadStream ;
try
2018-08-20 07:06:12 +00:00
{
2018-12-07 10:39:54 +00:00
Logger . Log ( $"Loading {d}..." , level : LogLevel . Debug ) ;
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 ;
var del = new ScheduledDelegate ( ( ) = > task = LoadComponentAsync ( d , add ) ) ;
Scheduler . Add ( del ) ;
// The delegate won't complete if OsuGame has been disposed in the meantime
while ( ! IsDisposed & & ! del . Completed )
await Task . Delay ( 10 ) ;
// 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 ) ;
await task ;
2018-12-07 10:39:54 +00:00
Logger . Log ( $"Loaded {d}!" , level : LogLevel . Debug ) ;
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
return d ;
2018-04-13 09:19:50 +00:00
}
public bool OnPressed ( GlobalAction action )
{
2019-01-23 11:52:00 +00:00
if ( introScreen = = null ) return false ;
2018-04-13 09:19:50 +00:00
switch ( action )
{
case GlobalAction . ToggleChat :
2018-11-23 02:00:17 +00:00
chatOverlay . ToggleVisibility ( ) ;
2018-04-13 09:19:50 +00:00
return true ;
2019-04-01 03:16:05 +00:00
2018-04-13 09:19:50 +00:00
case GlobalAction . ToggleSocial :
social . ToggleVisibility ( ) ;
return true ;
2019-04-01 03:16:05 +00:00
2018-04-13 09:19:50 +00:00
case GlobalAction . ResetInputSettings :
var sensitivity = frameworkConfig . GetBindable < double > ( FrameworkSetting . CursorSensitivity ) ;
sensitivity . Disabled = false ;
sensitivity . Value = 1 ;
sensitivity . Disabled = true ;
2018-04-13 12:46:17 +00:00
frameworkConfig . Set ( FrameworkSetting . IgnoredInputHandlers , string . Empty ) ;
2018-04-13 09:19:50 +00:00
frameworkConfig . GetBindable < ConfineMouseMode > ( FrameworkSetting . ConfineMouseMode ) . SetDefault ( ) ;
return true ;
2019-04-01 03:16:05 +00:00
2018-04-13 09:19:50 +00:00
case GlobalAction . ToggleToolbar :
Toolbar . ToggleVisibility ( ) ;
return true ;
2019-04-01 03:16:05 +00:00
2018-04-13 09:19:50 +00:00
case GlobalAction . ToggleSettings :
settings . ToggleVisibility ( ) ;
return true ;
2019-04-01 03:16:05 +00:00
2018-04-13 09:19:50 +00:00
case GlobalAction . ToggleDirect :
direct . ToggleVisibility ( ) ;
return true ;
2019-04-01 03:16:05 +00:00
2018-05-02 10:42:03 +00:00
case GlobalAction . ToggleGameplayMouseButtons :
2018-05-02 10:37:47 +00:00
LocalConfig . Set ( OsuSetting . MouseDisableButtons , ! LocalConfig . Get < bool > ( OsuSetting . MouseDisableButtons ) ) ;
return true ;
2018-04-13 09:19:50 +00:00
}
return false ;
}
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-04-13 09:19:50 +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-04-13 09:19:50 +00:00
}
2019-06-17 14:25:16 +00:00
#endregion
2018-04-13 09:19:50 +00:00
public bool OnReleased ( GlobalAction action ) = > false ;
private Container overlayContent ;
2019-04-05 07:00:21 +00:00
private Container rightFloatingOverlayContent ;
private Container leftFloatingOverlayContent ;
2019-01-21 10:34:35 +00:00
2019-03-21 18:16:10 +00:00
private Container topMostOverlayContent ;
2019-03-20 04:30:24 +00:00
2018-04-13 09:19:50 +00:00
private FrameworkConfigManager frameworkConfig ;
2019-06-20 14:40:25 +00:00
2019-01-04 04:29:37 +00:00
private ScalingContainer screenContainer ;
2018-04-13 09:19:50 +00:00
2019-06-20 14:40:25 +00:00
private MusicController musicController ;
2019-01-24 11:13:29 +00:00
protected override bool OnExiting ( )
{
if ( screenStack . CurrentScreen is Loader )
return false ;
if ( introScreen = = null )
return true ;
2019-07-09 08:59:40 +00:00
if ( ! introScreen . DidLoadMenu | | ! ( screenStack . CurrentScreen is IntroScreen ) )
2019-01-24 11:13:29 +00:00
{
Scheduler . Add ( introScreen . MakeCurrent ) ;
return true ;
}
return base . OnExiting ( ) ;
}
2018-04-13 09:19:50 +00:00
/// <summary>
/// Use to programatically exit the game as if the user was triggering via alt-f4.
/// Will keep persisting until an exit occurs (exit may be blocked multiple times).
/// </summary>
public void GracefullyExit ( )
{
if ( ! OnExiting ( ) )
Exit ( ) ;
else
Scheduler . AddDelayed ( GracefullyExit , 2000 ) ;
}
protected override void UpdateAfterChildren ( )
{
base . UpdateAfterChildren ( ) ;
2019-01-08 03:57:31 +00:00
screenContainer . Padding = new MarginPadding { Top = ToolbarOffset } ;
2019-01-21 10:34:35 +00:00
overlayContent . Padding = new MarginPadding { Top = ToolbarOffset } ;
2018-04-13 09:19:50 +00:00
2019-01-23 11:52:00 +00:00
MenuCursorContainer . CanShowCursor = ( screenStack . CurrentScreen as IOsuScreen ) ? . CursorVisible ? ? false ;
2018-04-13 09:19:50 +00:00
}
2019-02-01 06:42:15 +00:00
protected virtual void ScreenChanged ( IScreen current , IScreen newScreen )
2018-04-13 09:19:50 +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 ;
break ;
2019-04-01 03:16:05 +00:00
2019-01-23 11:52:00 +00:00
case MainMenu menu :
menuScreen = menu ;
break ;
}
2019-01-28 06:41:54 +00:00
if ( newScreen is IOsuScreen newOsuScreen )
{
OverlayActivationMode . Value = newOsuScreen . InitialOverlayActivationMode ;
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-06-25 09:30:43 +00:00
backButton . Show ( ) ;
else
backButton . Hide ( ) ;
2019-01-28 06:41:54 +00:00
}
2018-12-27 10:18:27 +00:00
}
2019-01-23 11:52:00 +00:00
private void screenPushed ( IScreen lastScreen , IScreen newScreen )
2018-12-27 10:18:27 +00:00
{
2019-01-23 11:52:00 +00:00
ScreenChanged ( lastScreen , newScreen ) ;
Logger . Log ( $"Screen changed → {newScreen}" ) ;
2018-04-13 09:19:50 +00:00
}
2019-01-23 11:52:00 +00:00
private void screenExited ( IScreen lastScreen , IScreen newScreen )
2018-04-13 09:19:50 +00:00
{
2019-01-23 11:52:00 +00:00
ScreenChanged ( lastScreen , newScreen ) ;
Logger . Log ( $"Screen changed ← {newScreen}" ) ;
2018-04-13 09:19:50 +00:00
if ( newScreen = = null )
Exit ( ) ;
}
}
}