2022-05-16 18:21:26 +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.
#nullable enable
2022-05-17 15:33:02 +08:00
using System ;
2022-05-16 19:53:04 +08:00
using System.Collections.Generic ;
2022-05-16 20:07:42 +08:00
using System.IO ;
2022-05-16 19:37:38 +08:00
using System.Linq ;
using System.Threading ;
2022-05-17 15:33:02 +08:00
using System.Threading.Tasks ;
2022-05-16 18:21:26 +08:00
using osu.Framework.Allocation ;
2022-05-17 15:33:02 +08:00
using osu.Framework.Bindables ;
2022-05-16 19:13:34 +08:00
using osu.Framework.Extensions ;
2022-05-16 18:21:26 +08:00
using osu.Framework.Graphics ;
2022-05-16 20:07:42 +08:00
using osu.Framework.Graphics.Containers ;
2022-05-17 15:33:02 +08:00
using osu.Framework.Graphics.UserInterface ;
2022-05-16 18:21:26 +08:00
using osu.Framework.Localisation ;
using osu.Game.Database ;
using osu.Game.Graphics ;
using osu.Game.Graphics.Containers ;
2022-05-16 19:37:38 +08:00
using osu.Game.Graphics.UserInterfaceV2 ;
2022-05-16 18:21:26 +08:00
using osu.Game.Localisation ;
using osu.Game.Overlays.Settings ;
2022-05-17 15:33:02 +08:00
using osu.Game.Screens.Edit.Setup ;
2022-05-16 18:21:26 +08:00
using osuTK ;
namespace osu.Game.Overlays.FirstRunSetup
{
[LocalisableDescription(typeof(FirstRunSetupOverlayStrings), nameof(FirstRunSetupOverlayStrings.ImportTitle))]
public class ScreenImportFromStable : FirstRunSetupScreen
{
2022-05-16 20:33:15 +08:00
private static readonly Vector2 button_size = new Vector2 ( 400 , 50 ) ;
2022-05-16 18:21:26 +08:00
private ProgressRoundedButton importButton = null ! ;
2022-05-17 16:26:44 +08:00
private OsuTextFlowContainer progressText = null ! ;
2022-05-16 19:13:34 +08:00
[Resolved]
private LegacyImportManager legacyImportManager { get ; set ; } = null ! ;
2022-05-16 18:21:26 +08:00
2022-05-17 15:33:02 +08:00
private StableLocatorLabelledTextBox stableLocatorTextBox = null ! ;
2022-05-16 19:53:04 +08:00
private IEnumerable < ImportCheckbox > contentCheckboxes = > Content . Children . OfType < ImportCheckbox > ( ) ;
2022-05-16 18:21:26 +08:00
[BackgroundDependencyLoader(permitNulls: true)]
private void load ( )
{
Content . Children = new Drawable [ ]
{
new OsuTextFlowContainer ( cp = > cp . Font = OsuFont . Default . With ( size : CONTENT_FONT_SIZE ) )
{
Colour = OverlayColourProvider . Content1 ,
Text =
"If you have an installation of a previous osu! version, you can choose to migrate your existing content. Note that this will create a copy, and not affect your existing installation." ,
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y
} ,
2022-05-17 15:33:02 +08:00
stableLocatorTextBox = new StableLocatorLabelledTextBox
2022-05-16 19:37:38 +08:00
{
2022-05-17 15:33:02 +08:00
Label = "previous osu! install" ,
PlaceholderText = "Click to locate a previous osu! install"
2022-05-16 19:37:38 +08:00
} ,
2022-05-16 19:53:04 +08:00
new ImportCheckbox ( "Beatmaps" , StableContent . Beatmaps ) ,
new ImportCheckbox ( "Scores" , StableContent . Scores ) ,
new ImportCheckbox ( "Skins" , StableContent . Skins ) ,
new ImportCheckbox ( "Collections" , StableContent . Collections ) ,
2022-05-16 18:21:26 +08:00
importButton = new ProgressRoundedButton
{
2022-05-16 20:33:15 +08:00
Size = button_size ,
2022-05-16 18:21:26 +08:00
Anchor = Anchor . TopCentre ,
Origin = Anchor . TopCentre ,
2022-05-17 16:26:44 +08:00
Text = FirstRunSetupOverlayStrings . ImportContentFromPreviousVersion ,
2022-05-16 18:21:26 +08:00
Action = runImport
} ,
2022-05-17 16:26:44 +08:00
progressText = new OsuTextFlowContainer ( cp = > cp . Font = OsuFont . Default . With ( size : CONTENT_FONT_SIZE ) )
{
Colour = OverlayColourProvider . Content1 ,
Text =
"Your import will continue in the background. Check on its progress in the notifications sidebar!" ,
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
Alpha = 0 ,
} ,
2022-05-16 18:21:26 +08:00
} ;
2022-05-16 19:13:34 +08:00
2022-05-17 15:33:02 +08:00
stableLocatorTextBox . Current . BindValueChanged ( _ = > updateStablePath ( ) , true ) ;
2022-05-16 19:37:38 +08:00
}
private void updateStablePath ( )
{
var storage = legacyImportManager . GetCurrentStableStorage ( ) ;
if ( storage = = null )
{
2022-05-17 16:26:44 +08:00
allowInteraction ( false ) ;
2022-05-16 20:33:15 +08:00
2022-05-17 16:26:44 +08:00
stableLocatorTextBox . Current . Disabled = false ;
2022-05-17 15:33:02 +08:00
stableLocatorTextBox . Current . Value = string . Empty ;
2022-05-16 19:37:38 +08:00
return ;
}
2022-05-16 19:53:04 +08:00
foreach ( var c in contentCheckboxes )
{
2022-05-16 19:37:38 +08:00
c . Current . Disabled = false ;
2022-05-16 19:53:04 +08:00
c . UpdateCount ( ) ;
}
2022-05-16 19:37:38 +08:00
2022-05-17 16:26:44 +08:00
allowInteraction ( true ) ;
2022-05-17 15:33:02 +08:00
stableLocatorTextBox . Current . Value = storage . GetFullPath ( string . Empty ) ;
2022-05-16 20:33:15 +08:00
importButton . Enabled . Value = true ;
2022-05-16 19:53:04 +08:00
}
2022-05-16 19:37:38 +08:00
2022-05-16 19:53:04 +08:00
private void runImport ( )
{
2022-05-17 16:26:44 +08:00
allowInteraction ( false ) ;
progressText . FadeIn ( 1000 , Easing . OutQuint ) ;
2022-05-16 19:37:38 +08:00
2022-05-16 19:53:04 +08:00
StableContent importableContent = 0 ;
2022-05-16 19:37:38 +08:00
2022-05-16 19:53:04 +08:00
foreach ( var c in contentCheckboxes . Where ( c = > c . Current . Value ) )
importableContent | = c . StableContent ;
2022-05-16 19:37:38 +08:00
2022-05-16 19:53:04 +08:00
legacyImportManager . ImportFromStableAsync ( importableContent , false ) . ContinueWith ( t = > Schedule ( ( ) = >
2022-05-16 19:37:38 +08:00
{
2022-05-17 16:26:44 +08:00
progressText . FadeOut ( 500 , Easing . OutQuint ) ;
2022-05-16 19:53:04 +08:00
if ( t . IsCompletedSuccessfully )
importButton . Complete ( ) ;
else
{
2022-05-17 16:26:44 +08:00
allowInteraction ( true ) ;
2022-05-16 19:53:04 +08:00
importButton . Abort ( ) ;
}
2022-05-16 19:37:38 +08:00
} ) ) ;
2022-05-16 18:21:26 +08:00
}
2022-05-17 16:26:44 +08:00
private void allowInteraction ( bool allow )
{
importButton . Enabled . Value = allow ;
stableLocatorTextBox . Current . Disabled = ! allow ;
foreach ( var c in contentCheckboxes )
c . Current . Disabled = ! allow ;
}
2022-05-16 19:53:04 +08:00
private class ImportCheckbox : SettingsCheckbox
2022-05-16 18:21:26 +08:00
{
2022-05-16 19:53:04 +08:00
public readonly StableContent StableContent ;
2022-05-16 18:21:26 +08:00
2022-05-16 19:53:04 +08:00
private readonly LocalisableString title ;
[Resolved]
private LegacyImportManager legacyImportManager { get ; set ; } = null ! ;
private CancellationTokenSource ? countUpdateCancellation ;
public ImportCheckbox ( LocalisableString title , StableContent stableContent )
{
this . title = title ;
2022-05-16 18:21:26 +08:00
2022-05-16 19:53:04 +08:00
StableContent = stableContent ;
2022-05-17 16:12:10 +08:00
Current . Default = true ;
2022-05-16 19:53:04 +08:00
Current . Value = true ;
LabelText = title ;
}
public void UpdateCount ( )
{
LabelText = LocalisableString . Interpolate ( $"{title} (calculating...)" ) ;
countUpdateCancellation ? . Cancel ( ) ;
countUpdateCancellation = new CancellationTokenSource ( ) ;
legacyImportManager . GetImportCount ( StableContent , countUpdateCancellation . Token ) . ContinueWith ( task = > Schedule ( ( ) = >
{
if ( task . IsCanceled )
return ;
LabelText = LocalisableString . Interpolate ( $"{title} ({task.GetResultSafely()} items)" ) ;
} ) ) ;
}
2022-05-16 18:21:26 +08:00
}
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
internal class StableLocatorLabelledTextBox : LabelledTextBoxWithPopover , ICanAcceptFiles
2022-05-16 20:07:42 +08:00
{
2022-05-17 15:33:02 +08:00
[Resolved]
private LegacyImportManager legacyImportManager { get ; set ; } = null ! ;
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
// TODO: test
public IEnumerable < string > HandledExtensions { get ; } = new [ ] { string . Empty } ;
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
private readonly Bindable < DirectoryInfo > currentDirectory = new Bindable < DirectoryInfo > ( ) ;
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
[Resolved]
private OsuGameBase game { get ; set ; } = null ! ;
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
protected override void LoadComplete ( )
2022-05-16 20:07:42 +08:00
{
2022-05-17 15:33:02 +08:00
base . LoadComplete ( ) ;
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
game . RegisterImportHandler ( this ) ;
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
currentDirectory . BindValueChanged ( onDirectorySelected ) ;
string? fullPath = legacyImportManager . GetCurrentStableStorage ( ) ? . GetFullPath ( string . Empty ) ;
if ( fullPath ! = null )
currentDirectory . Value = new DirectoryInfo ( fullPath ) ;
}
private void onDirectorySelected ( ValueChangedEvent < DirectoryInfo > directory )
2022-05-16 20:07:42 +08:00
{
2022-05-17 15:33:02 +08:00
if ( directory . NewValue = = null | | directory . OldValue = = null )
{
Current . Value = string . Empty ;
return ;
}
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
// DirectorySelectors can trigger a noop value changed, but `DirectoryInfo` equality doesn't catch this.
if ( directory . OldValue . FullName = = directory . NewValue . FullName )
return ;
if ( directory . NewValue ? . GetFiles ( @"osu!.*.cfg" ) . Any ( ) ? ? false )
{
this . HidePopover ( ) ;
string path = directory . NewValue . FullName ;
legacyImportManager . UpdateStorage ( path ) ;
Current . Value = path ;
}
2022-05-16 20:07:42 +08:00
}
2022-05-17 15:33:02 +08:00
Task ICanAcceptFiles . Import ( params string [ ] paths )
2022-05-16 20:07:42 +08:00
{
2022-05-17 15:33:02 +08:00
Schedule ( ( ) = > currentDirectory . Value = new DirectoryInfo ( paths . First ( ) ) ) ;
return Task . CompletedTask ;
}
Task ICanAcceptFiles . Import ( params ImportTask [ ] tasks ) = > throw new NotImplementedException ( ) ;
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
game . UnregisterImportHandler ( this ) ;
}
public override Popover GetPopover ( ) = > new DirectoryChooserPopover ( currentDirectory ) ;
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
private class DirectoryChooserPopover : OsuPopover
{
public DirectoryChooserPopover ( Bindable < DirectoryInfo > currentDirectory )
{
Child = new Container
{
Size = new Vector2 ( 600 , 400 ) ,
Child = new OsuDirectorySelector ( currentDirectory . Value ? . FullName )
{
RelativeSizeAxes = Axes . Both ,
CurrentPath = { BindTarget = currentDirectory }
} ,
} ;
}
2022-05-16 20:07:42 +08:00
}
}
2022-05-16 18:21:26 +08:00
}
}