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
using System ;
using System.IO ;
2018-04-13 20:13:09 +08:00
using System.Threading ;
using System.Threading.Tasks ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Allocation ;
using osu.Framework.Audio ;
using osu.Framework.Audio.Sample ;
2019-02-21 18:04:31 +08:00
using osu.Framework.Bindables ;
2020-02-07 04:02:03 +08:00
using osu.Framework.Graphics ;
2018-04-13 17:19:50 +08:00
using osu.Framework.Input ;
using osu.Framework.Input.Bindings ;
using osu.Framework.Platform ;
2018-04-13 20:13:09 +08:00
using osu.Framework.Threading ;
2018-04-13 17:19:50 +08:00
using osu.Game.Configuration ;
using osu.Game.Input.Bindings ;
using osu.Game.Overlays ;
using osu.Game.Overlays.Notifications ;
2018-08-17 13:30:44 +08:00
using SixLabors.ImageSharp ;
2018-04-13 17:19:50 +08:00
namespace osu.Game.Graphics
{
2020-02-07 04:22:30 +08:00
public class ScreenshotManager : Component , IKeyBindingHandler < GlobalAction > , IHandleGlobalKeyboardInput
2018-04-13 17:19:50 +08:00
{
2018-04-13 20:13:09 +08:00
private readonly BindableBool cursorVisibility = new BindableBool ( true ) ;
/// <summary>
2018-04-13 20:15:08 +08:00
/// Changed when screenshots are being or have finished being taken, to control whether cursors should be visible.
2018-04-13 20:13:09 +08:00
/// If cursors should not be visible, cursors have 3 frames to hide themselves.
/// </summary>
public IBindable < bool > CursorVisibility = > cursorVisibility ;
2018-04-13 17:19:50 +08:00
private Bindable < ScreenshotFormat > screenshotFormat ;
2018-04-13 20:13:09 +08:00
private Bindable < bool > captureMenuCursor ;
2020-02-14 21:14:00 +08:00
[Resolved]
private GameHost host { get ; set ; }
2018-04-13 17:19:50 +08:00
private Storage storage ;
2020-02-14 21:14:00 +08:00
[Resolved]
private NotificationOverlay notificationOverlay { get ; set ; }
2018-04-13 17:19:50 +08:00
private SampleChannel shutter ;
[BackgroundDependencyLoader]
2020-02-14 21:14:00 +08:00
private void load ( OsuConfigManager config , Storage storage , AudioManager audio )
2018-04-13 17:19:50 +08:00
{
this . storage = storage . GetStorageForDirectory ( @"screenshots" ) ;
screenshotFormat = config . GetBindable < ScreenshotFormat > ( OsuSetting . ScreenshotFormat ) ;
2018-04-13 20:13:09 +08:00
captureMenuCursor = config . GetBindable < bool > ( OsuSetting . ScreenshotCaptureMenuCursor ) ;
2018-04-13 17:19:50 +08:00
2019-05-28 16:06:01 +08:00
shutter = audio . Samples . Get ( "UI/shutter" ) ;
2018-04-13 17:19:50 +08:00
}
public bool OnPressed ( GlobalAction action )
{
switch ( action )
{
case GlobalAction . TakeScreenshot :
shutter . Play ( ) ;
TakeScreenshotAsync ( ) ;
return true ;
}
return false ;
}
2020-01-22 12:22:34 +08:00
public void OnReleased ( GlobalAction action )
{
}
2018-04-13 17:19:50 +08:00
2018-04-13 20:13:09 +08:00
private volatile int screenShotTasks ;
2018-08-29 19:57:48 +08:00
public Task TakeScreenshotAsync ( ) = > Task . Run ( async ( ) = >
2018-04-13 17:19:50 +08:00
{
2018-04-13 20:13:09 +08:00
Interlocked . Increment ( ref screenShotTasks ) ;
if ( ! captureMenuCursor . Value )
{
cursorVisibility . Value = false ;
// We need to wait for at most 3 draw nodes to be drawn, following which we can be assured at least one DrawNode has been generated/drawn with the set value
const int frames_to_wait = 3 ;
int framesWaited = 0 ;
2019-09-14 22:08:56 +08:00
using ( var framesWaitedEvent = new ManualResetEventSlim ( false ) )
{
ScheduledDelegate waitDelegate = host . DrawThread . Scheduler . AddDelayed ( ( ) = >
{
2020-01-27 11:16:00 +08:00
if ( framesWaited + + > = frames_to_wait )
2019-09-14 22:08:56 +08:00
// ReSharper disable once AccessToDisposedClosure
framesWaitedEvent . Set ( ) ;
} , 10 , true ) ;
framesWaitedEvent . Wait ( ) ;
waitDelegate . Cancel ( ) ;
}
2018-04-13 20:13:09 +08:00
}
2018-08-17 13:30:44 +08:00
using ( var image = await host . TakeScreenshotAsync ( ) )
2018-04-13 17:19:50 +08:00
{
2019-08-01 06:35:42 +08:00
if ( Interlocked . Decrement ( ref screenShotTasks ) = = 0 & & cursorVisibility . Value = = false )
cursorVisibility . Value = true ;
2018-04-13 20:13:09 +08:00
2018-04-13 17:19:50 +08:00
var fileName = getFileName ( ) ;
if ( fileName = = null ) return ;
var stream = storage . GetStream ( fileName , FileAccess . Write ) ;
switch ( screenshotFormat . Value )
{
case ScreenshotFormat . Png :
2018-08-17 13:30:44 +08:00
image . SaveAsPng ( stream ) ;
2018-04-13 17:19:50 +08:00
break ;
2019-04-01 11:44:46 +08:00
2018-04-13 17:19:50 +08:00
case ScreenshotFormat . Jpg :
2018-08-17 13:30:44 +08:00
image . SaveAsJpeg ( stream ) ;
2018-04-13 17:19:50 +08:00
break ;
2019-04-01 11:44:46 +08:00
2018-04-13 17:19:50 +08:00
default :
2019-11-28 22:21:21 +08:00
throw new InvalidOperationException ( $"Unknown enum member {nameof(ScreenshotFormat)} {screenshotFormat.Value}." ) ;
2018-04-13 17:19:50 +08:00
}
notificationOverlay . Post ( new SimpleNotification
{
Text = $"{fileName} saved!" ,
Activated = ( ) = >
{
storage . OpenInNativeExplorer ( ) ;
return true ;
}
} ) ;
}
2018-04-13 20:13:09 +08:00
} ) ;
2018-04-13 17:19:50 +08:00
private string getFileName ( )
{
var dt = DateTime . Now ;
2018-07-25 13:37:05 +08:00
var fileExt = screenshotFormat . ToString ( ) . ToLowerInvariant ( ) ;
2018-04-13 17:19:50 +08:00
var withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}" ;
if ( ! storage . Exists ( withoutIndex ) )
return withoutIndex ;
for ( ulong i = 1 ; i < ulong . MaxValue ; i + + )
{
var indexedName = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}-{i}.{fileExt}" ;
if ( ! storage . Exists ( indexedName ) )
return indexedName ;
}
return null ;
}
}
}