diff --git a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs index 394fd75488..1d207d04c7 100644 --- a/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs +++ b/osu.Game.Benchmarks/BenchmarkBeatmapParsing.cs @@ -8,7 +8,7 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Archives; -using osu.Game.Resources; +using osu.Game.Tests.Resources; namespace osu.Game.Benchmarks { @@ -18,8 +18,8 @@ namespace osu.Game.Benchmarks public override void SetUp() { - using (var resources = new DllResourceStore(OsuResources.ResourceAssembly)) - using (var archive = resources.GetStream("Beatmaps/241526 Soleily - Renatus.osz")) + using (var resources = new DllResourceStore(typeof(TestResources).Assembly)) + using (var archive = resources.GetStream("Resources/Archives/241526 Soleily - Renatus.osz")) using (var reader = new ZipArchiveReader(archive)) reader.GetStream("Soleily - Renatus (Gamu) [Insane].osu").CopyTo(beatmapStream); } diff --git a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj index 88fe8f1150..41e726e05c 100644 --- a/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj +++ b/osu.Game.Benchmarks/osu.Game.Benchmarks.csproj @@ -13,6 +13,7 @@ + diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index df14b11e8a..75dfbf45f2 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -40,10 +40,10 @@ namespace osu.Game.Rulesets.Osu.Edit [BackgroundDependencyLoader] private void load() { + LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both }); + EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid(); EditorBeatmap.PlacementObject.ValueChanged += _ => updateDistanceSnapGrid(); - - LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both }); } protected override ComposeBlueprintContainer CreateBlueprintContainer() => new OsuBlueprintContainer(HitObjects); @@ -86,10 +86,24 @@ namespace osu.Game.Rulesets.Osu.Edit distanceSnapGridContainer.Clear(); distanceSnapGridCache.Invalidate(); - if (BlueprintContainer.CurrentTool is SelectTool && !EditorBeatmap.SelectedHitObjects.Any()) - return; + switch (BlueprintContainer.CurrentTool) + { + case SelectTool _: + if (!EditorBeatmap.SelectedHitObjects.Any()) + return; - if ((distanceSnapGrid = createDistanceSnapGrid(EditorBeatmap.SelectedHitObjects)) != null) + distanceSnapGrid = createDistanceSnapGrid(EditorBeatmap.SelectedHitObjects); + break; + + default: + if (!CursorInPlacementArea) + return; + + distanceSnapGrid = createDistanceSnapGrid(Enumerable.Empty()); + break; + } + + if (distanceSnapGrid != null) { distanceSnapGridContainer.Add(distanceSnapGrid); distanceSnapGridCache.Validate(); diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index b286c054e9..f626b45e42 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -27,7 +27,6 @@ using osu.Game.Online.API.Requests; using osu.Game.Rulesets; using osu.Game.Rulesets.Objects; using Decoder = osu.Game.Beatmaps.Formats.Decoder; -using ZipArchive = SharpCompress.Archives.Zip.ZipArchive; namespace osu.Game.Beatmaps { @@ -66,7 +65,6 @@ namespace osu.Game.Beatmaps private readonly AudioManager audioManager; private readonly GameHost host; private readonly BeatmapOnlineLookupQueue onlineLookupQueue; - private readonly Storage exportStorage; public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null, WorkingBeatmap defaultBeatmap = null) @@ -83,7 +81,6 @@ namespace osu.Game.Beatmaps beatmaps.BeatmapRestored += b => beatmapRestored.Value = new WeakReference(b); onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage); - exportStorage = storage.GetStorageForDirectory("exports"); } protected override ArchiveDownloadRequest CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) => @@ -214,26 +211,6 @@ namespace osu.Game.Beatmaps workingCache.Remove(working); } - /// - /// Exports a to an .osz package. - /// - /// The to export. - public void Export(BeatmapSetInfo set) - { - var localSet = QueryBeatmapSet(s => s.ID == set.ID); - - using (var archive = ZipArchive.Create()) - { - foreach (var file in localSet.Files) - archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath)); - - using (var outputStream = exportStorage.GetStream($"{set}.osz", FileAccess.Write, FileMode.Create)) - archive.SaveTo(outputStream); - - exportStorage.OpenInNativeExplorer(); - } - } - private readonly WeakList workingCache = new WeakList(); /// diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 33b16cbaaf..f21f708f95 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -22,6 +22,7 @@ using osu.Game.IO.Archives; using osu.Game.IPC; using osu.Game.Overlays.Notifications; using osu.Game.Utils; +using SharpCompress.Archives.Zip; using SharpCompress.Common; using FileInfo = osu.Game.IO.FileInfo; @@ -82,6 +83,8 @@ namespace osu.Game.Database // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ArchiveImportIPCChannel ipc; + private readonly Storage exportStorage; + protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStoreWithFileIncludes modelStore, IIpcHost importHost = null) { ContextFactory = contextFactory; @@ -90,6 +93,8 @@ namespace osu.Game.Database ModelStore.ItemAdded += item => handleEvent(() => itemAdded.Value = new WeakReference(item)); ModelStore.ItemRemoved += item => handleEvent(() => itemRemoved.Value = new WeakReference(item)); + exportStorage = storage.GetStorageForDirectory("exports"); + Files = new FileStore(contextFactory, storage); if (importHost != null) @@ -369,6 +374,29 @@ namespace osu.Game.Database return item; }, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap(); + /// + /// Exports an item to a legacy (.zip based) package. + /// + /// The item to export. + public void Export(TModel item) + { + var retrievedItem = ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == item.ID); + + if (retrievedItem == null) + throw new ArgumentException("Specified model could not be found", nameof(item)); + + using (var archive = ZipArchive.Create()) + { + foreach (var file in retrievedItem.Files) + archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath)); + + using (var outputStream = exportStorage.GetStream($"{getValidFilename(item.ToString())}{HandledExtensions.First()}", FileAccess.Write, FileMode.Create)) + archive.SaveTo(outputStream); + + exportStorage.OpenInNativeExplorer(); + } + } + public void UpdateFile(TModel model, TFileModel file, Stream contents) { using (var usage = ContextFactory.GetForWrite()) @@ -710,5 +738,12 @@ namespace osu.Game.Database } #endregion + + private string getValidFilename(string filename) + { + foreach (char c in Path.GetInvalidFileNameChars()) + filename = filename.Replace(c, '_'); + return filename; + } } } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 94080f5592..b84b9fec37 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; +using osu.Framework.Logging; using osu.Game.Configuration; using osu.Game.Graphics.UserInterface; using osu.Game.Skinning; @@ -38,9 +39,11 @@ namespace osu.Game.Overlays.Settings.Sections private void load(OsuConfigManager config) { FlowContent.Spacing = new Vector2(0, 5); + Children = new Drawable[] { skinDropdown = new SkinSettingsDropdown(), + new ExportSkinButton(), new SettingsSlider { LabelText = "Menu cursor size", @@ -117,5 +120,35 @@ namespace osu.Game.Overlays.Settings.Sections protected override DropdownMenu CreateMenu() => base.CreateMenu().With(m => m.MaxHeight = 200); } } + + private class ExportSkinButton : SettingsButton + { + [Resolved] + private SkinManager skins { get; set; } + + private Bindable currentSkin; + + [BackgroundDependencyLoader] + private void load() + { + Text = "Export selected skin"; + Action = export; + + currentSkin = skins.CurrentSkin.GetBoundCopy(); + currentSkin.BindValueChanged(skin => Enabled.Value = skin.NewValue.SkinInfo.ID > 0, true); + } + + private void export() + { + try + { + skins.Export(currentSkin.Value.SkinInfo); + } + catch (Exception e) + { + Logger.Log($"Could not export current skin: {e.Message}", level: LogLevel.Error); + } + } + } } } diff --git a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs index dca0513eb1..3541a78faa 100644 --- a/osu.Game/Rulesets/Edit/PlacementBlueprint.cs +++ b/osu.Game/Rulesets/Edit/PlacementBlueprint.cs @@ -30,11 +30,13 @@ namespace osu.Game.Rulesets.Edit /// protected readonly HitObject HitObject; - [Resolved] + [Resolved(canBeNull: true)] protected EditorClock EditorClock { get; private set; } private readonly IBindable beatmap = new Bindable(); + private Bindable startTimeBindable; + [Resolved] private IPlacementHandler placementHandler { get; set; } @@ -54,7 +56,8 @@ namespace osu.Game.Rulesets.Edit { this.beatmap.BindTo(beatmap); - ApplyDefaultsToHitObject(); + startTimeBindable = HitObject.StartTimeBindable.GetBoundCopy(); + startTimeBindable.BindValueChanged(_ => ApplyDefaultsToHitObject(), true); } /// @@ -80,9 +83,6 @@ namespace osu.Game.Rulesets.Edit PlacementActive = false; } - [Resolved(canBeNull: true)] - private EditorClock editorClock { get; set; } - /// /// Updates the position of this to a new screen-space position. /// @@ -90,7 +90,7 @@ namespace osu.Game.Rulesets.Edit public virtual void UpdatePosition(SnapResult snapResult) { if (!PlacementActive) - HitObject.StartTime = snapResult.Time ?? editorClock?.CurrentTime ?? Time.Current; + HitObject.StartTime = snapResult.Time ?? EditorClock?.CurrentTime ?? Time.Current; } /// diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index 6b9627188e..b9fe44ef3b 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -24,8 +24,6 @@ namespace osu.Game.Skinning public bool DeletePending { get; set; } - public string FullName => $"\"{Name}\" by {Creator}"; - public static SkinInfo Default { get; } = new SkinInfo { Name = "osu!lazer", @@ -34,6 +32,10 @@ namespace osu.Game.Skinning public bool Equals(SkinInfo other) => other != null && ID == other.ID; - public override string ToString() => FullName; + public override string ToString() + { + string author = Creator == null ? string.Empty : $"({Creator})"; + return $"{Name} {author}".Trim(); + } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 010ef8578a..dbaf0697a0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -19,7 +19,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 88b0c7dd8a..664a629369 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -75,7 +75,7 @@ - +