2019-01-24 16:43:03 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2018-04-13 17:19:50 +08:00
2018-12-26 21:16:35 +08:00
using System ;
2020-12-10 15:56:56 +08:00
using System.Diagnostics ;
2018-04-13 17:19:50 +08:00
using System.Threading.Tasks ;
2020-10-20 06:08:05 +08:00
using JetBrains.Annotations ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Allocation ;
2019-09-15 21:59:46 +08:00
using osu.Framework.Audio ;
2019-10-01 23:39:01 +08:00
using osu.Framework.Bindables ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.Graphics.Sprites ;
2020-10-20 06:22:30 +08:00
using osu.Framework.Graphics.Transforms ;
2019-03-20 18:35:40 +08:00
using osu.Framework.Input ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Screens ;
2018-05-09 22:31:52 +08:00
using osu.Framework.Threading ;
2019-10-01 23:39:01 +08:00
using osu.Game.Configuration ;
2018-04-13 17:19:50 +08:00
using osu.Game.Graphics ;
2019-03-22 18:01:32 +08:00
using osu.Game.Graphics.Containers ;
2019-07-05 14:32:07 +08:00
using osu.Game.Input ;
2019-09-15 21:59:46 +08:00
using osu.Game.Overlays ;
using osu.Game.Overlays.Notifications ;
2018-04-13 17:19:50 +08:00
using osu.Game.Screens.Menu ;
using osu.Game.Screens.Play.PlayerSettings ;
2019-04-13 19:18:44 +08:00
using osu.Game.Users ;
2021-04-09 07:34:35 +08:00
using osu.Game.Utils ;
2018-11-20 15:51:59 +08:00
using osuTK ;
using osuTK.Graphics ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Screens.Play
{
public class PlayerLoader : ScreenWithBeatmapBackground
{
2019-03-14 15:09:17 +08:00
protected const float BACKGROUND_BLUR = 15 ;
2019-03-13 15:47:03 +08:00
2020-02-14 17:22:57 +08:00
public override bool HideOverlaysOnEnter = > hideOverlays ;
2018-04-13 17:19:50 +08:00
2020-02-14 17:22:57 +08:00
public override bool DisallowExternalBeatmapRulesetChanges = > true ;
2019-01-24 18:55:42 +08:00
2020-02-14 17:22:57 +08:00
// Here because IsHovered will not update unless we do so.
public override bool HandlePositionalInput = > true ;
2018-04-13 17:19:50 +08:00
2020-02-14 17:22:57 +08:00
// We show the previous screen status
protected override UserActivity InitialActivity = > null ;
2018-04-13 17:19:50 +08:00
2020-02-14 17:22:57 +08:00
protected override bool PlayResumeSound = > false ;
2019-04-13 19:18:44 +08:00
2020-02-14 17:22:57 +08:00
protected BeatmapMetadataDisplay MetadataInfo ;
2019-02-01 14:42:15 +08:00
2020-02-14 17:22:57 +08:00
protected VisualSettings VisualSettings ;
2019-04-25 13:15:07 +08:00
2019-12-08 02:16:41 +08:00
protected Task LoadTask { get ; private set ; }
2019-12-06 12:47:34 +08:00
2019-12-08 02:16:41 +08:00
protected Task DisposalTask { get ; private set ; }
2018-04-13 17:19:50 +08:00
2020-02-14 17:22:57 +08:00
private bool backgroundBrightnessReduction ;
2020-10-25 19:33:35 +08:00
private readonly BindableDouble volumeAdjustment = new BindableDouble ( 1 ) ;
2020-02-14 17:22:57 +08:00
protected bool BackgroundBrightnessReduction
{
set
{
if ( value = = backgroundBrightnessReduction )
return ;
backgroundBrightnessReduction = value ;
2021-01-04 17:32:23 +08:00
ApplyToBackground ( b = > b . FadeColour ( OsuColour . Gray ( backgroundBrightnessReduction ? 0.8f : 1 ) , 200 ) ) ;
2020-02-14 17:22:57 +08:00
}
}
private bool readyForPush = >
2020-12-10 15:56:56 +08:00
! playerConsumed
2020-02-14 18:02:37 +08:00
// don't push unless the player is completely loaded
2020-12-10 15:56:56 +08:00
& & player ? . LoadState = = LoadState . Ready
2020-02-14 18:02:37 +08:00
// don't push if the user is hovering one of the panes, unless they are idle.
& & ( IsHovered | | idleTracker . IsIdle . Value )
// don't push if the user is dragging a slider or otherwise.
& & inputManager ? . DraggedDrawable = = null
// don't push if a focused overlay is visible, like settings.
& & inputManager ? . FocusedDrawable = = null ;
2020-02-14 17:22:57 +08:00
private readonly Func < Player > createPlayer ;
private Player player ;
2020-12-10 15:56:56 +08:00
/// <summary>
/// Whether the curent player instance has been consumed via <see cref="consumePlayer"/>.
/// </summary>
private bool playerConsumed ;
2020-02-14 17:22:57 +08:00
private LogoTrackingContainer content ;
private bool hideOverlays ;
2019-03-20 18:35:40 +08:00
private InputManager inputManager ;
2020-02-14 17:22:57 +08:00
2019-09-15 22:32:23 +08:00
private IdleTracker idleTracker ;
2020-02-14 17:22:57 +08:00
private ScheduledDelegate scheduledPushPlayer ;
2020-10-20 06:08:05 +08:00
[CanBeNull]
private EpilepsyWarning epilepsyWarning ;
2019-10-03 18:16:31 +08:00
[Resolved(CanBeNull = true)]
private NotificationOverlay notificationOverlay { get ; set ; }
[Resolved(CanBeNull = true)]
private VolumeOverlay volumeOverlay { get ; set ; }
[Resolved]
private AudioManager audioManager { get ; set ; }
2021-04-10 05:55:41 +08:00
[Resolved(CanBeNull = true)]
2021-04-12 22:52:12 +08:00
private BatteryInfo batteryInfo { get ; set ; }
2021-04-09 07:34:35 +08:00
2018-12-26 21:16:35 +08:00
public PlayerLoader ( Func < Player > createPlayer )
2018-04-13 17:19:50 +08:00
{
2018-12-26 21:16:35 +08:00
this . createPlayer = createPlayer ;
}
2018-04-13 17:19:50 +08:00
[BackgroundDependencyLoader]
2019-10-01 23:39:01 +08:00
private void load ( SessionStatics sessionStatics )
2018-04-13 17:19:50 +08:00
{
2019-10-01 23:39:01 +08:00
muteWarningShownOnce = sessionStatics . GetBindable < bool > ( Static . MutedAudioNotificationShownOnce ) ;
2021-04-10 05:55:41 +08:00
batteryWarningShownOnce = sessionStatics . GetBindable < bool > ( Static . LowBatteryNotificationShownOnce ) ;
2019-10-01 23:39:01 +08:00
2019-04-05 11:02:47 +08:00
InternalChild = ( content = new LogoTrackingContainer
2018-04-13 17:19:50 +08:00
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
2019-01-24 18:55:42 +08:00
RelativeSizeAxes = Axes . Both ,
2019-04-05 11:02:47 +08:00
} ) . WithChildren ( new Drawable [ ]
2019-03-27 17:11:12 +08:00
{
2020-01-09 03:10:43 +08:00
MetadataInfo = new BeatmapMetadataDisplay ( Beatmap . Value , Mods , content . LogoFacade )
2018-05-09 22:31:52 +08:00
{
2019-03-27 17:11:12 +08:00
Alpha = 0 ,
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
} ,
new FillFlowContainer < PlayerSettingsGroup >
{
Anchor = Anchor . TopRight ,
Origin = Anchor . TopRight ,
AutoSizeAxes = Axes . Both ,
Direction = FillDirection . Vertical ,
Spacing = new Vector2 ( 0 , 20 ) ,
Margin = new MarginPadding ( 25 ) ,
Children = new PlayerSettingsGroup [ ]
2019-01-24 18:55:42 +08:00
{
2019-03-27 17:11:12 +08:00
VisualSettings = new VisualSettings ( ) ,
new InputSettings ( )
2019-01-24 18:55:42 +08:00
}
2019-07-05 14:32:07 +08:00
} ,
idleTracker = new IdleTracker ( 750 )
2019-04-05 11:02:47 +08:00
} ) ;
2020-10-20 06:08:05 +08:00
if ( Beatmap . Value . BeatmapInfo . EpilepsyWarning )
{
AddInternal ( epilepsyWarning = new EpilepsyWarning
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
} ) ;
}
2018-04-13 17:19:50 +08:00
}
2019-10-03 18:16:31 +08:00
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
inputManager = GetContainingInputManager ( ) ;
2019-12-27 18:36:48 +08:00
}
2020-02-14 17:28:58 +08:00
#region Screen handling
2019-12-27 18:36:48 +08:00
public override void OnEntering ( IScreen last )
{
base . OnEntering ( last ) ;
2021-01-04 17:32:23 +08:00
ApplyToBackground ( b = >
{
if ( epilepsyWarning ! = null )
epilepsyWarning . DimmableBackground = b ;
} ) ;
2020-10-25 19:33:35 +08:00
Beatmap . Value . Track . AddAdjustment ( AdjustableProperty . Volume , volumeAdjustment ) ;
2020-10-20 14:03:12 +08:00
2019-12-27 18:36:48 +08:00
content . ScaleTo ( 0.7f ) ;
contentIn ( ) ;
2020-01-09 02:55:35 +08:00
MetadataInfo . Delay ( 750 ) . FadeIn ( 500 ) ;
2020-12-10 15:33:28 +08:00
// after an initial delay, start the debounced load check.
// this will continue to execute even after resuming back on restart.
2020-12-14 13:21:21 +08:00
Scheduler . Add ( new ScheduledDelegate ( pushWhenLoaded , Clock . CurrentTime + 1800 , 0 ) ) ;
2019-10-03 18:16:31 +08:00
2020-02-14 17:28:58 +08:00
showMuteWarningIfNeeded ( ) ;
2021-04-08 02:41:21 +08:00
showBatteryWarningIfNeeded ( ) ;
2019-10-03 18:16:31 +08:00
}
2018-08-02 18:47:50 +08:00
2019-01-23 19:52:00 +08:00
public override void OnResuming ( IScreen last )
2018-04-13 17:19:50 +08:00
{
base . OnResuming ( last ) ;
2020-12-10 15:56:56 +08:00
// prepare for a retry.
player = null ;
playerConsumed = false ;
2020-12-10 15:34:58 +08:00
cancelLoad ( ) ;
2020-12-10 15:56:56 +08:00
2018-04-13 17:19:50 +08:00
contentIn ( ) ;
}
2020-02-14 17:22:57 +08:00
public override void OnSuspending ( IScreen next )
2018-12-26 21:16:35 +08:00
{
2020-02-14 17:22:57 +08:00
base . OnSuspending ( next ) ;
2018-12-26 21:16:35 +08:00
2020-02-14 17:22:57 +08:00
BackgroundBrightnessReduction = false ;
2020-10-25 19:33:35 +08:00
// we're moving to player, so a period of silence is upcoming.
// stop the track before removing adjustment to avoid a volume spike.
Beatmap . Value . Track . Stop ( ) ;
Beatmap . Value . Track . RemoveAdjustment ( AdjustableProperty . Volume , volumeAdjustment ) ;
2018-12-26 21:16:35 +08:00
}
2020-02-14 17:22:57 +08:00
public override bool OnExiting ( IScreen next )
2018-04-13 17:19:50 +08:00
{
2020-02-14 17:22:57 +08:00
cancelLoad ( ) ;
2018-04-13 17:19:50 +08:00
2020-02-14 17:22:57 +08:00
content . ScaleTo ( 0.7f , 150 , Easing . InQuint ) ;
this . FadeOut ( 150 ) ;
2019-03-28 15:29:35 +08:00
2021-04-13 14:24:35 +08:00
ApplyToBackground ( b = > b . IgnoreUserSettings . Value = true ) ;
2021-01-04 17:32:23 +08:00
2020-02-14 17:22:57 +08:00
BackgroundBrightnessReduction = false ;
2020-10-25 19:33:35 +08:00
Beatmap . Value . Track . RemoveAdjustment ( AdjustableProperty . Volume , volumeAdjustment ) ;
2020-02-14 17:22:57 +08:00
return base . OnExiting ( next ) ;
2018-04-13 17:19:50 +08:00
}
protected override void LogoArriving ( OsuLogo logo , bool resuming )
{
base . LogoArriving ( logo , resuming ) ;
2019-03-22 19:01:58 +08:00
const double duration = 300 ;
2018-04-13 17:19:50 +08:00
2020-02-14 17:22:57 +08:00
if ( ! resuming ) logo . MoveTo ( new Vector2 ( 0.5f ) , duration , Easing . In ) ;
2019-03-28 15:09:42 +08:00
2019-03-22 19:01:58 +08:00
logo . ScaleTo ( new Vector2 ( 0.15f ) , duration , Easing . In ) ;
2018-04-13 17:19:50 +08:00
logo . FadeIn ( 350 ) ;
2019-04-24 15:20:51 +08:00
Scheduler . AddDelayed ( ( ) = >
{
if ( this . IsCurrentScreen ( ) )
content . StartTracking ( logo , resuming ? 0 : 500 , Easing . InOutExpo ) ;
} , resuming ? 0 : 500 ) ;
2019-03-26 16:18:35 +08:00
}
protected override void LogoExiting ( OsuLogo logo )
{
base . LogoExiting ( logo ) ;
2019-04-08 14:24:09 +08:00
content . StopTracking ( ) ;
2018-04-13 17:19:50 +08:00
}
2020-02-14 17:28:58 +08:00
#endregion
2020-02-14 17:22:57 +08:00
protected override void Update ( )
{
base . Update ( ) ;
2018-04-13 17:19:50 +08:00
2020-02-14 17:22:57 +08:00
if ( ! this . IsCurrentScreen ( ) )
return ;
// We need to perform this check here rather than in OnHover as any number of children of VisualSettings
// may also be handling the hover events.
if ( inputManager . HoveredDrawables . Contains ( VisualSettings ) )
{
// Preview user-defined background dim and blur when hovered on the visual settings panel.
2021-01-04 17:32:23 +08:00
ApplyToBackground ( b = >
{
2021-04-13 14:24:35 +08:00
b . IgnoreUserSettings . Value = false ;
2021-01-04 17:32:23 +08:00
b . BlurAmount . Value = 0 ;
} ) ;
2020-02-14 17:22:57 +08:00
BackgroundBrightnessReduction = false ;
}
else
{
2021-01-04 17:32:23 +08:00
ApplyToBackground ( b = >
{
// Returns background dim and blur to the values specified by PlayerLoader.
2021-04-13 14:24:35 +08:00
b . IgnoreUserSettings . Value = true ;
2021-01-04 17:32:23 +08:00
b . BlurAmount . Value = BACKGROUND_BLUR ;
} ) ;
2020-02-14 17:22:57 +08:00
BackgroundBrightnessReduction = true ;
}
}
2020-12-10 15:32:14 +08:00
private Player consumePlayer ( )
{
2020-12-10 15:56:56 +08:00
Debug . Assert ( ! playerConsumed ) ;
playerConsumed = true ;
return player ;
2020-12-10 15:32:14 +08:00
}
2020-02-14 17:22:57 +08:00
private void prepareNewPlayer ( )
{
2020-08-13 11:04:32 +08:00
if ( ! this . IsCurrentScreen ( ) )
return ;
2020-02-14 17:22:57 +08:00
player = createPlayer ( ) ;
2021-03-31 13:09:38 +08:00
player . RestartCount = restartCount + + ;
2020-02-14 17:22:57 +08:00
player . RestartRequested = restartRequested ;
2019-03-20 18:59:54 +08:00
2020-02-14 17:22:57 +08:00
LoadTask = LoadComponentAsync ( player , _ = > MetadataInfo . Loading = false ) ;
}
private void restartRequested ( )
{
hideOverlays = true ;
ValidForResume = true ;
}
private void contentIn ( )
{
2020-08-13 11:04:32 +08:00
MetadataInfo . Loading = true ;
2020-02-14 17:22:57 +08:00
content . FadeInFromZero ( 400 ) ;
2020-08-13 11:04:32 +08:00
content . ScaleTo ( 1 , 650 , Easing . OutQuint ) . Then ( ) . Schedule ( prepareNewPlayer ) ;
2021-06-09 16:17:39 +08:00
ApplyToBackground ( b = > b ? . FadeColour ( Color4 . White , 800 , Easing . OutQuint ) ) ;
2020-02-14 17:22:57 +08:00
}
private void contentOut ( )
{
// Ensure the logo is no longer tracking before we scale the content
content . StopTracking ( ) ;
content . ScaleTo ( 0.7f , 300 , Easing . InQuint ) ;
content . FadeOut ( 250 ) ;
}
2018-04-13 17:19:50 +08:00
private void pushWhenLoaded ( )
{
2019-01-23 19:52:00 +08:00
if ( ! this . IsCurrentScreen ( ) ) return ;
2018-04-13 17:19:50 +08:00
2020-12-10 15:33:28 +08:00
if ( ! readyForPush )
2018-04-13 17:19:50 +08:00
{
2020-12-10 15:33:28 +08:00
// as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce
// if we become unready for push during the delay.
cancelLoad ( ) ;
return ;
}
2018-04-13 17:19:50 +08:00
2020-12-10 15:33:28 +08:00
// if a push has already been scheduled, no further action is required.
// this value is reset via cancelLoad() to allow a second usage of the same PlayerLoader screen.
if ( scheduledPushPlayer ! = null )
return ;
2018-04-13 17:19:50 +08:00
2020-12-10 15:33:28 +08:00
scheduledPushPlayer = Scheduler . AddDelayed ( ( ) = >
{
2020-12-10 15:32:14 +08:00
// ensure that once we have reached this "point of no return", readyForPush will be false for all future checks (until a new player instance is prepared).
var consumedPlayer = consumePlayer ( ) ;
2018-04-13 17:19:50 +08:00
2020-12-10 15:33:28 +08:00
contentOut ( ) ;
2020-10-20 06:22:30 +08:00
2020-12-10 15:33:28 +08:00
TransformSequence < PlayerLoader > pushSequence = this . Delay ( 250 ) ;
2020-10-20 05:32:44 +08:00
2020-12-10 15:33:28 +08:00
// only show if the warning was created (i.e. the beatmap needs it)
// and this is not a restart of the map (the warning expires after first load).
if ( epilepsyWarning ? . IsAlive = = true )
{
const double epilepsy_display_length = 3000 ;
pushSequence
. Schedule ( ( ) = > epilepsyWarning . State . Value = Visibility . Visible )
. TransformBindableTo ( volumeAdjustment , 0.25 , EpilepsyWarning . FADE_DURATION , Easing . OutQuint )
. Delay ( epilepsy_display_length )
. Schedule ( ( ) = >
{
epilepsyWarning . Hide ( ) ;
epilepsyWarning . Expire ( ) ;
} )
. Delay ( EpilepsyWarning . FADE_DURATION ) ;
}
2018-04-13 17:19:50 +08:00
2020-12-10 15:33:28 +08:00
pushSequence . Schedule ( ( ) = >
{
if ( ! this . IsCurrentScreen ( ) ) return ;
2018-04-13 17:19:50 +08:00
2020-12-10 15:33:28 +08:00
LoadTask = null ;
// By default, we want to load the player and never be returned to.
// Note that this may change if the player we load requested a re-run.
ValidForResume = false ;
2018-04-20 15:52:15 +08:00
2020-12-10 15:32:14 +08:00
if ( consumedPlayer . LoadedBeatmapSuccessfully )
this . Push ( consumedPlayer ) ;
2020-12-10 15:33:28 +08:00
else
this . Exit ( ) ;
} ) ;
} , 500 ) ;
2018-04-13 17:19:50 +08:00
}
private void cancelLoad ( )
{
2020-02-14 17:22:57 +08:00
scheduledPushPlayer ? . Cancel ( ) ;
scheduledPushPlayer = null ;
2018-04-13 17:19:50 +08:00
}
2020-02-14 17:22:57 +08:00
#region Disposal
2018-04-13 17:19:50 +08:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
2018-05-30 14:47:31 +08:00
if ( isDisposing )
{
// if the player never got pushed, we should explicitly dispose it.
2020-12-10 15:56:56 +08:00
DisposalTask = LoadTask ? . ContinueWith ( _ = > player ? . Dispose ( ) ) ;
2018-05-30 14:47:31 +08:00
}
2018-04-13 17:19:50 +08:00
}
2020-02-14 17:22:57 +08:00
#endregion
2019-03-20 15:50:47 +08:00
2020-02-14 17:28:58 +08:00
#region Mute warning
private Bindable < bool > muteWarningShownOnce ;
2021-03-31 12:57:57 +08:00
private int restartCount ;
2020-02-14 17:28:58 +08:00
private void showMuteWarningIfNeeded ( )
{
if ( ! muteWarningShownOnce . Value )
{
2020-05-05 09:31:11 +08:00
// Checks if the notification has not been shown yet and also if master volume is muted, track/music volume is muted or if the whole game is muted.
2020-02-14 17:28:58 +08:00
if ( volumeOverlay ? . IsMuted . Value = = true | | audioManager . Volume . Value < = audioManager . Volume . MinValue | | audioManager . VolumeTrack . Value < = audioManager . VolumeTrack . MinValue )
{
notificationOverlay ? . Post ( new MutedNotification ( ) ) ;
muteWarningShownOnce . Value = true ;
}
}
}
2019-09-15 21:59:46 +08:00
private class MutedNotification : SimpleNotification
{
2020-02-14 17:22:57 +08:00
public override bool IsImportant = > true ;
2019-09-15 23:47:44 +08:00
public MutedNotification ( )
{
Text = "Your music volume is set to 0%! Click here to restore it." ;
}
2019-09-15 21:59:46 +08:00
[BackgroundDependencyLoader]
2019-09-15 22:32:23 +08:00
private void load ( OsuColour colours , AudioManager audioManager , NotificationOverlay notificationOverlay , VolumeOverlay volumeOverlay )
2019-09-15 21:59:46 +08:00
{
Icon = FontAwesome . Solid . VolumeMute ;
IconBackgound . Colour = colours . RedDark ;
Activated = delegate
{
notificationOverlay . Hide ( ) ;
2019-09-15 22:50:01 +08:00
volumeOverlay . IsMuted . Value = false ;
2019-09-15 21:59:46 +08:00
audioManager . Volume . SetDefault ( ) ;
audioManager . VolumeTrack . SetDefault ( ) ;
return true ;
} ;
}
}
2020-02-14 17:28:58 +08:00
#endregion
2021-04-08 02:41:21 +08:00
#region Low battery warning
2021-04-09 08:28:23 +08:00
2021-04-08 02:41:21 +08:00
private Bindable < bool > batteryWarningShownOnce ;
private void showBatteryWarningIfNeeded ( )
{
2021-04-12 22:52:12 +08:00
if ( batteryInfo = = null ) return ;
2021-04-10 05:55:41 +08:00
2021-04-08 02:41:21 +08:00
if ( ! batteryWarningShownOnce . Value )
{
2021-04-12 23:11:22 +08:00
if ( ! batteryInfo . IsCharging & & batteryInfo . ChargeLevel < = 0.25 )
2021-04-08 02:41:21 +08:00
{
notificationOverlay ? . Post ( new BatteryWarningNotification ( ) ) ;
batteryWarningShownOnce . Value = true ;
}
}
}
private class BatteryWarningNotification : SimpleNotification
{
public override bool IsImportant = > true ;
public BatteryWarningNotification ( )
{
2021-04-10 05:55:41 +08:00
Text = "Your battery level is low! Charge your device to prevent interruptions during gameplay." ;
2021-04-08 02:41:21 +08:00
}
[BackgroundDependencyLoader]
private void load ( OsuColour colours , NotificationOverlay notificationOverlay )
{
Icon = FontAwesome . Solid . BatteryQuarter ;
IconBackgound . Colour = colours . RedDark ;
Activated = delegate
{
notificationOverlay . Hide ( ) ;
return true ;
} ;
}
}
#endregion
2018-04-13 17:19:50 +08:00
}
}