1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-13 19:53:23 +08:00

Merge branch 'master' into input-handler-configuration

This commit is contained in:
Dean Herbert 2021-03-17 18:05:14 +09:00
commit 249ab8ab3d
30 changed files with 278 additions and 122 deletions

View File

@ -27,8 +27,8 @@
<PackageReference Include="Microsoft.NETCore.Targets" Version="5.0.0" /> <PackageReference Include="Microsoft.NETCore.Targets" Version="5.0.0" />
<PackageReference Include="System.IO.Packaging" Version="5.0.0" /> <PackageReference Include="System.IO.Packaging" Version="5.0.0" />
<PackageReference Include="ppy.squirrel.windows" Version="1.9.0.5" /> <PackageReference Include="ppy.squirrel.windows" Version="1.9.0.5" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="5.0.4" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" /> <PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="DiscordRichPresence" Version="1.0.175" /> <PackageReference Include="DiscordRichPresence" Version="1.0.175" />
</ItemGroup> </ItemGroup>

View File

@ -5,7 +5,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>

View File

@ -5,7 +5,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>

View File

@ -5,7 +5,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>

View File

@ -37,9 +37,10 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
AddInternal(scaleContainer = new Container AddInternal(scaleContainer = new Container
{ {
Scale = new Vector2(SPRITE_SCALE), Scale = new Vector2(SPRITE_SCALE),
Anchor = Anchor.Centre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Y = SPINNER_Y_CENTRE,
Children = new Drawable[] Children = new Drawable[]
{ {
glow = new Sprite glow = new Sprite

View File

@ -33,30 +33,23 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{ {
spinnerBlink = source.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true; spinnerBlink = source.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.SpinnerNoBlink)?.Value != true;
AddInternal(new Container AddRangeInternal(new Drawable[]
{
// the old-style spinner relied heavily on absolute screen-space coordinate values.
// wrap everything in a container simulating absolute coords to preserve alignment
// as there are skins that depend on it.
Width = 640,
Height = 480,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Children = new Drawable[]
{ {
new Sprite new Sprite
{ {
Anchor = Anchor.Centre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Texture = source.GetTexture("spinner-background"), Texture = source.GetTexture("spinner-background"),
Scale = new Vector2(SPRITE_SCALE) Scale = new Vector2(SPRITE_SCALE),
Y = SPINNER_Y_CENTRE,
}, },
disc = new Sprite disc = new Sprite
{ {
Anchor = Anchor.Centre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Texture = source.GetTexture("spinner-circle"), Texture = source.GetTexture("spinner-circle"),
Scale = new Vector2(SPRITE_SCALE) Scale = new Vector2(SPRITE_SCALE),
Y = SPINNER_Y_CENTRE,
}, },
metre = new Container metre = new Container
{ {
@ -64,8 +57,7 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
// this anchor makes no sense, but that's what stable uses. // this anchor makes no sense, but that's what stable uses.
Anchor = Anchor.TopLeft, Anchor = Anchor.TopLeft,
Origin = Anchor.TopLeft, Origin = Anchor.TopLeft,
// adjustment for stable (metre has additional offset) Margin = new MarginPadding { Top = SPINNER_TOP_OFFSET },
Margin = new MarginPadding { Top = 20 },
Masking = true, Masking = true,
Child = metreSprite = new Sprite Child = metreSprite = new Sprite
{ {
@ -75,7 +67,6 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
Scale = new Vector2(SPRITE_SCALE) Scale = new Vector2(SPRITE_SCALE)
} }
} }
}
}); });
} }

View File

@ -16,6 +16,15 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{ {
public abstract class LegacySpinner : CompositeDrawable public abstract class LegacySpinner : CompositeDrawable
{ {
/// <remarks>
/// All constants are in osu!stable's gamefield space, which is shifted 16px downwards.
/// This offset is negated in both osu!stable and osu!lazer to bring all constants into window-space.
/// Note: SPINNER_Y_CENTRE + SPINNER_TOP_OFFSET - Position.Y = 240 (=480/2, or half the window-space in osu!stable)
/// </remarks>
protected const float SPINNER_TOP_OFFSET = 45f - 16f;
protected const float SPINNER_Y_CENTRE = SPINNER_TOP_OFFSET + 219f;
protected const float SPRITE_SCALE = 0.625f; protected const float SPRITE_SCALE = 0.625f;
protected DrawableSpinner DrawableSpinner { get; private set; } protected DrawableSpinner DrawableSpinner { get; private set; }
@ -26,7 +35,13 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(DrawableHitObject drawableHitObject, ISkinSource source) private void load(DrawableHitObject drawableHitObject, ISkinSource source)
{ {
RelativeSizeAxes = Axes.Both; Anchor = Anchor.Centre;
Origin = Anchor.Centre;
// osu!stable positions spinner components in window-space (as opposed to gamefield-space). This is a 640x480 area taking up the entire screen.
// In lazer, the gamefield-space positional transformation is applied in OsuPlayfieldAdjustmentContainer, which is inverted here to make this area take up the entire window space.
Size = new Vector2(640, 480);
Position = new Vector2(0, -8f);
DrawableSpinner = (DrawableSpinner)drawableHitObject; DrawableSpinner = (DrawableSpinner)drawableHitObject;
@ -34,22 +49,22 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
{ {
spin = new Sprite spin = new Sprite
{ {
Anchor = Anchor.Centre, Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Depth = float.MinValue, Depth = float.MinValue,
Texture = source.GetTexture("spinner-spin"), Texture = source.GetTexture("spinner-spin"),
Scale = new Vector2(SPRITE_SCALE), Scale = new Vector2(SPRITE_SCALE),
Y = 120 - 45 // offset temporarily to avoid overlapping default spin counter Y = SPINNER_TOP_OFFSET + 335,
}, },
clear = new Sprite clear = new Sprite
{ {
Anchor = Anchor.Centre, Alpha = 0,
Anchor = Anchor.TopCentre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Depth = float.MinValue, Depth = float.MinValue,
Alpha = 0,
Texture = source.GetTexture("spinner-clear"), Texture = source.GetTexture("spinner-clear"),
Scale = new Vector2(SPRITE_SCALE), Scale = new Vector2(SPRITE_SCALE),
Y = -60 Y = SPINNER_TOP_OFFSET + 115,
}, },
}); });
} }

View File

@ -5,7 +5,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">
<OutputType>WinExe</OutputType> <OutputType>WinExe</OutputType>

View File

@ -56,6 +56,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
beatmaps.Add(new BeatmapInfo beatmaps.Add(new BeatmapInfo
{ {
Ruleset = rulesets.GetRuleset(i % 4), Ruleset = rulesets.GetRuleset(i % 4),
RulesetID = i % 4, // workaround for efcore 5 compatibility.
OnlineBeatmapID = beatmapId, OnlineBeatmapID = beatmapId,
Length = length, Length = length,
BPM = bpm, BPM = bpm,

View File

@ -229,6 +229,35 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("settings displayed", () => Game.Settings.State.Value == Visibility.Visible); AddUntilStep("settings displayed", () => Game.Settings.State.Value == Visibility.Visible);
} }
[Test]
public void TestToolbarHiddenByUser()
{
AddStep("Enter menu", () => InputManager.Key(Key.Enter));
AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded);
AddStep("Hide toolbar", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.T);
InputManager.ReleaseKey(Key.ControlLeft);
});
pushEscape();
AddStep("Enter menu", () => InputManager.Key(Key.Enter));
AddAssert("Toolbar is hidden", () => Game.Toolbar.State.Value == Visibility.Hidden);
AddStep("Enter song select", () =>
{
InputManager.Key(Key.Enter);
InputManager.Key(Key.Enter);
});
AddAssert("Toolbar is hidden", () => Game.Toolbar.State.Value == Visibility.Hidden);
}
private void pushEscape() => private void pushEscape() =>
AddStep("Press escape", () => InputManager.Key(Key.Escape)); AddStep("Press escape", () => InputManager.Key(Key.Escape));

View File

@ -186,6 +186,7 @@ namespace osu.Game.Tests.Visual.SongSelect
Metadata = metadata, Metadata = metadata,
BaseDifficulty = new BeatmapDifficulty(), BaseDifficulty = new BeatmapDifficulty(),
Ruleset = ruleset, Ruleset = ruleset,
RulesetID = ruleset.ID.GetValueOrDefault(), // workaround for efcore 5 compatibility.
StarDifficulty = difficultyIndex + 1, StarDifficulty = difficultyIndex + 1,
Version = $"SR{difficultyIndex + 1}" Version = $"SR{difficultyIndex + 1}"
}).ToList() }).ToList()

View File

@ -911,9 +911,11 @@ namespace osu.Game.Tests.Visual.SongSelect
int length = RNG.Next(30000, 200000); int length = RNG.Next(30000, 200000);
double bpm = RNG.NextSingle(80, 200); double bpm = RNG.NextSingle(80, 200);
var ruleset = getRuleset();
beatmaps.Add(new BeatmapInfo beatmaps.Add(new BeatmapInfo
{ {
Ruleset = getRuleset(), Ruleset = ruleset,
RulesetID = ruleset.ID.GetValueOrDefault(), // workaround for efcore 5 compatibility.
OnlineBeatmapID = beatmapId, OnlineBeatmapID = beatmapId,
Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})", Version = $"{beatmapId} (length {TimeSpan.FromMilliseconds(length):m\\:ss}, bpm {bpm:0.#})",
Length = length, Length = length,

View File

@ -6,7 +6,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.9.1" />
<PackageReference Include="NUnit" Version="3.13.1" /> <PackageReference Include="NUnit" Version="3.13.1" />
<PackageReference Include="NUnit3TestAdapter" Version="3.17.0" /> <PackageReference Include="NUnit3TestAdapter" Version="3.17.0" />
<PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.4" /> <PackageReference Update="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
<PackageReference Include="Moq" Version="4.16.1" /> <PackageReference Include="Moq" Version="4.16.1" />
</ItemGroup> </ItemGroup>
<PropertyGroup Label="Project"> <PropertyGroup Label="Project">

View File

@ -17,12 +17,12 @@ namespace osu.Game.Beatmaps
public abstract class BeatmapConverter<T> : IBeatmapConverter public abstract class BeatmapConverter<T> : IBeatmapConverter
where T : HitObject where T : HitObject
{ {
private event Action<HitObject, IEnumerable<HitObject>> ObjectConverted; private event Action<HitObject, IEnumerable<HitObject>> objectConverted;
event Action<HitObject, IEnumerable<HitObject>> IBeatmapConverter.ObjectConverted event Action<HitObject, IEnumerable<HitObject>> IBeatmapConverter.ObjectConverted
{ {
add => ObjectConverted += value; add => objectConverted += value;
remove => ObjectConverted -= value; remove => objectConverted -= value;
} }
public IBeatmap Beatmap { get; } public IBeatmap Beatmap { get; }
@ -92,10 +92,10 @@ namespace osu.Game.Beatmaps
var converted = ConvertHitObject(obj, beatmap, cancellationToken); var converted = ConvertHitObject(obj, beatmap, cancellationToken);
if (ObjectConverted != null) if (objectConverted != null)
{ {
converted = converted.ToList(); converted = converted.ToList();
ObjectConverted.Invoke(obj, converted); objectConverted.Invoke(obj, converted);
} }
foreach (var c in converted) foreach (var c in converted)

View File

@ -174,6 +174,8 @@ namespace osu.Game.Beatmaps
if (beatmapSet.Beatmaps.Any(b => b.BaseDifficulty == null)) if (beatmapSet.Beatmaps.Any(b => b.BaseDifficulty == null))
throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}."); throw new InvalidOperationException($"Cannot import {nameof(BeatmapInfo)} with null {nameof(BeatmapInfo.BaseDifficulty)}.");
beatmapSet.Requery(ContextFactory);
// check if a set already exists with the same online id, delete if it does. // check if a set already exists with the same online id, delete if it does.
if (beatmapSet.OnlineBeatmapSetID != null) if (beatmapSet.OnlineBeatmapSetID != null)
{ {

View File

@ -471,9 +471,6 @@ namespace osu.Game.Beatmaps.Formats
private string toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo) private string toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo)
{ {
if (hitSampleInfo == null)
return "0";
if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy) if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy)
return legacy.CustomSampleBank.ToString(CultureInfo.InvariantCulture); return legacy.CustomSampleBank.ToString(CultureInfo.InvariantCulture);

View File

@ -462,6 +462,8 @@ namespace osu.Game.Database
// Dereference the existing file info, since the file model will be removed. // Dereference the existing file info, since the file model will be removed.
if (file.FileInfo != null) if (file.FileInfo != null)
{ {
file.Requery(usage.Context);
Files.Dereference(file.FileInfo); Files.Dereference(file.FileInfo);
// This shouldn't be required, but here for safety in case the provided TModel is not being change tracked // This shouldn't be required, but here for safety in case the provided TModel is not being change tracked
@ -635,10 +637,12 @@ namespace osu.Game.Database
{ {
using (Stream s = reader.GetStream(file)) using (Stream s = reader.GetStream(file))
{ {
var fileInfo = files.Add(s);
fileInfos.Add(new TFileModel fileInfos.Add(new TFileModel
{ {
Filename = file.Substring(prefix.Length).ToStandardisedPath(), Filename = file.Substring(prefix.Length).ToStandardisedPath(),
FileInfo = files.Add(s) FileInfo = fileInfo,
FileInfoID = fileInfo.ID // workaround for efcore 5 compatibility.
}); });
} }
} }

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 System;
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Scoring;
using osu.Game.Skinning;
namespace osu.Game.Database
{
/// <summary>
/// Extension methods which contain workarounds to make EFcore 5.x work with our existing (incorrect) thread safety.
/// The intention is to avoid blocking package updates while we consider the future of the database backend, with a potential backend switch imminent.
/// </summary>
public static class DatabaseWorkaroundExtensions
{
/// <summary>
/// Re-query the provided model to ensure it is in a sane state. This method requires explicit implementation per model type.
/// </summary>
/// <param name="model"></param>
/// <param name="contextFactory"></param>
public static void Requery(this IHasPrimaryKey model, IDatabaseContextFactory contextFactory)
{
switch (model)
{
case SkinInfo skinInfo:
requeryFiles(skinInfo.Files, contextFactory);
break;
case ScoreInfo scoreInfo:
requeryFiles(scoreInfo.Beatmap.BeatmapSet.Files, contextFactory);
requeryFiles(scoreInfo.Files, contextFactory);
break;
case BeatmapSetInfo beatmapSetInfo:
var context = contextFactory.Get();
foreach (var beatmap in beatmapSetInfo.Beatmaps)
{
// Workaround System.InvalidOperationException
// The instance of entity type 'RulesetInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked.
beatmap.Ruleset = context.RulesetInfo.Find(beatmap.RulesetID);
}
requeryFiles(beatmapSetInfo.Files, contextFactory);
break;
default:
throw new ArgumentException($"{nameof(Requery)} does not have support for the provided model type", nameof(model));
}
void requeryFiles<T>(List<T> files, IDatabaseContextFactory databaseContextFactory) where T : class, INamedFileInfo
{
var dbContext = databaseContextFactory.Get();
foreach (var file in files)
{
Requery(file, dbContext);
}
}
}
public static void Requery(this INamedFileInfo file, OsuDbContext dbContext)
{
// Workaround System.InvalidOperationException
// The instance of entity type 'FileInfo' cannot be tracked because another instance with the same key value for {'ID'} is already being tracked.
file.FileInfo = dbContext.FileInfo.Find(file.FileInfoID);
}
}
}

View File

@ -3,7 +3,6 @@
using System; using System;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Statistics; using osu.Framework.Statistics;
@ -111,10 +110,10 @@ namespace osu.Game.Database
{ {
base.OnConfiguring(optionsBuilder); base.OnConfiguring(optionsBuilder);
optionsBuilder optionsBuilder
// this is required for the time being due to the way we are querying in places like BeatmapStore. .UseSqlite(connectionString,
// if we ever move to having consumers file their own .Includes, or get eager loading support, this could be re-enabled. sqliteOptions => sqliteOptions
.ConfigureWarnings(warnings => warnings.Ignore(CoreEventId.IncludeIgnoredWarning)) .CommandTimeout(10)
.UseSqlite(connectionString, sqliteOptions => sqliteOptions.CommandTimeout(10)) .UseQuerySplittingBehavior(QuerySplittingBehavior.SingleQuery))
.UseLoggerFactory(logger.Value); .UseLoggerFactory(logger.Value);
} }

View File

@ -884,12 +884,9 @@ namespace osu.Game
frameworkConfig.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode).SetDefault(); frameworkConfig.GetBindable<ConfineMouseMode>(FrameworkSetting.ConfineMouseMode).SetDefault();
return true; return true;
case GlobalAction.ToggleToolbar:
Toolbar.ToggleVisibility();
return true;
case GlobalAction.ToggleGameplayMouseButtons: case GlobalAction.ToggleGameplayMouseButtons:
LocalConfig.Set(OsuSetting.MouseDisableButtons, !LocalConfig.Get<bool>(OsuSetting.MouseDisableButtons)); var mouseDisableButtons = LocalConfig.GetBindable<bool>(OsuSetting.MouseDisableButtons);
mouseDisableButtons.Value = !mouseDisableButtons.Value;
return true; return true;
case GlobalAction.RandomSkin: case GlobalAction.RandomSkin:

View File

@ -21,6 +21,8 @@ namespace osu.Game.Overlays
{ {
public class ChangelogOverlay : OnlineOverlay<ChangelogHeader> public class ChangelogOverlay : OnlineOverlay<ChangelogHeader>
{ {
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
public readonly Bindable<APIChangelogBuild> Current = new Bindable<APIChangelogBuild>(); public readonly Bindable<APIChangelogBuild> Current = new Bindable<APIChangelogBuild>();
private Sample sampleBack; private Sample sampleBack;
@ -126,8 +128,11 @@ namespace osu.Game.Overlays
private Task initialFetchTask; private Task initialFetchTask;
private void performAfterFetch(Action action) => fetchListing()?.ContinueWith(_ => private void performAfterFetch(Action action) => Schedule(() =>
{
fetchListing()?.ContinueWith(_ =>
Schedule(action), TaskContinuationOptions.OnlyOnRanToCompletion); Schedule(action), TaskContinuationOptions.OnlyOnRanToCompletion);
});
private Task fetchListing() private Task fetchListing()
{ {
@ -163,7 +168,7 @@ namespace osu.Game.Overlays
await API.PerformAsync(req).ConfigureAwait(false); await API.PerformAsync(req).ConfigureAwait(false);
return tcs.Task; return tcs.Task;
}); }).Unwrap();
} }
private CancellationTokenSource loadContentCancellation; private CancellationTokenSource loadContentCancellation;

View File

@ -138,7 +138,6 @@ namespace osu.Game.Overlays.Profile.Header
if (!string.IsNullOrEmpty(user.Twitter)) if (!string.IsNullOrEmpty(user.Twitter))
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}"); anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}");
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Discord, user.Discord); anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Discord, user.Discord);
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat");
anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtocol, user.Website); anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtocol, user.Website);
// If no information was added to the bottomLinkContainer, hide it to avoid unwanted padding // If no information was added to the bottomLinkContainer, hide it to avoid unwanted padding

View File

@ -13,14 +13,22 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
namespace osu.Game.Overlays.Toolbar namespace osu.Game.Overlays.Toolbar
{ {
public class Toolbar : VisibilityContainer public class Toolbar : VisibilityContainer, IKeyBindingHandler<GlobalAction>
{ {
public const float HEIGHT = 40; public const float HEIGHT = 40;
public const float TOOLTIP_HEIGHT = 30; public const float TOOLTIP_HEIGHT = 30;
/// <summary>
/// Whether the user hid this <see cref="Toolbar"/> with <see cref="GlobalAction.ToggleToolbar"/>.
/// In this state, automatic toggles should not occur, respecting the user's preference to have no toolbar.
/// </summary>
private bool hiddenByUser;
public Action OnHome; public Action OnHome;
private ToolbarUserButton userButton; private ToolbarUserButton userButton;
@ -30,7 +38,7 @@ namespace osu.Game.Overlays.Toolbar
protected readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All); protected readonly IBindable<OverlayActivation> OverlayActivationMode = new Bindable<OverlayActivation>(OverlayActivation.All);
// Toolbar components like RulesetSelector should receive keyboard input events even when the toolbar is hidden. // Toolbar and its components need keyboard input even when hidden.
public override bool PropagateNonPositionalInputSubTree => true; public override bool PropagateNonPositionalInputSubTree => true;
public Toolbar() public Toolbar()
@ -142,7 +150,9 @@ namespace osu.Game.Overlays.Toolbar
protected override void UpdateState(ValueChangedEvent<Visibility> state) protected override void UpdateState(ValueChangedEvent<Visibility> state)
{ {
if (state.NewValue == Visibility.Visible && OverlayActivationMode.Value == OverlayActivation.Disabled) bool blockShow = hiddenByUser || OverlayActivationMode.Value == OverlayActivation.Disabled;
if (state.NewValue == Visibility.Visible && blockShow)
{ {
State.Value = Visibility.Hidden; State.Value = Visibility.Hidden;
return; return;
@ -164,5 +174,25 @@ namespace osu.Game.Overlays.Toolbar
this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint); this.MoveToY(-DrawSize.Y, transition_time, Easing.OutQuint);
this.FadeOut(transition_time, Easing.InQuint); this.FadeOut(transition_time, Easing.InQuint);
} }
public bool OnPressed(GlobalAction action)
{
if (OverlayActivationMode.Value == OverlayActivation.Disabled)
return false;
switch (action)
{
case GlobalAction.ToggleToolbar:
hiddenByUser = State.Value == Visibility.Visible; // set before toggling to allow the operation to always succeed.
ToggleVisibility();
return true;
}
return false;
}
public void OnReleased(GlobalAction action)
{
}
} }
} }

View File

@ -73,7 +73,7 @@ namespace osu.Game.Scoring
} }
set set
{ {
modsJson = null; modsJson = JsonConvert.SerializeObject(value.Select(m => new DeserializedMod { Acronym = m.Acronym }));
mods = value; mods = value;
} }
} }
@ -86,16 +86,7 @@ namespace osu.Game.Scoring
[Column("Mods")] [Column("Mods")]
public string ModsJson public string ModsJson
{ {
get get => modsJson;
{
if (modsJson != null)
return modsJson;
if (mods == null)
return null;
return modsJson = JsonConvert.SerializeObject(mods.Select(m => new DeserializedMod { Acronym = m.Acronym }));
}
set set
{ {
modsJson = value; modsJson = value;

View File

@ -52,6 +52,11 @@ namespace osu.Game.Scoring
this.configManager = configManager; this.configManager = configManager;
} }
protected override void PreImport(ScoreInfo model)
{
model.Requery(ContextFactory);
}
protected override ScoreInfo CreateModel(ArchiveReader archive) protected override ScoreInfo CreateModel(ArchiveReader archive)
{ {
if (archive == null) if (archive == null)

View File

@ -74,7 +74,6 @@ namespace osu.Game.Screens.Edit
private string lastSavedHash; private string lastSavedHash;
private Box bottomBackground;
private Container<EditorScreen> screenContainer; private Container<EditorScreen> screenContainer;
private EditorScreen currentScreen; private EditorScreen currentScreen;
@ -106,26 +105,29 @@ namespace osu.Game.Screens.Edit
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours, GameHost host, OsuConfigManager config) private void load(OsuColour colours, GameHost host, OsuConfigManager config)
{ {
if (Beatmap.Value is DummyWorkingBeatmap) var loadableBeatmap = Beatmap.Value;
if (loadableBeatmap is DummyWorkingBeatmap)
{ {
isNewBeatmap = true; isNewBeatmap = true;
var newBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value); loadableBeatmap = beatmapManager.CreateNew(Ruleset.Value, api.LocalUser.Value);
// required so we can get the track length in EditorClock.
// this is safe as nothing has yet got a reference to this new beatmap.
loadableBeatmap.LoadTrack();
// this is a bit haphazard, but guards against setting the lease Beatmap bindable if // this is a bit haphazard, but guards against setting the lease Beatmap bindable if
// the editor has already been exited. // the editor has already been exited.
if (!ValidForPush) if (!ValidForPush)
return; return;
// this probably shouldn't be set in the asynchronous load method, but everything following relies on it.
Beatmap.Value = newBeatmap;
} }
beatDivisor.Value = Beatmap.Value.BeatmapInfo.BeatDivisor; beatDivisor.Value = loadableBeatmap.BeatmapInfo.BeatDivisor;
beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue); beatDivisor.BindValueChanged(divisor => loadableBeatmap.BeatmapInfo.BeatDivisor = divisor.NewValue);
// Todo: should probably be done at a DrawableRuleset level to share logic with Player. // Todo: should probably be done at a DrawableRuleset level to share logic with Player.
clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock = new EditorClock(loadableBeatmap, beatDivisor) { IsCoupled = false };
UpdateClockSource(); UpdateClockSource();
@ -139,7 +141,7 @@ namespace osu.Game.Screens.Edit
try try
{ {
playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); playableBeatmap = loadableBeatmap.GetPlayableBeatmap(loadableBeatmap.BeatmapInfo.Ruleset);
// clone these locally for now to avoid incurring overhead on GetPlayableBeatmap usages. // clone these locally for now to avoid incurring overhead on GetPlayableBeatmap usages.
// eventually we will want to improve how/where this is done as there are issues with *not* cloning it in all cases. // eventually we will want to improve how/where this is done as there are issues with *not* cloning it in all cases.
@ -153,13 +155,21 @@ namespace osu.Game.Screens.Edit
return; return;
} }
AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, Beatmap.Value.Skin)); AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, loadableBeatmap.Skin));
dependencies.CacheAs(editorBeatmap); dependencies.CacheAs(editorBeatmap);
changeHandler = new EditorChangeHandler(editorBeatmap); changeHandler = new EditorChangeHandler(editorBeatmap);
dependencies.CacheAs<IEditorChangeHandler>(changeHandler); dependencies.CacheAs<IEditorChangeHandler>(changeHandler);
updateLastSavedHash(); updateLastSavedHash();
Schedule(() =>
{
// we need to avoid changing the beatmap from an asynchronous load thread. it can potentially cause weirdness including crashes.
// this assumes that nothing during the rest of this load() method is accessing Beatmap.Value (loadableBeatmap should be preferred).
// generally this is quite safe, as the actual load of editor content comes after menuBar.Mode.ValueChanged is fired in its own LoadComplete.
Beatmap.Value = loadableBeatmap;
});
OsuMenuItem undoMenuItem; OsuMenuItem undoMenuItem;
OsuMenuItem redoMenuItem; OsuMenuItem redoMenuItem;
@ -167,17 +177,6 @@ namespace osu.Game.Screens.Edit
EditorMenuItem copyMenuItem; EditorMenuItem copyMenuItem;
EditorMenuItem pasteMenuItem; EditorMenuItem pasteMenuItem;
var fileMenuItems = new List<MenuItem>
{
new EditorMenuItem("Save", MenuItemType.Standard, Save)
};
if (RuntimeInfo.IsDesktop)
fileMenuItems.Add(new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap));
fileMenuItems.Add(new EditorMenuItemSpacer());
fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit));
AddInternal(new OsuContextMenuContainer AddInternal(new OsuContextMenuContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -209,7 +208,7 @@ namespace osu.Game.Screens.Edit
{ {
new MenuItem("File") new MenuItem("File")
{ {
Items = fileMenuItems Items = createFileMenuItems()
}, },
new MenuItem("Edit") new MenuItem("Edit")
{ {
@ -242,7 +241,11 @@ namespace osu.Game.Screens.Edit
Height = 60, Height = 60,
Children = new Drawable[] Children = new Drawable[]
{ {
bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Gray2
},
new Container new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -299,8 +302,6 @@ namespace osu.Game.Screens.Edit
clipboard.BindValueChanged(content => pasteMenuItem.Action.Disabled = string.IsNullOrEmpty(content.NewValue)); clipboard.BindValueChanged(content => pasteMenuItem.Action.Disabled = string.IsNullOrEmpty(content.NewValue));
menuBar.Mode.ValueChanged += onModeChanged; menuBar.Mode.ValueChanged += onModeChanged;
bottomBackground.Colour = colours.Gray2;
} }
/// <summary> /// <summary>
@ -681,6 +682,21 @@ namespace osu.Game.Screens.Edit
lastSavedHash = changeHandler.CurrentStateHash; lastSavedHash = changeHandler.CurrentStateHash;
} }
private List<MenuItem> createFileMenuItems()
{
var fileMenuItems = new List<MenuItem>
{
new EditorMenuItem("Save", MenuItemType.Standard, Save)
};
if (RuntimeInfo.IsDesktop)
fileMenuItems.Add(new EditorMenuItem("Export package", MenuItemType.Standard, exportBeatmap));
fileMenuItems.Add(new EditorMenuItemSpacer());
fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit));
return fileMenuItems;
}
public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime); public double SnapTime(double time, double? referenceTime) => editorBeatmap.SnapTime(time, referenceTime);
public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime); public double GetBeatLengthAtTime(double referenceTime) => editorBeatmap.GetBeatLengthAtTime(referenceTime);

View File

@ -142,6 +142,11 @@ namespace osu.Game.Skinning
} }
} }
protected override void PreImport(SkinInfo model)
{
model.Requery(ContextFactory);
}
/// <summary> /// <summary>
/// Retrieve a <see cref="Skin"/> instance for the provided <see cref="SkinInfo"/> /// Retrieve a <see cref="Skin"/> instance for the provided <see cref="SkinInfo"/>
/// </summary> /// </summary>

View File

@ -111,9 +111,6 @@ namespace osu.Game.Users
[JsonProperty(@"twitter")] [JsonProperty(@"twitter")]
public string Twitter; public string Twitter;
[JsonProperty(@"skype")]
public string Skype;
[JsonProperty(@"discord")] [JsonProperty(@"discord")]
public string Discord; public string Discord;

View File

@ -24,10 +24,10 @@
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.2" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="5.0.2" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.3" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.MessagePack" Version="5.0.3" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.2" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Protocols.NewtonsoftJson" Version="5.0.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="5.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="5.0.4" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" /> <PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.NETCore.Targets" Version="3.1.0" /> <PackageReference Include="Microsoft.NETCore.Targets" Version="5.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2021.309.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.309.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2021.211.1" />

View File

@ -90,8 +90,6 @@
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
<PackageReference Include="DiffPlex" Version="1.6.3" /> <PackageReference Include="DiffPlex" Version="1.6.3" />
<PackageReference Include="Humanizer" Version="2.8.26" /> <PackageReference Include="Humanizer" Version="2.8.26" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" /> <PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="ppy.osu.Framework" Version="2021.309.0" /> <PackageReference Include="ppy.osu.Framework" Version="2021.309.0" />
<PackageReference Include="SharpCompress" Version="0.28.1" /> <PackageReference Include="SharpCompress" Version="0.28.1" />