1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-14 03:25:11 +08:00

Merge branch 'master' into new-multiplayer-playlist

This commit is contained in:
Dan Balasescu 2021-11-30 18:16:17 +09:00
commit ef170471f0
48 changed files with 400 additions and 1005 deletions

View File

@ -52,7 +52,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1124.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2021.1127.0" />
</ItemGroup>
<ItemGroup Label="Transitive Dependencies">
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// 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;
@ -26,12 +26,12 @@ namespace osu.Game.Rulesets.Osu.Mods
public BindableFloat Scale { get; } = new BindableFloat(4)
{
Precision = 0.1f,
MinValue = 2,
MinValue = 1.5f,
MaxValue = 10,
};
[SettingSource("Style", "Change the animation style of the approach circles.", 1)]
public Bindable<AnimationStyle> Style { get; } = new Bindable<AnimationStyle>();
public Bindable<AnimationStyle> Style { get; } = new Bindable<AnimationStyle>(AnimationStyle.Gravity);
public void ApplyToDrawableHitObject(DrawableHitObject drawable)
{
@ -52,9 +52,18 @@ namespace osu.Game.Rulesets.Osu.Mods
{
switch (style)
{
default:
case AnimationStyle.Linear:
return Easing.None;
case AnimationStyle.Gravity:
return Easing.InBack;
case AnimationStyle.InOut1:
return Easing.InOutCubic;
case AnimationStyle.InOut2:
return Easing.InOutQuint;
case AnimationStyle.Accelerate1:
return Easing.In;
@ -64,9 +73,6 @@ namespace osu.Game.Rulesets.Osu.Mods
case AnimationStyle.Accelerate3:
return Easing.InQuint;
case AnimationStyle.Gravity:
return Easing.InBack;
case AnimationStyle.Decelerate1:
return Easing.Out;
@ -76,16 +82,14 @@ namespace osu.Game.Rulesets.Osu.Mods
case AnimationStyle.Decelerate3:
return Easing.OutQuint;
case AnimationStyle.InOut1:
return Easing.InOutCubic;
case AnimationStyle.InOut2:
return Easing.InOutQuint;
default:
throw new ArgumentOutOfRangeException(nameof(style), style, @"Unsupported animation style");
}
}
public enum AnimationStyle
{
Linear,
Gravity,
InOut1,
InOut2,

View File

@ -12,6 +12,7 @@ using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Extensions;
@ -474,7 +475,7 @@ namespace osu.Game.Tests.Database
}
[Test]
public void TestImportThenDeleteThenImport()
public void TestImportThenDeleteThenImportOptimisedPath()
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
@ -485,11 +486,39 @@ namespace osu.Game.Tests.Database
deleteBeatmapSet(imported, realmFactory.Context);
Assert.IsTrue(imported.DeletePending);
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
Assert.IsFalse(imported.DeletePending);
Assert.IsFalse(importedSecondTime.DeletePending);
});
}
[Test]
public void TestImportThenDeleteThenImportNonOptimisedPath()
{
RunTestWithRealmAsync(async (realmFactory, storage) =>
{
using var importer = new NonOptimisedBeatmapImporter(realmFactory, storage);
using var store = new RealmRulesetStore(realmFactory, storage);
var imported = await LoadOszIntoStore(importer, realmFactory.Context);
deleteBeatmapSet(imported, realmFactory.Context);
Assert.IsTrue(imported.DeletePending);
var importedSecondTime = await LoadOszIntoStore(importer, realmFactory.Context);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID == importedSecondTime.Beatmaps.First().ID);
Assert.IsFalse(imported.DeletePending);
Assert.IsFalse(importedSecondTime.DeletePending);
});
}
@ -823,7 +852,11 @@ namespace osu.Game.Tests.Database
{
IQueryable<RealmBeatmapSet>? resultSets = null;
waitForOrAssert(() => (resultSets = realm.All<RealmBeatmapSet>().Where(s => !s.DeletePending && s.OnlineID == 241526)).Any(),
waitForOrAssert(() =>
{
realm.Refresh();
return (resultSets = realm.All<RealmBeatmapSet>().Where(s => !s.DeletePending && s.OnlineID == 241526)).Any();
},
@"BeatmapSet did not import to the database in allocated time.", timeout);
// ensure we were stored to beatmap database backing...
@ -836,16 +869,16 @@ namespace osu.Game.Tests.Database
// ReSharper disable once PossibleUnintendedReferenceComparison
IEnumerable<RealmBeatmap> queryBeatmaps() => realm.All<RealmBeatmap>().Where(s => s.BeatmapSet != null && s.BeatmapSet == set);
waitForOrAssert(() => queryBeatmaps().Count() == 12, @"Beatmaps did not import to the database in allocated time", timeout);
waitForOrAssert(() => queryBeatmapSets().Count() == 1, @"BeatmapSet did not import to the database in allocated time", timeout);
Assert.AreEqual(12, queryBeatmaps().Count(), @"Beatmap count was not correct");
Assert.AreEqual(1, queryBeatmapSets().Count(), @"Beatmapset count was not correct");
int countBeatmapSetBeatmaps = 0;
int countBeatmaps = 0;
int countBeatmapSetBeatmaps;
int countBeatmaps;
waitForOrAssert(() =>
(countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count) ==
(countBeatmaps = queryBeatmaps().Count()),
$@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).", timeout);
Assert.AreEqual(
countBeatmapSetBeatmaps = queryBeatmapSets().First().Beatmaps.Count,
countBeatmaps = queryBeatmaps().Count(),
$@"Incorrect database beatmap count post-import ({countBeatmaps} but should be {countBeatmapSetBeatmaps}).");
foreach (RealmBeatmap b in set.Beatmaps)
Assert.IsTrue(set.Beatmaps.Any(c => c.OnlineID == b.OnlineID));
@ -867,5 +900,15 @@ namespace osu.Game.Tests.Database
Assert.Fail(failureMessage);
}
public class NonOptimisedBeatmapImporter : BeatmapImporter
{
public NonOptimisedBeatmapImporter(RealmContextFactory realmFactory, Storage storage)
: base(realmFactory, storage)
{
}
protected override bool HasCustomHashFunction => true;
}
}
}

View File

@ -29,6 +29,22 @@ namespace osu.Game.Tests.Database
});
}
[Test]
public void TestAccessAfterAttach()
{
RunTestWithRealm((realmFactory, _) =>
{
var beatmap = new RealmBeatmap(CreateRuleset(), new RealmBeatmapDifficulty(), new RealmBeatmapMetadata());
var liveBeatmap = beatmap.ToLive();
using (var context = realmFactory.CreateContext())
context.Write(r => r.Add(beatmap));
Assert.IsFalse(liveBeatmap.PerformRead(l => l.Hidden));
});
}
[Test]
public void TestAccessNonManaged()
{
@ -51,7 +67,7 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
RealmLive<RealmBeatmap>? liveBeatmap = null;
ILive<RealmBeatmap>? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
@ -88,7 +104,7 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
RealmLive<RealmBeatmap>? liveBeatmap = null;
ILive<RealmBeatmap>? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
@ -117,7 +133,7 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
RealmLive<RealmBeatmap>? liveBeatmap = null;
ILive<RealmBeatmap>? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
@ -143,7 +159,7 @@ namespace osu.Game.Tests.Database
{
RunTestWithRealm((realmFactory, _) =>
{
RealmLive<RealmBeatmap>? liveBeatmap = null;
ILive<RealmBeatmap>? liveBeatmap = null;
Task.Factory.StartNew(() =>
{
using (var threadContext = realmFactory.CreateContext())
@ -176,7 +192,7 @@ namespace osu.Game.Tests.Database
using (var updateThreadContext = realmFactory.CreateContext())
{
updateThreadContext.All<RealmBeatmap>().SubscribeForNotifications(gotChange);
RealmLive<RealmBeatmap>? liveBeatmap = null;
ILive<RealmBeatmap>? liveBeatmap = null;
Task.Factory.StartNew(() =>
{

View File

@ -0,0 +1,71 @@
// 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.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.Drawables.Cards;
using osu.Game.Graphics;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
namespace osu.Game.Tests.Visual.Beatmaps
{
public class TestSceneBeatmapCardDifficultyList : OsuTestScene
{
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
var beatmapSet = new APIBeatmapSet
{
Beatmaps = new[]
{
new APIBeatmap { RulesetID = 1, StarRating = 5.76, DifficultyName = "Oni" },
new APIBeatmap { RulesetID = 1, StarRating = 3.20, DifficultyName = "Muzukashii" },
new APIBeatmap { RulesetID = 1, StarRating = 2.45, DifficultyName = "Futsuu" },
new APIBeatmap { RulesetID = 0, StarRating = 2.04, DifficultyName = "Normal" },
new APIBeatmap { RulesetID = 0, StarRating = 3.51, DifficultyName = "Hard" },
new APIBeatmap { RulesetID = 0, StarRating = 5.25, DifficultyName = "Insane" },
new APIBeatmap { RulesetID = 2, StarRating = 2.64, DifficultyName = "Salad" },
new APIBeatmap { RulesetID = 2, StarRating = 3.56, DifficultyName = "Platter" },
new APIBeatmap { RulesetID = 2, StarRating = 4.65, DifficultyName = "Rain" },
new APIBeatmap { RulesetID = 3, StarRating = 1.93, DifficultyName = "[7K] Normal" },
new APIBeatmap { RulesetID = 3, StarRating = 3.18, DifficultyName = "[7K] Hyper" },
new APIBeatmap { RulesetID = 3, StarRating = 4.82, DifficultyName = "[7K] Another" },
new APIBeatmap { RulesetID = 4, StarRating = 9.99, DifficultyName = "Unknown?!" },
}
};
Child = new Container
{
Width = 300,
AutoSizeAxes = Axes.Y,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background2
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(10),
Child = new BeatmapCardDifficultyList(beatmapSet)
}
}
};
}
}
}

View File

@ -17,7 +17,6 @@ using osu.Game.Database;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
@ -236,7 +235,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
assertDownloadButtonVisible(false);
void assertDownloadButtonVisible(bool visible) => AddUntilStep($"download button {(visible ? "shown" : "hidden")}",
() => playlist.ChildrenOfType<BeatmapPanelDownloadButton>().Single().Alpha == (visible ? 1 : 0));
() => playlist.ChildrenOfType<BeatmapDownloadButton>().Single().Alpha == (visible ? 1 : 0));
}
[Test]
@ -250,7 +249,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
createPlaylist(byOnlineId, byChecksum);
AddAssert("download buttons shown", () => playlist.ChildrenOfType<BeatmapPanelDownloadButton>().All(d => d.IsPresent));
AddAssert("download buttons shown", () => playlist.ChildrenOfType<BeatmapDownloadButton>().All(d => d.IsPresent));
}
[Test]

View File

@ -6,15 +6,15 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Tests.Resources;
using osuTK;
namespace osu.Game.Tests.Visual.Online
{
public class TestSceneDirectDownloadButton : OsuTestScene
public class TestSceneBeatmapDownloadButton : OsuTestScene
{
private TestDownloadButton downloadButton;
@ -137,7 +137,7 @@ namespace osu.Game.Tests.Visual.Online
return apiBeatmapSet;
}
private class TestDownloadButton : BeatmapPanelDownloadButton
private class TestDownloadButton : BeatmapDownloadButton
{
public new bool DownloadEnabled => base.DownloadEnabled;

View File

@ -1,137 +0,0 @@
// 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.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Rulesets;
using osuTK;
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser;
namespace osu.Game.Tests.Visual.Online
{
[Cached(typeof(IPreviewTrackOwner))]
public class TestSceneDirectPanel : OsuTestScene, IPreviewTrackOwner
{
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
var normal = getBeatmapSet();
normal.HasVideo = true;
normal.HasStoryboard = true;
var undownloadable = getUndownloadableBeatmapSet();
var manyDifficulties = getManyDifficultiesBeatmapSet();
var explicitMap = getBeatmapSet();
explicitMap.HasExplicitContent = true;
var featuredMap = getBeatmapSet();
featuredMap.TrackId = 1;
var explicitFeaturedMap = getBeatmapSet();
explicitFeaturedMap.HasExplicitContent = true;
explicitFeaturedMap.TrackId = 2;
Child = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Full,
Padding = new MarginPadding(20),
Spacing = new Vector2(5, 20),
Children = new Drawable[]
{
new GridBeatmapPanel(normal),
new GridBeatmapPanel(undownloadable),
new GridBeatmapPanel(manyDifficulties),
new GridBeatmapPanel(explicitMap),
new GridBeatmapPanel(featuredMap),
new GridBeatmapPanel(explicitFeaturedMap),
new ListBeatmapPanel(normal),
new ListBeatmapPanel(undownloadable),
new ListBeatmapPanel(manyDifficulties),
new ListBeatmapPanel(explicitMap),
new ListBeatmapPanel(featuredMap),
new ListBeatmapPanel(explicitFeaturedMap)
},
},
};
APIBeatmapSet getBeatmapSet() => CreateAPIBeatmapSet(Ruleset.Value);
APIBeatmapSet getUndownloadableBeatmapSet() => new APIBeatmapSet
{
OnlineID = 123,
Title = "undownloadable beatmap",
Artist = "test",
Source = "more tests",
Author = new APIUser
{
Username = "BanchoBot",
Id = 3,
},
Availability = new BeatmapSetOnlineAvailability
{
DownloadDisabled = true,
},
Preview = @"https://b.ppy.sh/preview/12345.mp3",
PlayCount = 123,
FavouriteCount = 456,
BPM = 111,
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = new[]
{
new APIBeatmap
{
RulesetID = Ruleset.Value.OnlineID,
DifficultyName = "Test",
StarRating = 6.42,
}
}
};
APIBeatmapSet getManyDifficultiesBeatmapSet()
{
var beatmaps = new List<APIBeatmap>();
for (int i = 0; i < 100; i++)
{
beatmaps.Add(new APIBeatmap
{
RulesetID = i % 4,
StarRating = 2 + i % 4 * 2,
OverallDifficulty = 3.5f,
});
}
return new APIBeatmapSet
{
OnlineID = 1,
Title = "undownloadable beatmap",
Artist = "test",
Source = "more tests",
Author = new APIUser
{
Username = "BanchoBot",
Id = 3,
},
HasVideo = true,
HasStoryboard = true,
Covers = new BeatmapSetOnlineCovers(),
Beatmaps = beatmaps.ToArray(),
};
}
}
}
}

View File

@ -82,7 +82,7 @@ namespace osu.Game.Tests.Visual.SongSelect
beatmaps.Add(testBeatmap);
AddStep("set ruleset", () => Ruleset.Value = rulesetInfo);
setRuleset(rulesetInfo);
selectBeatmap(testBeatmap);
@ -167,6 +167,22 @@ namespace osu.Game.Tests.Visual.SongSelect
label => label.Statistic.Name == "BPM" && label.Statistic.Content == target.ToString(CultureInfo.InvariantCulture)));
}
private void setRuleset(RulesetInfo rulesetInfo)
{
Container containerBefore = null;
AddStep("set ruleset", () =>
{
// wedge content is only refreshed if the ruleset changes, so only wait for load in that case.
if (!rulesetInfo.Equals(Ruleset.Value))
containerBefore = infoWedge.DisplayedContent;
Ruleset.Value = rulesetInfo;
});
AddUntilStep("wait for async load", () => infoWedge.DisplayedContent != containerBefore);
}
private void selectBeatmap([CanBeNull] IBeatmap b)
{
Container containerBefore = null;

View File

@ -87,9 +87,9 @@ namespace osu.Game.Tournament.Tests.NonVisual
// Recreate the old setup that uses "tournament" as the base path.
string oldPath = Path.Combine(osuRoot, "tournament");
string videosPath = Path.Combine(oldPath, "videos");
string modsPath = Path.Combine(oldPath, "mods");
string flagsPath = Path.Combine(oldPath, "flags");
string videosPath = Path.Combine(oldPath, "Videos");
string modsPath = Path.Combine(oldPath, "Mods");
string flagsPath = Path.Combine(oldPath, "Flags");
Directory.CreateDirectory(videosPath);
Directory.CreateDirectory(modsPath);
@ -123,9 +123,9 @@ namespace osu.Game.Tournament.Tests.NonVisual
string migratedPath = Path.Combine(host.Storage.GetFullPath("."), "tournaments", "default");
videosPath = Path.Combine(migratedPath, "videos");
modsPath = Path.Combine(migratedPath, "mods");
flagsPath = Path.Combine(migratedPath, "flags");
videosPath = Path.Combine(migratedPath, "Videos");
modsPath = Path.Combine(migratedPath, "Mods");
flagsPath = Path.Combine(migratedPath, "Flags");
videoFile = Path.Combine(videosPath, "video.mp4");
modFile = Path.Combine(modsPath, "mod.png");

View File

@ -31,7 +31,7 @@ namespace osu.Game.Tournament.Components
[BackgroundDependencyLoader]
private void load(TextureStore textures, LadderInfo ladderInfo)
{
var customTexture = textures.Get($"mods/{modAcronym}");
var customTexture = textures.Get($"Mods/{modAcronym}");
if (customTexture != null)
{

View File

@ -51,6 +51,23 @@ namespace osu.Game.Tournament.IO
Logger.Log("Changing tournament storage: " + GetFullPath(string.Empty));
}
protected override void ChangeTargetStorage(Storage newStorage)
{
// due to an unfortunate oversight, on OSes that are sensitive to pathname casing
// the custom flags directory needed to be named `Flags` (uppercase),
// while custom mods and videos directories needed to be named `mods` and `videos` respectively (lowercase).
// to unify handling to uppercase, move any non-compliant directories automatically for the user to migrate.
// can be removed 20220528
if (newStorage.ExistsDirectory("flags"))
AttemptOperation(() => Directory.Move(newStorage.GetFullPath("flags"), newStorage.GetFullPath("Flags")));
if (newStorage.ExistsDirectory("mods"))
AttemptOperation(() => Directory.Move(newStorage.GetFullPath("mods"), newStorage.GetFullPath("Mods")));
if (newStorage.ExistsDirectory("videos"))
AttemptOperation(() => Directory.Move(newStorage.GetFullPath("videos"), newStorage.GetFullPath("Videos")));
base.ChangeTargetStorage(newStorage);
}
public IEnumerable<string> ListTournaments() => AllTournaments.GetDirectories(string.Empty);
public override void Migrate(Storage newStorage)

View File

@ -9,7 +9,7 @@ namespace osu.Game.Tournament.IO
public class TournamentVideoResourceStore : NamespacedResourceStore<byte[]>
{
public TournamentVideoResourceStore(Storage storage)
: base(new StorageBackedResourceStore(storage), "videos")
: base(new StorageBackedResourceStore(storage), "Videos")
{
AddExtension("m4v");
AddExtension("avi");

View File

@ -197,7 +197,7 @@ namespace osu.Game.Tournament.Screens.TeamIntro
{
row.Add(new Sprite
{
Texture = textures.Get($"mods/{mods.ToLower()}"),
Texture = textures.Get($"Mods/{mods.ToLower()}"),
Scale = new Vector2(0.5f)
});
}

View File

@ -105,7 +105,9 @@ namespace osu.Game.Audio
protected override void LoadComplete()
{
base.LoadComplete();
Logger.Log($"A {nameof(PreviewTrack)} was created without a containing {nameof(IPreviewTrackOwner)}. An owner should be added for correct behaviour.");
if (Owner == null)
Logger.Log($"A {nameof(PreviewTrack)} was created without a containing {nameof(IPreviewTrackOwner)}. An owner should be added for correct behaviour.");
}
protected override Track GetTrack() => trackManager.Get($"https://b.ppy.sh/preview/{beatmapSetInfo.OnlineID}.mp3");

View File

@ -288,9 +288,9 @@ namespace osu.Game.Beatmaps
#region Implementation of IModelFileManager<in BeatmapSetInfo,in BeatmapSetFileInfo>
public void ReplaceFile(BeatmapSetInfo model, BeatmapSetFileInfo file, Stream contents, string filename = null)
public void ReplaceFile(BeatmapSetInfo model, BeatmapSetFileInfo file, Stream contents)
{
beatmapModelManager.ReplaceFile(model, file, contents, filename);
beatmapModelManager.ReplaceFile(model, file, contents);
}
public void DeleteFile(BeatmapSetInfo model, BeatmapSetFileInfo file)

View File

@ -6,7 +6,6 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Extensions;
using osu.Game.Graphics.Containers;
@ -14,9 +13,9 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Overlays.BeatmapListing.Panels
namespace osu.Game.Beatmaps.Drawables
{
public class BeatmapPanelDownloadButton : CompositeDrawable
public class BeatmapDownloadButton : CompositeDrawable
{
protected bool DownloadEnabled => button.Enabled.Value;
@ -35,7 +34,7 @@ namespace osu.Game.Overlays.BeatmapListing.Panels
private readonly IBeatmapSetInfo beatmapSet;
public BeatmapPanelDownloadButton(IBeatmapSetInfo beatmapSet)
public BeatmapDownloadButton(IBeatmapSetInfo beatmapSet)
{
this.beatmapSet = beatmapSet;

View File

@ -23,7 +23,6 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapSet;
using osuTK;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Resources.Localisation.Web;
using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton;

View File

@ -0,0 +1,103 @@
// 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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Chat;
using osu.Game.Rulesets;
using osuTK;
namespace osu.Game.Beatmaps.Drawables.Cards
{
public class BeatmapCardDifficultyList : CompositeDrawable
{
public BeatmapCardDifficultyList(IBeatmapSetInfo beatmapSetInfo)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
FillFlowContainer flow;
InternalChild = flow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 3)
};
bool firstGroup = true;
foreach (var group in beatmapSetInfo.Beatmaps.GroupBy(beatmap => beatmap.Ruleset.OnlineID).OrderBy(group => group.Key))
{
if (!firstGroup)
{
flow.Add(Empty().With(s =>
{
s.RelativeSizeAxes = Axes.X;
s.Height = 4;
}));
}
foreach (var difficulty in group.OrderBy(b => b.StarRating))
flow.Add(new BeatmapCardDifficultyRow(difficulty));
firstGroup = false;
}
}
private class BeatmapCardDifficultyRow : CompositeDrawable
{
private readonly IBeatmapInfo beatmapInfo;
public BeatmapCardDifficultyRow(IBeatmapInfo beatmapInfo)
{
this.beatmapInfo = beatmapInfo;
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(4, 0),
Children = new[]
{
(rulesets.GetRuleset(beatmapInfo.Ruleset.OnlineID)?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }).With(icon =>
{
icon.Anchor = icon.Origin = Anchor.CentreLeft;
icon.Size = new Vector2(16);
}),
new StarRatingDisplay(new StarDifficulty(beatmapInfo.StarRating, 0), StarRatingDisplaySize.Small)
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft
},
new LinkFlowContainer(s =>
{
s.Font = OsuFont.Default.With(size: 14, weight: FontWeight.SemiBold);
}).With(d =>
{
d.AutoSizeAxes = Axes.Both;
d.Anchor = Anchor.CentreLeft;
d.Origin = Anchor.CentreLeft;
d.Padding = new MarginPadding { Bottom = 2 };
d.AddLink(beatmapInfo.DifficultyName, LinkAction.OpenBeatmap, beatmapInfo.OnlineID.ToString());
})
}
};
}
}
}
}

View File

@ -97,6 +97,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
if (previewTrack == null)
{
toggleLoading(true);
LoadComponentAsync(previewTrack = previewTrackManager.Get(beatmapSetInfo), onPreviewLoaded);
}
else
@ -112,18 +113,23 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons
private void onPreviewLoaded(PreviewTrack loadedPreview)
{
// another async load might have completed before this one.
// if so, do not make any changes.
if (loadedPreview != previewTrack)
return;
// Make sure that we schedule to after the next audio frame to fix crashes in single-threaded execution.
// See: https://github.com/ppy/osu-framework/issues/4692
Schedule(() =>
{
// another async load might have completed before this one.
// if so, do not make any changes.
if (loadedPreview != previewTrack)
return;
AddInternal(loadedPreview);
toggleLoading(false);
AddInternal(loadedPreview);
toggleLoading(false);
loadedPreview.Stopped += () => Schedule(() => Playing.Value = false);
loadedPreview.Stopped += () => Schedule(() => Playing.Value = false);
if (Playing.Value)
tryStartPreview();
if (Playing.Value)
tryStartPreview();
});
}
private void tryStartPreview()

View File

@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapListing.Panels
namespace osu.Game.Beatmaps.Drawables.Cards
{
public class IconPill : CircularContainer
{

View File

@ -62,7 +62,7 @@ namespace osu.Game.Beatmaps.Drawables
// matching web: https://github.com/ppy/osu-web/blob/d06d8c5e735eb1f48799b1654b528e9a7afb0a35/resources/assets/lib/beatmapset-panel.tsx#L127
bool collapsed = beatmapSet.Beatmaps.Count() > 12;
foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset.OnlineID))
foreach (var rulesetGrouping in beatmapSet.Beatmaps.GroupBy(beatmap => beatmap.Ruleset.OnlineID).OrderBy(group => group.Key))
{
flow.Add(new RulesetDifficultyGroup(rulesetGrouping.Key, rulesetGrouping, collapsed));
}

View File

@ -5,13 +5,12 @@ using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapListing.Panels
namespace osu.Game.Beatmaps.Drawables
{
public class DownloadProgressBar : CompositeDrawable
{

View File

@ -453,13 +453,12 @@ namespace osu.Game.Database
/// <param name="model">The item to operate on.</param>
/// <param name="file">The existing file to be replaced.</param>
/// <param name="contents">The new file contents.</param>
/// <param name="filename">An optional filename for the new file. Will use the previous filename if not specified.</param>
public void ReplaceFile(TModel model, TFileModel file, Stream contents, string filename = null)
public void ReplaceFile(TModel model, TFileModel file, Stream contents)
{
using (ContextFactory.GetForWrite())
{
DeleteFile(model, file);
AddFile(model, contents, filename ?? file.Filename);
AddFile(model, contents, file.Filename);
}
}

View File

@ -15,8 +15,7 @@ namespace osu.Game.Database
/// <param name="model">The item to operate on.</param>
/// <param name="file">The existing file to be replaced.</param>
/// <param name="contents">The new file contents.</param>
/// <param name="filename">An optional filename for the new file. Will use the previous filename if not specified.</param>
void ReplaceFile(TModel model, TFileModel file, Stream contents, string filename = null);
void ReplaceFile(TModel model, TFileModel file, Stream contents);
/// <summary>
/// Delete an existing file.

View File

@ -307,6 +307,9 @@ namespace osu.Game.Database
case 10:
string rulesetSettingClassName = getMappedOrOriginalName(typeof(RealmRulesetSetting));
if (!migration.OldRealm.Schema.TryFindObjectSchema(rulesetSettingClassName, out _))
return;
var oldSettings = migration.OldRealm.DynamicApi.All(rulesetSettingClassName);
var newSettings = migration.NewRealm.All<RealmRulesetSetting>().ToList();
@ -329,6 +332,9 @@ namespace osu.Game.Database
case 11:
string keyBindingClassName = getMappedOrOriginalName(typeof(RealmKeyBinding));
if (!migration.OldRealm.Schema.TryFindObjectSchema(keyBindingClassName, out _))
return;
var oldKeyBindings = migration.OldRealm.DynamicApi.All(keyBindingClassName);
var newKeyBindings = migration.NewRealm.All<RealmKeyBinding>().ToList();

View File

@ -17,7 +17,7 @@ namespace osu.Game.Database
{
public Guid ID { get; }
public bool IsManaged { get; }
public bool IsManaged => data.IsManaged;
private readonly SynchronizationContext? fetchedContext;
private readonly int fetchedThreadId;
@ -37,8 +37,6 @@ namespace osu.Game.Database
if (data.IsManaged)
{
IsManaged = true;
fetchedContext = SynchronizationContext.Current;
fetchedThreadId = Thread.CurrentThread.ManagedThreadId;
}

View File

@ -49,13 +49,13 @@ namespace osu.Game.Database
return mapper.Map<T>(item);
}
public static List<RealmLive<T>> ToLive<T>(this IEnumerable<T> realmList)
public static List<ILive<T>> ToLive<T>(this IEnumerable<T> realmList)
where T : RealmObject, IHasGuidPrimaryKey
{
return realmList.Select(l => new RealmLive<T>(l)).ToList();
return realmList.Select(l => new RealmLive<T>(l)).Cast<ILive<T>>().ToList();
}
public static RealmLive<T> ToLive<T>(this T realmObject)
public static ILive<T> ToLive<T>(this T realmObject)
where T : RealmObject, IHasGuidPrimaryKey
{
return new RealmLive<T>(realmObject);

View File

@ -66,7 +66,7 @@ namespace osu.Game.Graphics.UserInterface
Origin = Anchor.TopRight;
BackgroundColour = Color4.Black.Opacity(0.7f);
MaxHeight = 400;
MaxHeight = 200;
}
protected override DrawableDropdownMenuItem CreateDrawableDropdownMenuItem(MenuItem item) => new DrawableOsuTabDropdownMenuItem(item);

View File

@ -16,6 +16,7 @@ namespace osu.Game.Models
{
public RealmFile File { get; set; } = null!;
// [Indexed] cannot be used on `EmbeddedObject`s as it only applies to top-level queries. May need to reconsider this if performance becomes a concern.
public string Filename { get; set; } = null!;
public RealmNamedFileUsage(RealmFile file, string filename)

View File

@ -377,6 +377,13 @@ namespace osu.Game
FrameStatistics.ValueChanged += e => fpsDisplayVisible.Value = e.NewValue != FrameStatisticsMode.None;
}
protected override void Update()
{
base.Update();
realmFactory.Refresh();
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
@ -442,10 +449,6 @@ namespace osu.Game
protected override Storage CreateStorage(GameHost host, Storage defaultStorage) => new OsuStorage(host, defaultStorage);
private void migrateDataToRealm()
{
}
private void onRulesetChanged(ValueChangedEvent<RulesetInfo> r)
{
if (r.NewValue?.Available != true)

View File

@ -1,216 +0,0 @@
// 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 System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapListing.Panels
{
public abstract class BeatmapPanel : OsuClickableContainer, IHasContextMenu
{
public readonly APIBeatmapSet SetInfo;
private const double hover_transition_time = 400;
private const int maximum_difficulty_icons = 10;
private Container content;
public PreviewTrack Preview => PlayButton.Preview;
public IBindable<bool> PreviewPlaying => PlayButton?.Playing;
protected abstract PlayButton PlayButton { get; }
protected abstract Box PreviewBar { get; }
protected virtual bool FadePlayButton => true;
protected override Container<Drawable> Content => content;
protected Action ViewBeatmap;
protected BeatmapPanel(APIBeatmapSet setInfo)
: base(HoverSampleSet.Submit)
{
Debug.Assert(setInfo.OnlineID > 0);
SetInfo = setInfo;
}
private readonly EdgeEffectParameters edgeEffectNormal = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 1f),
Radius = 2f,
Colour = Color4.Black.Opacity(0.25f),
};
private readonly EdgeEffectParameters edgeEffectHovered = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 5f),
Radius = 10f,
Colour = Color4.Black.Opacity(0.3f),
};
[BackgroundDependencyLoader(permitNulls: true)]
private void load(BeatmapManager beatmaps, OsuColour colours, BeatmapSetOverlay beatmapSetOverlay)
{
AddInternal(content = new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
EdgeEffect = edgeEffectNormal,
Children = new[]
{
CreateBackground(),
new DownloadProgressBar(SetInfo)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Depth = -1,
},
}
});
Action = ViewBeatmap = () =>
{
Debug.Assert(SetInfo.OnlineID > 0);
beatmapSetOverlay?.FetchAndShowBeatmapSet(SetInfo.OnlineID);
};
}
protected override void Update()
{
base.Update();
if (PreviewPlaying.Value && Preview != null && Preview.TrackLoaded)
{
PreviewBar.Width = (float)(Preview.CurrentTime / Preview.Length);
}
}
protected override bool OnHover(HoverEvent e)
{
content.TweenEdgeEffectTo(edgeEffectHovered, hover_transition_time, Easing.OutQuint);
content.MoveToY(-4, hover_transition_time, Easing.OutQuint);
if (FadePlayButton)
PlayButton.FadeIn(120, Easing.InOutQuint);
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
content.TweenEdgeEffectTo(edgeEffectNormal, hover_transition_time, Easing.OutQuint);
content.MoveToY(0, hover_transition_time, Easing.OutQuint);
if (FadePlayButton && !PreviewPlaying.Value)
PlayButton.FadeOut(120, Easing.InOutQuint);
base.OnHoverLost(e);
}
protected override void LoadComplete()
{
base.LoadComplete();
this.FadeInFromZero(200, Easing.Out);
PreviewPlaying.ValueChanged += playing =>
{
PlayButton.FadeTo(playing.NewValue || IsHovered || !FadePlayButton ? 1 : 0, 120, Easing.InOutQuint);
PreviewBar.FadeTo(playing.NewValue ? 1 : 0, 120, Easing.InOutQuint);
};
}
protected List<DifficultyIcon> GetDifficultyIcons(OsuColour colours)
{
var icons = new List<DifficultyIcon>();
if (SetInfo.Beatmaps.Length > maximum_difficulty_icons)
{
foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct())
icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.Where(b => b.RulesetID == ruleset.OnlineID).ToList(), ruleset, this is ListBeatmapPanel ? Color4.White : colours.Gray5));
}
else
{
foreach (var b in SetInfo.Beatmaps.OrderBy(beatmap => beatmap.RulesetID).ThenBy(beatmap => beatmap.StarRating))
icons.Add(new DifficultyIcon(b));
}
return icons;
}
protected Drawable CreateBackground() => new UpdateableOnlineBeatmapSetCover
{
RelativeSizeAxes = Axes.Both,
OnlineInfo = SetInfo,
};
public class Statistic : FillFlowContainer
{
private readonly SpriteText text;
private int value;
public int Value
{
get => value;
set
{
this.value = value;
text.Text = Value.ToString(@"N0");
}
}
public Statistic(IconUsage icon, int value = 0)
{
Anchor = Anchor.TopRight;
Origin = Anchor.TopRight;
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
Spacing = new Vector2(5f, 0f);
Children = new Drawable[]
{
text = new OsuSpriteText { Font = OsuFont.GetFont(weight: FontWeight.SemiBold, italics: true) },
new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Icon = icon,
Shadow = true,
Size = new Vector2(14),
},
};
Value = value;
}
}
public MenuItem[] ContextMenuItems => new MenuItem[]
{
new OsuMenuItem("View Beatmap", MenuItemType.Highlighted, ViewBeatmap),
};
}
}

View File

@ -1,269 +0,0 @@
// 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.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapSet;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapListing.Panels
{
public class GridBeatmapPanel : BeatmapPanel
{
private const float horizontal_padding = 10;
private const float vertical_padding = 5;
private FillFlowContainer bottomPanel, statusContainer, titleContainer, artistContainer;
private PlayButton playButton;
private Box progressBar;
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
public GridBeatmapPanel(APIBeatmapSet beatmap)
: base(beatmap)
{
Width = 380;
Height = 140 + vertical_padding; // full height of all the elements plus vertical padding (autosize uses the image)
}
protected override void LoadComplete()
{
base.LoadComplete();
bottomPanel.LayoutDuration = 200;
bottomPanel.LayoutEasing = Easing.Out;
bottomPanel.Origin = Anchor.BottomLeft;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Content.CornerRadius = 4;
AddRange(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black.Opacity(0.5f),
},
bottomPanel = new FillFlowContainer
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.TopLeft,
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0f, vertical_padding),
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = horizontal_padding, Right = horizontal_padding },
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
titleContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new OsuSpriteText
{
Text = new RomanisableString(SetInfo.TitleUnicode, SetInfo.Title),
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true)
},
}
},
artistContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new OsuSpriteText
{
Text = new RomanisableString(SetInfo.ArtistUnicode, SetInfo.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
}
}
}
},
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
},
progressBar = new Box
{
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
BypassAutoSizeAxes = Axes.Both,
Size = new Vector2(0, 3),
Alpha = 0,
Colour = colours.Yellow,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Padding = new MarginPadding
{
Top = vertical_padding,
Bottom = vertical_padding,
Left = horizontal_padding,
Right = horizontal_padding,
},
Children = new Drawable[]
{
new LinkFlowContainer(s =>
{
s.Shadow = false;
s.Font = OsuFont.GetFont(size: 14);
}).With(d =>
{
d.AutoSizeAxes = Axes.Both;
d.AddText("mapped by ", t => t.Colour = colours.Gray5);
d.AddUserLink(SetInfo.Author);
}),
new Container
{
AutoSizeAxes = Axes.X,
Height = 14,
Children = new[]
{
new OsuSpriteText
{
Text = SetInfo.Source,
Font = OsuFont.GetFont(size: 14),
Shadow = false,
Colour = colours.Gray5,
Alpha = string.IsNullOrEmpty(SetInfo.Source) ? 0f : 1f,
},
},
},
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Spacing = new Vector2(3),
Children = GetDifficultyIcons(colours),
},
},
},
new BeatmapPanelDownloadButton(SetInfo)
{
Size = new Vector2(50, 30),
Margin = new MarginPadding(horizontal_padding),
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
},
},
},
},
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Margin = new MarginPadding { Top = vertical_padding, Right = vertical_padding },
Children = new[]
{
new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.PlayCount),
new Statistic(FontAwesome.Solid.Heart, SetInfo.FavouriteCount),
},
},
statusContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = 5, Left = 5 },
Spacing = new Vector2(5),
},
playButton = new PlayButton(SetInfo)
{
Margin = new MarginPadding { Top = 5, Left = 10 },
Size = new Vector2(30),
Alpha = 0,
},
});
if (SetInfo.HasExplicitContent)
{
titleContainer.Add(new ExplicitContentBeatmapPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 10f, Top = 2f },
});
}
if (SetInfo.TrackId != null)
{
artistContainer.Add(new FeaturedArtistBeatmapPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 10f, Top = 2f },
});
}
if (SetInfo.HasVideo)
{
statusContainer.Add(new IconPill(FontAwesome.Solid.Film));
}
if (SetInfo.HasStoryboard)
{
statusContainer.Add(new IconPill(FontAwesome.Solid.Image));
}
statusContainer.Add(new BeatmapSetOnlineStatusPill
{
AutoSizeAxes = Axes.Both,
TextSize = 12,
TextPadding = new MarginPadding { Horizontal = 10, Vertical = 5 },
Status = SetInfo.Status,
});
PreviewPlaying.ValueChanged += _ => updateStatusContainer();
}
protected override bool OnHover(HoverEvent e)
{
updateStatusContainer();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
updateStatusContainer();
}
private void updateStatusContainer() => statusContainer.FadeTo(IsHovered || PreviewPlaying.Value ? 0 : 1, 120, Easing.InOutQuint);
}
}

View File

@ -1,267 +0,0 @@
// 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.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapSet;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapListing.Panels
{
public class ListBeatmapPanel : BeatmapPanel
{
private const float transition_duration = 120;
private const float horizontal_padding = 10;
private const float vertical_padding = 5;
private const float height = 70;
private FillFlowContainer statusContainer, titleContainer, artistContainer;
protected BeatmapPanelDownloadButton DownloadButton;
private PlayButton playButton;
private Box progressBar;
protected override bool FadePlayButton => false;
protected override PlayButton PlayButton => playButton;
protected override Box PreviewBar => progressBar;
public ListBeatmapPanel(APIBeatmapSet beatmap)
: base(beatmap)
{
RelativeSizeAxes = Axes.X;
Height = height;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Content.CornerRadius = 5;
AddRange(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.25f), Color4.Black.Opacity(0.75f)),
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding, Left = horizontal_padding, Right = vertical_padding },
Children = new Drawable[]
{
new FillFlowContainer
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
LayoutEasing = Easing.OutQuint,
LayoutDuration = transition_duration,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
playButton = new PlayButton(SetInfo)
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
Size = new Vector2(height / 3),
FillMode = FillMode.Fit,
Margin = new MarginPadding { Right = 10 },
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
titleContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
new OsuSpriteText
{
Text = new RomanisableString(SetInfo.TitleUnicode, SetInfo.Title),
Font = OsuFont.GetFont(size: 18, weight: FontWeight.Bold, italics: true)
},
}
},
artistContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
new OsuSpriteText
{
Text = new RomanisableString(SetInfo.ArtistUnicode, SetInfo.Artist),
Font = OsuFont.GetFont(weight: FontWeight.Bold, italics: true)
},
},
},
}
},
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
statusContainer = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Vertical = vertical_padding, Horizontal = 5 },
Spacing = new Vector2(5),
},
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
Height = 20,
Margin = new MarginPadding { Top = vertical_padding, Bottom = vertical_padding },
Spacing = new Vector2(3),
Children = GetDifficultyIcons(colours),
},
},
},
},
},
}
},
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new Container
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.Both,
Child = DownloadButton = new BeatmapPanelDownloadButton(SetInfo)
{
Size = new Vector2(height - vertical_padding * 3),
Margin = new MarginPadding { Left = vertical_padding * 2, Right = vertical_padding },
},
},
new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new Statistic(FontAwesome.Solid.PlayCircle, SetInfo.PlayCount),
new Statistic(FontAwesome.Solid.Heart, SetInfo.FavouriteCount),
new LinkFlowContainer(s =>
{
s.Shadow = false;
s.Font = OsuFont.GetFont(size: 14);
})
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Both,
}.With(d =>
{
d.AutoSizeAxes = Axes.Both;
d.AddText("mapped by ");
d.AddUserLink(SetInfo.Author);
}),
new OsuSpriteText
{
Text = SetInfo.Source,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Font = OsuFont.GetFont(size: 14),
Alpha = string.IsNullOrEmpty(SetInfo.Source) ? 0f : 1f,
},
},
},
},
},
},
},
progressBar = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
BypassAutoSizeAxes = Axes.Y,
Size = new Vector2(0, 3),
Alpha = 0,
Colour = colours.Yellow,
},
});
if (SetInfo.HasExplicitContent)
{
titleContainer.Add(new ExplicitContentBeatmapPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 10f, Top = 2f },
});
}
if (SetInfo.TrackId != null)
{
artistContainer.Add(new FeaturedArtistBeatmapPill
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 10f, Top = 2f },
});
}
if (SetInfo.HasVideo)
{
statusContainer.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
}
if (SetInfo.HasStoryboard)
{
statusContainer.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) });
}
statusContainer.Add(new BeatmapSetOnlineStatusPill
{
AutoSizeAxes = Axes.Both,
TextSize = 12,
TextPadding = new MarginPadding { Horizontal = 10, Vertical = 4 },
Status = SetInfo.Status,
});
}
}
}

View File

@ -17,7 +17,6 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Overlays.BeatmapSet.Buttons;
using osuTK;
@ -286,7 +285,7 @@ namespace osu.Game.Overlays.BeatmapSet
{
case DownloadState.LocallyAvailable:
// temporary for UX until new design is implemented.
downloadButtonsContainer.Child = new BeatmapPanelDownloadButton(BeatmapSet.Value)
downloadButtonsContainer.Child = new BeatmapDownloadButton(BeatmapSet.Value)
{
Width = 50,
RelativeSizeAxes = Axes.Y,

View File

@ -9,13 +9,13 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Resources.Localisation.Web;
using osuTK;
using osuTK.Graphics;

View File

@ -14,7 +14,7 @@ using osu.Game.Online.API.Requests.Responses;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.BeatmapListing.Panels
namespace osu.Game.Overlays.BeatmapSet.Buttons
{
public class PlayButton : Container
{

View File

@ -11,7 +11,6 @@ using osu.Game.Audio;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.BeatmapListing.Panels;
using osuTK;
namespace osu.Game.Overlays.BeatmapSet.Buttons

View File

@ -214,7 +214,8 @@ namespace osu.Game.Overlays
{
base.LoadComplete();
beatmap.BindDisabledChanged(beatmapDisabledChanged, true);
beatmap.BindDisabledChanged(_ => Scheduler.AddOnce(beatmapDisabledChanged));
beatmapDisabledChanged();
musicController.TrackChanged += trackChanged;
trackChanged(beatmap.Value);
@ -318,8 +319,10 @@ namespace osu.Game.Overlays
};
}
private void beatmapDisabledChanged(bool disabled)
private void beatmapDisabledChanged()
{
bool disabled = beatmap.Disabled;
if (disabled)
playlist?.Hide();

View File

@ -51,7 +51,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Text = MaintenanceSettingsStrings.DeleteAllBeatmaps,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
dialogOverlay?.Push(new MassDeleteConfirmationDialog(() =>
{
deleteBeatmapsButton.Enabled.Value = false;
Task.Run(() => beatmaps.Delete(beatmaps.GetAllUsableBeatmapSets())).ContinueWith(t => Schedule(() => deleteBeatmapsButton.Enabled.Value = true));
@ -77,7 +77,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Text = MaintenanceSettingsStrings.DeleteAllScores,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
dialogOverlay?.Push(new MassDeleteConfirmationDialog(() =>
{
deleteScoresButton.Enabled.Value = false;
Task.Run(() => scores.Delete(scores.GetAllUsableScores())).ContinueWith(t => Schedule(() => deleteScoresButton.Enabled.Value = true));
@ -103,7 +103,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Text = MaintenanceSettingsStrings.DeleteAllSkins,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() =>
dialogOverlay?.Push(new MassDeleteConfirmationDialog(() =>
{
deleteSkinsButton.Enabled.Value = false;
Task.Run(() => skins.Delete(skins.GetAllUserSkins())).ContinueWith(t => Schedule(() => deleteSkinsButton.Enabled.Value = true));
@ -131,7 +131,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
Text = MaintenanceSettingsStrings.DeleteAllCollections,
Action = () =>
{
dialogOverlay?.Push(new DeleteAllBeatmapsDialog(collectionManager.DeleteAll));
dialogOverlay?.Push(new MassDeleteConfirmationDialog(collectionManager.DeleteAll));
}
});
}

View File

@ -7,9 +7,9 @@ using osu.Game.Overlays.Dialog;
namespace osu.Game.Overlays.Settings.Sections.Maintenance
{
public class DeleteAllBeatmapsDialog : PopupDialog
public class MassDeleteConfirmationDialog : PopupDialog
{
public DeleteAllBeatmapsDialog(Action deleteAction)
public MassDeleteConfirmationDialog(Action deleteAction)
{
BodyText = "Everything?";

View File

@ -68,13 +68,19 @@ namespace osu.Game.Overlays.Toolbar
{
base.LoadComplete();
Current.BindDisabledChanged(disabled => this.FadeColour(disabled ? Color4.Gray : Color4.White, 300), true);
Current.BindValueChanged(_ => moveLineToCurrent());
Current.BindDisabledChanged(_ => Scheduler.AddOnce(currentDisabledChanged));
currentDisabledChanged();
Current.BindValueChanged(_ => moveLineToCurrent());
// Scheduled to allow the button flow layout to be computed before the line position is updated
ScheduleAfterChildren(moveLineToCurrent);
}
private void currentDisabledChanged()
{
this.FadeColour(Current.Disabled ? Color4.Gray : Color4.White, 300);
}
private bool hasInitialPosition;
private void moveLineToCurrent()

View File

@ -78,9 +78,9 @@ namespace osu.Game.Screens.Edit.Setup
using (var stream = info.OpenRead())
{
if (oldFile != null)
beatmaps.ReplaceFile(set, oldFile, stream, info.Name);
else
beatmaps.AddFile(set, stream, info.Name);
beatmaps.DeleteFile(set, oldFile);
beatmaps.AddFile(set, stream, info.Name);
}
working.Value.Metadata.BackgroundFile = info.Name;
@ -105,9 +105,8 @@ namespace osu.Game.Screens.Edit.Setup
using (var stream = info.OpenRead())
{
if (oldFile != null)
beatmaps.ReplaceFile(set, oldFile, stream, info.Name);
else
beatmaps.AddFile(set, stream, info.Name);
beatmaps.DeleteFile(set, oldFile);
beatmaps.AddFile(set, stream, info.Name);
}
working.Value.Metadata.AudioFile = info.Name;

View File

@ -25,7 +25,6 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.Chat;
using osu.Game.Online.Rooms;
using osu.Game.Overlays.BeatmapListing.Panels;
using osu.Game.Overlays.BeatmapSet;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@ -147,7 +146,8 @@ namespace osu.Game.Screens.OnlinePlay
if (showItemOwner)
{
ownerAvatar.Show();
userLookupCache.GetUserAsync(Item.OwnerID).ContinueWith(u => Schedule(() => ownerAvatar.User = u.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
userLookupCache.GetUserAsync(Item.OwnerID)
.ContinueWith(u => Schedule(() => ownerAvatar.User = u.Result), TaskContinuationOptions.OnlyOnRanToCompletion);
}
difficultyIconContainer.Child = new DifficultyIcon(Item.Beatmap.Value, ruleset.Value, requiredMods, performBackgroundDifficultyLookup: false) { Size = new Vector2(ICON_HEIGHT) };
@ -338,7 +338,7 @@ namespace osu.Game.Screens.OnlinePlay
return true;
}
private sealed class PlaylistDownloadButton : BeatmapPanelDownloadButton
private sealed class PlaylistDownloadButton : BeatmapDownloadButton
{
private readonly PlaylistItem playlistItem;
@ -458,7 +458,7 @@ namespace osu.Game.Screens.OnlinePlay
});
}
public LocalisableString TooltipText => User == null ? "loading user..." : $"queued by {User.Username}";
public LocalisableString TooltipText => User == null ? string.Empty : $"queued by {User.Username}";
private class TooltipArea : Component, IHasTooltip
{

View File

@ -171,7 +171,7 @@ namespace osu.Game.Skinning
var oldFile = skin.SkinInfo.Files.FirstOrDefault(f => f.Filename == filename);
if (oldFile != null)
skinModelManager.ReplaceFile(skin.SkinInfo, oldFile, streamContent, oldFile.Filename);
skinModelManager.ReplaceFile(skin.SkinInfo, oldFile, streamContent);
else
skinModelManager.AddFile(skin.SkinInfo, streamContent, filename);
}

View File

@ -294,12 +294,8 @@ namespace osu.Game.Stores
/// <remarks>
/// In the case of no matching files, a hash will be generated from the passed archive's <see cref="ArchiveReader.Name"/>.
/// </remarks>
protected virtual string ComputeHash(TModel item, ArchiveReader? reader = null)
protected virtual string ComputeHash(TModel item)
{
if (reader != null)
// fast hashing for cases where the item's files may not be populated.
return computeHashFast(reader);
// for now, concatenate all hashable files in the set to create a unique hash.
MemoryStream hashable = new MemoryStream();
@ -374,7 +370,7 @@ namespace osu.Game.Stores
// TODO: look into rollback of file additions (or delayed commit).
item.Files.AddRange(createFileInfos(archive, Files, realm));
item.Hash = ComputeHash(item, archive);
item.Hash = ComputeHash(item);
// TODO: we may want to run this outside of the transaction.
await Populate(item, archive, realm, cancellationToken).ConfigureAwait(false);
@ -387,7 +383,9 @@ namespace osu.Game.Stores
if (CanReuseExisting(existing, item))
{
LogForModel(item, @$"Found existing {HumanisedModelName} for {item} (ID {existing.ID}) skipping import.");
existing.DeletePending = false;
transaction.Commit();
return existing.ToLive();
}

View File

@ -36,7 +36,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="10.7.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.1124.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.1127.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
<PackageReference Include="Sentry" Version="3.11.1" />
<PackageReference Include="SharpCompress" Version="0.30.0" />

View File

@ -60,7 +60,7 @@
<Reference Include="System.Net.Http" />
</ItemGroup>
<ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1124.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2021.1127.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.1112.0" />
</ItemGroup>
<!-- See https://github.com/dotnet/runtime/issues/35988 (can be removed after Xamarin uses net5.0 / net6.0) -->
@ -83,7 +83,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.1" />
<PackageReference Include="ppy.osu.Framework" Version="2021.1124.0" />
<PackageReference Include="ppy.osu.Framework" Version="2021.1127.0" />
<PackageReference Include="SharpCompress" Version="0.30.0" />
<PackageReference Include="NUnit" Version="3.13.2" />
<PackageReference Include="SharpRaven" Version="2.4.0" />