mirror of
https://github.com/ppy/osu.git
synced 2025-02-13 11:12:54 +08:00
Merge pull request #18313 from peppy/first-run-screen-import-from-stable
Add first run screen for importing from previous installation
This commit is contained in:
commit
5709dc566a
@ -41,7 +41,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
private readonly Bindable<Color4> accentColour = new Bindable<Color4>();
|
||||
private readonly IBindable<int> indexInCurrentCombo = new Bindable<int>();
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
[Resolved(canBeNull: true)] // Can't really be null but required to handle potential of disposal before DI completes.
|
||||
private DrawableHitObject? drawableObject { get; set; }
|
||||
|
||||
[Resolved]
|
||||
|
@ -0,0 +1,48 @@
|
||||
// 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.
|
||||
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Moq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.FirstRunSetup;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public class TestSceneFirstRunScreenImportFromStable : OsuManualInputManagerTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
private readonly Mock<LegacyImportManager> legacyImportManager = new Mock<LegacyImportManager>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
legacyImportManager.Setup(m => m.GetImportCount(It.IsAny<StableContent>(), It.IsAny<CancellationToken>())).Returns(() => Task.FromResult(RNG.Next(0, 256)));
|
||||
|
||||
Dependencies.CacheAs(legacyImportManager.Object);
|
||||
}
|
||||
|
||||
public TestSceneFirstRunScreenImportFromStable()
|
||||
{
|
||||
AddStep("load screen", () =>
|
||||
{
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new ScreenStack(new ScreenImportFromStable())
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -3,9 +3,11 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Online;
|
||||
@ -89,6 +91,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
private void queueDownloads(string[] sourceFilenames, int? limit = null)
|
||||
{
|
||||
Debug.Assert(LoadState == LoadState.NotLoaded);
|
||||
|
||||
try
|
||||
{
|
||||
// Matches osu-stable, in order to provide new users with roughly the same randomised selection of bundled beatmaps.
|
||||
|
@ -111,6 +111,18 @@ namespace osu.Game.Collections
|
||||
|
||||
public Action<Notification> PostNotification { protected get; set; }
|
||||
|
||||
public Task<int> GetAvailableCount(StableStorage stableStorage)
|
||||
{
|
||||
if (!stableStorage.Exists(database_name))
|
||||
return Task.FromResult(0);
|
||||
|
||||
return Task.Run(() =>
|
||||
{
|
||||
using (var stream = stableStorage.GetStream(database_name))
|
||||
return readCollections(stream).Count;
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is a temporary method and will likely be replaced by a full-fledged (and more correctly placed) migration process in the future.
|
||||
/// </summary>
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
@ -36,22 +37,66 @@ namespace osu.Game.Database
|
||||
[Resolved]
|
||||
private CollectionManager collections { get; set; }
|
||||
|
||||
[Resolved]
|
||||
[Resolved(canBeNull: true)]
|
||||
private OsuGame game { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IDialogOverlay dialogOverlay { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
[Resolved(canBeNull: true)]
|
||||
private DesktopGameHost desktopGameHost { get; set; }
|
||||
|
||||
private StableStorage cachedStorage;
|
||||
|
||||
public bool SupportsImportFromStable => RuntimeInfo.IsDesktop;
|
||||
|
||||
public async Task ImportFromStableAsync(StableContent content)
|
||||
public void UpdateStorage(string stablePath) => cachedStorage = new StableStorage(stablePath, desktopGameHost);
|
||||
|
||||
public virtual async Task<int> GetImportCount(StableContent content, CancellationToken cancellationToken)
|
||||
{
|
||||
var stableStorage = await getStableStorage().ConfigureAwait(false);
|
||||
var stableStorage = GetCurrentStableStorage();
|
||||
|
||||
if (stableStorage == null)
|
||||
return 0;
|
||||
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
switch (content)
|
||||
{
|
||||
case StableContent.Beatmaps:
|
||||
return await new LegacyBeatmapImporter(beatmaps).GetAvailableCount(stableStorage);
|
||||
|
||||
case StableContent.Skins:
|
||||
return await new LegacySkinImporter(skins).GetAvailableCount(stableStorage);
|
||||
|
||||
case StableContent.Collections:
|
||||
return await collections.GetAvailableCount(stableStorage);
|
||||
|
||||
case StableContent.Scores:
|
||||
return await new LegacyScoreImporter(scores).GetAvailableCount(stableStorage);
|
||||
|
||||
default:
|
||||
throw new ArgumentException($"Only one {nameof(StableContent)} flag should be specified.");
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ImportFromStableAsync(StableContent content, bool interactiveLocateIfNotFound = true)
|
||||
{
|
||||
var stableStorage = GetCurrentStableStorage();
|
||||
|
||||
if (stableStorage == null)
|
||||
{
|
||||
if (!interactiveLocateIfNotFound)
|
||||
return;
|
||||
|
||||
var taskCompletionSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
Schedule(() => dialogOverlay.Push(new StableDirectoryLocationDialog(taskCompletionSource)));
|
||||
string stablePath = await taskCompletionSource.Task.ConfigureAwait(false);
|
||||
|
||||
UpdateStorage(stablePath);
|
||||
stableStorage = GetCurrentStableStorage();
|
||||
}
|
||||
|
||||
var importTasks = new List<Task>();
|
||||
|
||||
Task beatmapImportTask = Task.CompletedTask;
|
||||
@ -70,20 +115,16 @@ namespace osu.Game.Database
|
||||
await Task.WhenAll(importTasks.ToArray()).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<StableStorage> getStableStorage()
|
||||
public StableStorage GetCurrentStableStorage()
|
||||
{
|
||||
if (cachedStorage != null)
|
||||
return cachedStorage;
|
||||
|
||||
var stableStorage = game.GetStorageForStableInstall();
|
||||
var stableStorage = game?.GetStorageForStableInstall();
|
||||
if (stableStorage != null)
|
||||
return cachedStorage = stableStorage;
|
||||
|
||||
var taskCompletionSource = new TaskCompletionSource<string>(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||
Schedule(() => dialogOverlay.Push(new StableDirectoryLocationDialog(taskCompletionSource)));
|
||||
string stablePath = await taskCompletionSource.Task.ConfigureAwait(false);
|
||||
|
||||
return cachedStorage = new StableStorage(stablePath, desktopGameHost);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,8 +24,14 @@ namespace osu.Game.Database
|
||||
/// <summary>
|
||||
/// Select paths to import from stable where all paths should be absolute. Default implementation iterates all directories in <see cref="ImportFromStablePath"/>.
|
||||
/// </summary>
|
||||
protected virtual IEnumerable<string> GetStableImportPaths(Storage storage) => storage.GetDirectories(ImportFromStablePath)
|
||||
.Select(path => storage.GetFullPath(path));
|
||||
protected virtual IEnumerable<string> GetStableImportPaths(Storage storage)
|
||||
{
|
||||
if (!storage.ExistsDirectory(ImportFromStablePath))
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
return storage.GetDirectories(ImportFromStablePath)
|
||||
.Select(path => storage.GetFullPath(path));
|
||||
}
|
||||
|
||||
protected readonly IModelImporter<TModel> Importer;
|
||||
|
||||
@ -34,6 +40,8 @@ namespace osu.Game.Database
|
||||
Importer = importer;
|
||||
}
|
||||
|
||||
public Task<int> GetAvailableCount(StableStorage stableStorage) => Task.Run(() => GetStableImportPaths(PrepareStableStorage(stableStorage)).Count());
|
||||
|
||||
public Task ImportFromStableAsync(StableStorage stableStorage)
|
||||
{
|
||||
var storage = PrepareStableStorage(stableStorage);
|
||||
|
@ -15,8 +15,14 @@ namespace osu.Game.Database
|
||||
protected override string ImportFromStablePath => Path.Combine("Data", "r");
|
||||
|
||||
protected override IEnumerable<string> GetStableImportPaths(Storage storage)
|
||||
=> storage.GetFiles(ImportFromStablePath).Where(p => Importer.HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false))
|
||||
.Select(path => storage.GetFullPath(path));
|
||||
{
|
||||
if (!storage.ExistsDirectory(ImportFromStablePath))
|
||||
return Enumerable.Empty<string>();
|
||||
|
||||
return storage.GetFiles(ImportFromStablePath)
|
||||
.Where(p => Importer.HandledExtensions.Any(ext => Path.GetExtension(p)?.Equals(ext, StringComparison.OrdinalIgnoreCase) ?? false))
|
||||
.Select(path => storage.GetFullPath(path));
|
||||
}
|
||||
|
||||
public LegacyScoreImporter(IModelImporter<ScoreInfo> importer)
|
||||
: base(importer)
|
||||
|
@ -6,6 +6,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
@ -25,7 +26,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
set => Component.ReadOnly = value;
|
||||
}
|
||||
|
||||
public string PlaceholderText
|
||||
public LocalisableString PlaceholderText
|
||||
{
|
||||
set => Component.PlaceholderText = value;
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
@ -44,8 +45,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
internal class Background : CompositeDrawable
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OverlayColourProvider overlayColourProvider, OsuColour colours)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
@ -54,7 +55,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
InternalChild = new Box
|
||||
{
|
||||
Colour = colours.GreySeaFoamDarker,
|
||||
Colour = overlayColourProvider?.Background5 ?? colours.GreySeaFoamDarker,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
}
|
||||
|
@ -69,6 +69,26 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString SelectAll => new TranslatableString(getKey(@"select_all"), @"Select All");
|
||||
|
||||
/// <summary>
|
||||
/// "Beatmaps"
|
||||
/// </summary>
|
||||
public static LocalisableString Beatmaps => new TranslatableString(getKey(@"beatmaps"), @"Beatmaps");
|
||||
|
||||
/// <summary>
|
||||
/// "Scores"
|
||||
/// </summary>
|
||||
public static LocalisableString Scores => new TranslatableString(getKey(@"scores"), @"Scores");
|
||||
|
||||
/// <summary>
|
||||
/// "Skins"
|
||||
/// </summary>
|
||||
public static LocalisableString Skins => new TranslatableString(getKey(@"skins"), @"Skins");
|
||||
|
||||
/// <summary>
|
||||
/// "Collections"
|
||||
/// </summary>
|
||||
public static LocalisableString Collections => new TranslatableString(getKey(@"collections"), @"Collections");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,56 @@
|
||||
// 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.
|
||||
|
||||
using osu.Framework.Localisation;
|
||||
|
||||
namespace osu.Game.Localisation
|
||||
{
|
||||
public static class FirstRunOverlayImportFromStableScreenStrings
|
||||
{
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.ScreenImportFromStable";
|
||||
|
||||
/// <summary>
|
||||
/// "Import"
|
||||
/// </summary>
|
||||
public static LocalisableString Header => new TranslatableString(getKey(@"header"), @"Import");
|
||||
|
||||
/// <summary>
|
||||
/// "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."
|
||||
/// </summary>
|
||||
public static LocalisableString Description => new TranslatableString(getKey(@"description"),
|
||||
@"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.");
|
||||
|
||||
/// <summary>
|
||||
/// "previous osu! install"
|
||||
/// </summary>
|
||||
public static LocalisableString LocateDirectoryLabel => new TranslatableString(getKey(@"locate_directory_label"), @"previous osu! install");
|
||||
|
||||
/// <summary>
|
||||
/// "Click to locate a previous osu! install"
|
||||
/// </summary>
|
||||
public static LocalisableString LocateDirectoryPlaceholder => new TranslatableString(getKey(@"locate_directory_placeholder"), @"Click to locate a previous osu! install");
|
||||
|
||||
/// <summary>
|
||||
/// "Import content from previous version"
|
||||
/// </summary>
|
||||
public static LocalisableString ImportButton => new TranslatableString(getKey(@"import_button"), @"Import content from previous version");
|
||||
|
||||
/// <summary>
|
||||
/// "Your import will continue in the background. Check on its progress in the notifications sidebar!"
|
||||
/// </summary>
|
||||
public static LocalisableString ImportInProgress =>
|
||||
new TranslatableString(getKey(@"import_in_progress"), @"Your import will continue in the background. Check on its progress in the notifications sidebar!");
|
||||
|
||||
/// <summary>
|
||||
/// "calculating..."
|
||||
/// </summary>
|
||||
public static LocalisableString Calculating => new TranslatableString(getKey(@"calculating"), @"calculating...");
|
||||
|
||||
/// <summary>
|
||||
/// "{0} items"
|
||||
/// </summary>
|
||||
public static LocalisableString Items(int arg0) => new TranslatableString(getKey(@"items"), @"{0} item(s)", arg0);
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
@ -21,6 +21,8 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
|
||||
protected const float CONTENT_FONT_SIZE = 16;
|
||||
|
||||
protected const float CONTENT_PADDING = 30;
|
||||
|
||||
protected const float HEADER_FONT_SIZE = 24;
|
||||
|
||||
[Resolved]
|
||||
@ -41,7 +43,7 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Horizontal = 30 },
|
||||
Padding = new MarginPadding { Horizontal = CONTENT_PADDING },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
|
@ -25,7 +25,6 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
public class ScreenBeatmaps : FirstRunSetupScreen
|
||||
{
|
||||
private ProgressRoundedButton downloadBundledButton = null!;
|
||||
private ProgressRoundedButton importBeatmapsButton = null!;
|
||||
private ProgressRoundedButton downloadTutorialButton = null!;
|
||||
|
||||
private OsuTextFlowContainer currentlyLoadedBeatmaps = null!;
|
||||
@ -41,8 +40,8 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
|
||||
private IDisposable? beatmapSubscription;
|
||||
|
||||
[BackgroundDependencyLoader(permitNulls: true)]
|
||||
private void load(LegacyImportManager? legacyImportManager)
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Vector2 buttonSize = new Vector2(400, 50);
|
||||
|
||||
@ -104,35 +103,6 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
Action = downloadBundled
|
||||
},
|
||||
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE))
|
||||
{
|
||||
Colour = OverlayColourProvider.Content1,
|
||||
Text = "If you have an existing osu! install, you can also choose to import your existing beatmap collection.",
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
},
|
||||
importBeatmapsButton = new ProgressRoundedButton
|
||||
{
|
||||
Size = buttonSize,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
BackgroundColour = colours.Blue3,
|
||||
Text = MaintenanceSettingsStrings.ImportBeatmapsFromStable,
|
||||
Action = () =>
|
||||
{
|
||||
importBeatmapsButton.Enabled.Value = false;
|
||||
legacyImportManager?.ImportFromStableAsync(StableContent.Beatmaps).ContinueWith(t => Schedule(() =>
|
||||
{
|
||||
if (t.IsCompletedSuccessfully)
|
||||
importBeatmapsButton.Complete();
|
||||
else
|
||||
{
|
||||
importBeatmapsButton.Enabled.Value = true;
|
||||
importBeatmapsButton.Abort();
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE))
|
||||
{
|
||||
Colour = OverlayColourProvider.Content1,
|
||||
Text = FirstRunSetupBeatmapScreenStrings.ObtainMoreBeatmaps,
|
||||
|
267
osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs
Normal file
267
osu.Game/Overlays/FirstRunSetup/ScreenImportFromStable.cs
Normal file
@ -0,0 +1,267 @@
|
||||
// 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
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Screens.Edit.Setup;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.FirstRunSetup
|
||||
{
|
||||
[LocalisableDescription(typeof(FirstRunOverlayImportFromStableScreenStrings), nameof(FirstRunOverlayImportFromStableScreenStrings.Header))]
|
||||
public class ScreenImportFromStable : FirstRunSetupScreen
|
||||
{
|
||||
private static readonly Vector2 button_size = new Vector2(400, 50);
|
||||
|
||||
private ProgressRoundedButton importButton = null!;
|
||||
|
||||
private OsuTextFlowContainer progressText = null!;
|
||||
|
||||
[Resolved]
|
||||
private LegacyImportManager legacyImportManager { get; set; } = null!;
|
||||
|
||||
private StableLocatorLabelledTextBox stableLocatorTextBox = null!;
|
||||
|
||||
private IEnumerable<ImportCheckbox> contentCheckboxes => Content.Children.OfType<ImportCheckbox>();
|
||||
|
||||
[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 = FirstRunOverlayImportFromStableScreenStrings.Description,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
},
|
||||
stableLocatorTextBox = new StableLocatorLabelledTextBox
|
||||
{
|
||||
Label = FirstRunOverlayImportFromStableScreenStrings.LocateDirectoryLabel,
|
||||
PlaceholderText = FirstRunOverlayImportFromStableScreenStrings.LocateDirectoryPlaceholder
|
||||
},
|
||||
new ImportCheckbox(CommonStrings.Beatmaps, StableContent.Beatmaps),
|
||||
new ImportCheckbox(CommonStrings.Scores, StableContent.Scores),
|
||||
new ImportCheckbox(CommonStrings.Skins, StableContent.Skins),
|
||||
new ImportCheckbox(CommonStrings.Collections, StableContent.Collections),
|
||||
importButton = new ProgressRoundedButton
|
||||
{
|
||||
Size = button_size,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = FirstRunOverlayImportFromStableScreenStrings.ImportButton,
|
||||
Action = runImport
|
||||
},
|
||||
progressText = new OsuTextFlowContainer(cp => cp.Font = OsuFont.Default.With(size: CONTENT_FONT_SIZE))
|
||||
{
|
||||
Colour = OverlayColourProvider.Content1,
|
||||
Text = FirstRunOverlayImportFromStableScreenStrings.ImportInProgress,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Alpha = 0,
|
||||
},
|
||||
};
|
||||
|
||||
stableLocatorTextBox.Current.BindValueChanged(_ => updateStablePath(), true);
|
||||
}
|
||||
|
||||
private void updateStablePath()
|
||||
{
|
||||
var storage = legacyImportManager.GetCurrentStableStorage();
|
||||
|
||||
if (storage == null)
|
||||
{
|
||||
toggleInteraction(false);
|
||||
|
||||
stableLocatorTextBox.Current.Disabled = false;
|
||||
stableLocatorTextBox.Current.Value = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var c in contentCheckboxes)
|
||||
{
|
||||
c.Current.Disabled = false;
|
||||
c.UpdateCount();
|
||||
}
|
||||
|
||||
toggleInteraction(true);
|
||||
stableLocatorTextBox.Current.Value = storage.GetFullPath(string.Empty);
|
||||
importButton.Enabled.Value = true;
|
||||
}
|
||||
|
||||
private void runImport()
|
||||
{
|
||||
toggleInteraction(false);
|
||||
progressText.FadeIn(1000, Easing.OutQuint);
|
||||
|
||||
StableContent importableContent = 0;
|
||||
|
||||
foreach (var c in contentCheckboxes.Where(c => c.Current.Value))
|
||||
importableContent |= c.StableContent;
|
||||
|
||||
legacyImportManager.ImportFromStableAsync(importableContent, false).ContinueWith(t => Schedule(() =>
|
||||
{
|
||||
progressText.FadeOut(500, Easing.OutQuint);
|
||||
|
||||
if (t.IsCompletedSuccessfully)
|
||||
importButton.Complete();
|
||||
else
|
||||
{
|
||||
toggleInteraction(true);
|
||||
importButton.Abort();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void toggleInteraction(bool allow)
|
||||
{
|
||||
importButton.Enabled.Value = allow;
|
||||
stableLocatorTextBox.Current.Disabled = !allow;
|
||||
foreach (var c in contentCheckboxes)
|
||||
c.Current.Disabled = !allow;
|
||||
}
|
||||
|
||||
private class ImportCheckbox : SettingsCheckbox
|
||||
{
|
||||
public readonly StableContent StableContent;
|
||||
|
||||
private readonly LocalisableString title;
|
||||
|
||||
[Resolved]
|
||||
private LegacyImportManager legacyImportManager { get; set; } = null!;
|
||||
|
||||
private CancellationTokenSource? countUpdateCancellation;
|
||||
|
||||
public ImportCheckbox(LocalisableString title, StableContent stableContent)
|
||||
{
|
||||
this.title = title;
|
||||
|
||||
StableContent = stableContent;
|
||||
|
||||
Current.Default = true;
|
||||
Current.Value = true;
|
||||
|
||||
LabelText = title;
|
||||
}
|
||||
|
||||
public void UpdateCount()
|
||||
{
|
||||
LabelText = LocalisableString.Interpolate($"{title} ({FirstRunOverlayImportFromStableScreenStrings.Calculating})");
|
||||
|
||||
countUpdateCancellation?.Cancel();
|
||||
countUpdateCancellation = new CancellationTokenSource();
|
||||
|
||||
legacyImportManager.GetImportCount(StableContent, countUpdateCancellation.Token).ContinueWith(task => Schedule(() =>
|
||||
{
|
||||
if (task.IsCanceled)
|
||||
return;
|
||||
|
||||
int count = task.GetResultSafely();
|
||||
|
||||
LabelText = LocalisableString.Interpolate($"{title} ({FirstRunOverlayImportFromStableScreenStrings.Items(count)})");
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
internal class StableLocatorLabelledTextBox : LabelledTextBoxWithPopover, ICanAcceptFiles
|
||||
{
|
||||
[Resolved]
|
||||
private LegacyImportManager legacyImportManager { get; set; } = null!;
|
||||
|
||||
public IEnumerable<string> HandledExtensions { get; } = new[] { string.Empty };
|
||||
|
||||
private readonly Bindable<DirectoryInfo> currentDirectory = new Bindable<DirectoryInfo>();
|
||||
|
||||
[Resolved(canBeNull: true)] // Can't really be null but required to handle potential of disposal before DI completes.
|
||||
private OsuGameBase? game { get; set; }
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
game?.RegisterImportHandler(this);
|
||||
|
||||
currentDirectory.BindValueChanged(onDirectorySelected);
|
||||
|
||||
string? fullPath = legacyImportManager.GetCurrentStableStorage()?.GetFullPath(string.Empty);
|
||||
if (fullPath != null)
|
||||
currentDirectory.Value = new DirectoryInfo(fullPath);
|
||||
}
|
||||
|
||||
private void onDirectorySelected(ValueChangedEvent<DirectoryInfo> directory)
|
||||
{
|
||||
if (directory.NewValue == null)
|
||||
{
|
||||
Current.Value = string.Empty;
|
||||
return;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
||||
Task ICanAcceptFiles.Import(params string[] paths)
|
||||
{
|
||||
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);
|
||||
|
||||
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 }
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,12 +4,14 @@
|
||||
#nullable enable
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -17,6 +19,7 @@ using osu.Framework.Localisation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
@ -55,13 +58,7 @@ namespace osu.Game.Overlays
|
||||
/// </summary>
|
||||
public FirstRunSetupScreen? CurrentScreen => (FirstRunSetupScreen?)stack?.CurrentScreen;
|
||||
|
||||
private readonly Type[] steps =
|
||||
{
|
||||
typeof(ScreenWelcome),
|
||||
typeof(ScreenBeatmaps),
|
||||
typeof(ScreenUIScale),
|
||||
typeof(ScreenBehaviour),
|
||||
};
|
||||
private readonly List<Type> steps = new List<Type>();
|
||||
|
||||
private Container screenContent = null!;
|
||||
|
||||
@ -77,15 +74,22 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
[BackgroundDependencyLoader(permitNulls: true)]
|
||||
private void load(OsuColour colours, LegacyImportManager? legacyImportManager)
|
||||
{
|
||||
steps.Add(typeof(ScreenWelcome));
|
||||
steps.Add(typeof(ScreenBeatmaps));
|
||||
if (legacyImportManager?.SupportsImportFromStable == true)
|
||||
steps.Add(typeof(ScreenImportFromStable));
|
||||
steps.Add(typeof(ScreenUIScale));
|
||||
steps.Add(typeof(ScreenBehaviour));
|
||||
|
||||
Header.Title = FirstRunSetupOverlayStrings.FirstRunSetupTitle;
|
||||
Header.Description = FirstRunSetupOverlayStrings.FirstRunSetupDescription;
|
||||
|
||||
MainAreaContent.AddRange(new Drawable[]
|
||||
{
|
||||
content = new Container
|
||||
content = new PopoverContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@ -313,7 +317,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
currentStepIndex++;
|
||||
|
||||
if (currentStepIndex < steps.Length)
|
||||
if (currentStepIndex < steps.Count)
|
||||
{
|
||||
var nextScreen = (Screen)Activator.CreateInstance(steps[currentStepIndex.Value]);
|
||||
|
||||
@ -345,7 +349,7 @@ namespace osu.Game.Overlays
|
||||
return;
|
||||
|
||||
bool isFirstStep = currentStepIndex == 0;
|
||||
bool isLastStep = currentStepIndex == steps.Length - 1;
|
||||
bool isLastStep = currentStepIndex == steps.Count - 1;
|
||||
|
||||
if (isFirstStep)
|
||||
{
|
||||
|
@ -154,7 +154,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
private void updateBankPlaceholderText(IEnumerable<HitObject> objects)
|
||||
{
|
||||
string? commonBank = getCommonBank(objects.Select(h => h.SampleControlPoint).ToArray());
|
||||
bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : null;
|
||||
bank.PlaceholderText = string.IsNullOrEmpty(commonBank) ? "(multiple)" : string.Empty;
|
||||
}
|
||||
|
||||
private void updateVolumeFor(IEnumerable<HitObject> objects, int? newVolume)
|
||||
|
@ -11,11 +11,8 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osuTK;
|
||||
|
||||
@ -24,7 +21,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
/// <summary>
|
||||
/// A labelled textbox which reveals an inline file chooser when clicked.
|
||||
/// </summary>
|
||||
internal class FileChooserLabelledTextBox : LabelledTextBox, ICanAcceptFiles, IHasPopover
|
||||
internal class FileChooserLabelledTextBox : LabelledTextBoxWithPopover, ICanAcceptFiles
|
||||
{
|
||||
private readonly string[] handledExtensions;
|
||||
|
||||
@ -40,16 +37,6 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
this.handledExtensions = handledExtensions;
|
||||
}
|
||||
|
||||
protected override OsuTextBox CreateTextBox() =>
|
||||
new FileChooserOsuTextBox
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
OnFocused = this.ShowPopover
|
||||
};
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -81,27 +68,7 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
game.UnregisterImportHandler(this);
|
||||
}
|
||||
|
||||
internal class FileChooserOsuTextBox : OsuTextBox
|
||||
{
|
||||
public Action OnFocused;
|
||||
|
||||
protected override bool OnDragStart(DragStartEvent e)
|
||||
{
|
||||
// This text box is intended to be "read only" without actually specifying that.
|
||||
// As such we don't want to allow the user to select its content with a drag.
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
OnFocused?.Invoke();
|
||||
base.OnFocus(e);
|
||||
|
||||
GetContainingInputManager().TriggerFocusContention(this);
|
||||
}
|
||||
}
|
||||
|
||||
public Popover GetPopover() => new FileChooserPopover(handledExtensions, currentFile);
|
||||
public override Popover GetPopover() => new FileChooserPopover(handledExtensions, currentFile);
|
||||
|
||||
private class FileChooserPopover : OsuPopover
|
||||
{
|
||||
|
52
osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs
Normal file
52
osu.Game/Screens/Edit/Setup/LabelledTextBoxWithPopover.cs
Normal file
@ -0,0 +1,52 @@
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Setup
|
||||
{
|
||||
internal abstract class LabelledTextBoxWithPopover : LabelledTextBox, IHasPopover
|
||||
{
|
||||
public abstract Popover GetPopover();
|
||||
|
||||
protected override OsuTextBox CreateTextBox() =>
|
||||
new PopoverTextBox
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
CornerRadius = CORNER_RADIUS,
|
||||
OnFocused = this.ShowPopover
|
||||
};
|
||||
|
||||
internal class PopoverTextBox : OsuTextBox
|
||||
{
|
||||
public Action OnFocused;
|
||||
|
||||
protected override bool OnDragStart(DragStartEvent e)
|
||||
{
|
||||
// This text box is intended to be "read only" without actually specifying that.
|
||||
// As such we don't want to allow the user to select its content with a drag.
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
if (Current.Disabled)
|
||||
return;
|
||||
|
||||
OnFocused?.Invoke();
|
||||
base.OnFocus(e);
|
||||
|
||||
GetContainingInputManager().TriggerFocusContention(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user