mirror of
https://github.com/ppy/osu.git
synced 2025-01-26 17:02:57 +08:00
Merge branch 'master' into fix-editor-difficulty-name-update
This commit is contained in:
commit
3604a762d0
76
osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
Normal file
76
osu.Game.Rulesets.Osu/Mods/OsuModNoScope.cs
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
|
{
|
||||||
|
public class OsuModNoScope : Mod, IUpdatableByPlayfield, IApplicableToScoreProcessor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Slightly higher than the cutoff for <see cref="Drawable.IsPresent"/>.
|
||||||
|
/// </summary>
|
||||||
|
private const float min_alpha = 0.0002f;
|
||||||
|
|
||||||
|
private const float transition_duration = 100;
|
||||||
|
|
||||||
|
public override string Name => "No Scope";
|
||||||
|
public override string Acronym => "NS";
|
||||||
|
public override ModType Type => ModType.Fun;
|
||||||
|
public override IconUsage? Icon => FontAwesome.Solid.EyeSlash;
|
||||||
|
public override string Description => "Where's the cursor?";
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
|
||||||
|
private BindableNumber<int> currentCombo;
|
||||||
|
|
||||||
|
private float targetAlpha;
|
||||||
|
|
||||||
|
[SettingSource(
|
||||||
|
"Hidden at combo",
|
||||||
|
"The combo count at which the cursor becomes completely hidden",
|
||||||
|
SettingControlType = typeof(SettingsSlider<int, HiddenComboSlider>)
|
||||||
|
)]
|
||||||
|
public BindableInt HiddenComboCount { get; } = new BindableInt
|
||||||
|
{
|
||||||
|
Default = 10,
|
||||||
|
Value = 10,
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 50,
|
||||||
|
};
|
||||||
|
|
||||||
|
public ScoreRank AdjustRank(ScoreRank rank, double accuracy) => rank;
|
||||||
|
|
||||||
|
public void ApplyToScoreProcessor(ScoreProcessor scoreProcessor)
|
||||||
|
{
|
||||||
|
if (HiddenComboCount.Value == 0) return;
|
||||||
|
|
||||||
|
currentCombo = scoreProcessor.Combo.GetBoundCopy();
|
||||||
|
currentCombo.BindValueChanged(combo =>
|
||||||
|
{
|
||||||
|
targetAlpha = Math.Max(min_alpha, 1 - (float)combo.NewValue / HiddenComboCount.Value);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Update(Playfield playfield)
|
||||||
|
{
|
||||||
|
playfield.Cursor.Alpha = (float)Interpolation.Lerp(playfield.Cursor.Alpha, targetAlpha, Math.Clamp(playfield.Time.Elapsed / transition_duration, 0, 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class HiddenComboSlider : OsuSliderBar<int>
|
||||||
|
{
|
||||||
|
public override LocalisableString TooltipText => Current.Value == 0 ? "always hidden" : base.TooltipText;
|
||||||
|
}
|
||||||
|
}
|
@ -192,6 +192,7 @@ namespace osu.Game.Rulesets.Osu
|
|||||||
new OsuModBarrelRoll(),
|
new OsuModBarrelRoll(),
|
||||||
new OsuModApproachDifferent(),
|
new OsuModApproachDifferent(),
|
||||||
new OsuModMuted(),
|
new OsuModMuted(),
|
||||||
|
new OsuModNoScope(),
|
||||||
};
|
};
|
||||||
|
|
||||||
case ModType.System:
|
case ModType.System:
|
||||||
|
54
osu.Game.Tests/Database/RulesetStoreTests.cs
Normal file
54
osu.Game.Tests/Database/RulesetStoreTests.cs
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Game.Models;
|
||||||
|
using osu.Game.Stores;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Database
|
||||||
|
{
|
||||||
|
public class RulesetStoreTests : RealmTest
|
||||||
|
{
|
||||||
|
[Test]
|
||||||
|
public void TestCreateStore()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
var rulesets = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
|
||||||
|
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||||
|
Assert.AreEqual(4, realmFactory.Context.All<RealmRuleset>().Count());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestCreateStoreTwiceDoesntAddRulesetsAgain()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
var rulesets = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
var rulesets2 = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
|
||||||
|
Assert.AreEqual(4, rulesets.AvailableRulesets.Count());
|
||||||
|
Assert.AreEqual(4, rulesets2.AvailableRulesets.Count());
|
||||||
|
|
||||||
|
Assert.AreEqual(rulesets.AvailableRulesets.First(), rulesets2.AvailableRulesets.First());
|
||||||
|
Assert.AreEqual(4, realmFactory.Context.All<RealmRuleset>().Count());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRetrievedRulesetsAreDetached()
|
||||||
|
{
|
||||||
|
RunTestWithRealm((realmFactory, storage) =>
|
||||||
|
{
|
||||||
|
var rulesets = new RealmRulesetStore(realmFactory, storage);
|
||||||
|
|
||||||
|
Assert.IsTrue((rulesets.AvailableRulesets.First() as RealmRuleset)?.IsManaged == false);
|
||||||
|
Assert.IsTrue((rulesets.GetRuleset(0) as RealmRuleset)?.IsManaged == false);
|
||||||
|
Assert.IsTrue((rulesets.GetRuleset("mania") as RealmRuleset)?.IsManaged == false);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,7 +4,7 @@
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Database;
|
using osu.Game.Overlays.Notifications;
|
||||||
using osu.Game.Tests.Resources;
|
using osu.Game.Tests.Resources;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Navigation
|
namespace osu.Game.Tests.Visual.Navigation
|
||||||
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestImportCreatedNotification()
|
public void TestImportCreatedNotification()
|
||||||
{
|
{
|
||||||
AddUntilStep("Import notification was presented", () => Game.Notifications.ChildrenOfType<ImportProgressNotification>().Count() == 1);
|
AddUntilStep("Import notification was presented", () => Game.Notifications.ChildrenOfType<ProgressCompletionNotification>().Count() == 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.UserInterface
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
{
|
{
|
||||||
@ -19,28 +23,62 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
{
|
{
|
||||||
AddStep("create component", () =>
|
AddStep("create component", () =>
|
||||||
{
|
{
|
||||||
LabelledSliderBar<double> component;
|
FillFlowContainer flow;
|
||||||
|
|
||||||
Child = new Container
|
Child = flow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Width = 500,
|
Width = 500,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Child = component = new LabelledSliderBar<double>
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new LabelledSliderBar<double>
|
||||||
{
|
{
|
||||||
Current = new BindableDouble(5)
|
Current = new BindableDouble(5)
|
||||||
{
|
{
|
||||||
MinValue = 0,
|
MinValue = 0,
|
||||||
MaxValue = 10,
|
MaxValue = 10,
|
||||||
Precision = 1,
|
Precision = 1,
|
||||||
}
|
},
|
||||||
}
|
Label = "a sample component",
|
||||||
|
Description = hasDescription ? "this text describes the component" : string.Empty,
|
||||||
|
},
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
component.Label = "a sample component";
|
foreach (var colour in Enum.GetValues(typeof(OverlayColourScheme)).OfType<OverlayColourScheme>())
|
||||||
component.Description = hasDescription ? "this text describes the component" : string.Empty;
|
{
|
||||||
|
flow.Add(new OverlayColourContainer(colour)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Child = new LabelledSliderBar<double>
|
||||||
|
{
|
||||||
|
Current = new BindableDouble(5)
|
||||||
|
{
|
||||||
|
MinValue = 0,
|
||||||
|
MaxValue = 10,
|
||||||
|
Precision = 1,
|
||||||
|
},
|
||||||
|
Label = "a sample component",
|
||||||
|
Description = hasDescription ? "this text describes the component" : string.Empty,
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OverlayColourContainer : Container
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider;
|
||||||
|
|
||||||
|
public OverlayColourContainer(OverlayColourScheme scheme)
|
||||||
|
{
|
||||||
|
colourProvider = new OverlayColourProvider(scheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,68 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Settings;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
public class TestSceneSettingsCheckbox : OsuTestScene
|
||||||
|
{
|
||||||
|
[TestCase]
|
||||||
|
public void TestCheckbox()
|
||||||
|
{
|
||||||
|
AddStep("create component", () =>
|
||||||
|
{
|
||||||
|
FillFlowContainer flow;
|
||||||
|
|
||||||
|
Child = flow = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Width = 500,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(5),
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = "a sample component",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
foreach (var colour1 in Enum.GetValues(typeof(OverlayColourScheme)).OfType<OverlayColourScheme>())
|
||||||
|
{
|
||||||
|
flow.Add(new OverlayColourContainer(colour1)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Child = new SettingsCheckbox
|
||||||
|
{
|
||||||
|
LabelText = "a sample component",
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class OverlayColourContainer : Container
|
||||||
|
{
|
||||||
|
[Cached]
|
||||||
|
private OverlayColourProvider colourProvider;
|
||||||
|
|
||||||
|
public OverlayColourContainer(OverlayColourScheme scheme)
|
||||||
|
{
|
||||||
|
colourProvider = new OverlayColourProvider(scheme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -176,11 +176,6 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Fired when the user requests to view the resulting import.
|
|
||||||
/// </summary>
|
|
||||||
public Action<IEnumerable<ILive<BeatmapSetInfo>>> PresentImport { set => beatmapModelManager.PostImport = value; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Delete a beatmap difficulty.
|
/// Delete a beatmap difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -338,5 +333,14 @@ namespace osu.Game.Beatmaps
|
|||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
#region Implementation of IPostImports<out BeatmapSetInfo>
|
||||||
|
|
||||||
|
public Action<IEnumerable<ILive<BeatmapSetInfo>>> PostImport
|
||||||
|
{
|
||||||
|
set => beatmapModelManager.PostImport = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ namespace osu.Game.Database
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TModel">The model type.</typeparam>
|
/// <typeparam name="TModel">The model type.</typeparam>
|
||||||
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
|
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
|
||||||
public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>, IModelFileManager<TModel, TFileModel>, IPostImports<TModel>
|
public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>, IModelFileManager<TModel, TFileModel>
|
||||||
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
|
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
|
||||||
where TFileModel : class, INamedFileInfo, new()
|
where TFileModel : class, INamedFileInfo, new()
|
||||||
{
|
{
|
||||||
|
@ -13,7 +13,7 @@ namespace osu.Game.Database
|
|||||||
/// A class which handles importing of associated models to the game store.
|
/// A class which handles importing of associated models to the game store.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="TModel">The model type.</typeparam>
|
/// <typeparam name="TModel">The model type.</typeparam>
|
||||||
public interface IModelImporter<TModel> : IPostNotifications
|
public interface IModelImporter<TModel> : IPostNotifications, IPostImports<TModel>
|
||||||
where TModel : class
|
where TModel : class
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -5,7 +5,6 @@ using System;
|
|||||||
using System.Threading;
|
using System.Threading;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Development;
|
using osu.Framework.Development;
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Statistics;
|
using osu.Framework.Statistics;
|
||||||
@ -18,7 +17,7 @@ namespace osu.Game.Database
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage.
|
/// A factory which provides both the main (update thread bound) realm context and creates contexts for async usage.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class RealmContextFactory : Component, IRealmFactory
|
public class RealmContextFactory : IDisposable, IRealmFactory
|
||||||
{
|
{
|
||||||
private readonly Storage storage;
|
private readonly Storage storage;
|
||||||
|
|
||||||
@ -79,10 +78,11 @@ namespace osu.Game.Database
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public bool Compact() => Realm.Compact(getConfiguration());
|
public bool Compact() => Realm.Compact(getConfiguration());
|
||||||
|
|
||||||
protected override void Update()
|
/// <summary>
|
||||||
|
/// Perform a blocking refresh on the main realm context.
|
||||||
|
/// </summary>
|
||||||
|
public void Refresh()
|
||||||
{
|
{
|
||||||
base.Update();
|
|
||||||
|
|
||||||
lock (contextLock)
|
lock (contextLock)
|
||||||
{
|
{
|
||||||
if (context?.Refresh() == true)
|
if (context?.Refresh() == true)
|
||||||
@ -92,7 +92,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
public Realm CreateContext()
|
public Realm CreateContext()
|
||||||
{
|
{
|
||||||
if (IsDisposed)
|
if (isDisposed)
|
||||||
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -132,7 +132,7 @@ namespace osu.Game.Database
|
|||||||
/// <returns>An <see cref="IDisposable"/> which should be disposed to end the blocking section.</returns>
|
/// <returns>An <see cref="IDisposable"/> which should be disposed to end the blocking section.</returns>
|
||||||
public IDisposable BlockAllOperations()
|
public IDisposable BlockAllOperations()
|
||||||
{
|
{
|
||||||
if (IsDisposed)
|
if (isDisposed)
|
||||||
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
throw new ObjectDisposedException(nameof(RealmContextFactory));
|
||||||
|
|
||||||
if (!ThreadSafety.IsUpdateThread)
|
if (!ThreadSafety.IsUpdateThread)
|
||||||
@ -176,21 +176,23 @@ namespace osu.Game.Database
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
private bool isDisposed;
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
{
|
{
|
||||||
lock (contextLock)
|
lock (contextLock)
|
||||||
{
|
{
|
||||||
context?.Dispose();
|
context?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!IsDisposed)
|
if (!isDisposed)
|
||||||
{
|
{
|
||||||
// intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal.
|
// intentionally block context creation indefinitely. this ensures that nothing can start consuming a new context after disposal.
|
||||||
contextCreationLock.Wait();
|
contextCreationLock.Wait();
|
||||||
contextCreationLock.Dispose();
|
contextCreationLock.Dispose();
|
||||||
}
|
|
||||||
|
|
||||||
base.Dispose(isDisposing);
|
isDisposed = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -12,31 +13,39 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
public class Nub : CircularContainer, IHasCurrentValue<bool>, IHasAccentColour
|
public class Nub : CompositeDrawable, IHasCurrentValue<bool>, IHasAccentColour
|
||||||
{
|
{
|
||||||
public const float COLLAPSED_SIZE = 20;
|
public const float HEIGHT = 15;
|
||||||
public const float EXPANDED_SIZE = 40;
|
|
||||||
|
public const float EXPANDED_SIZE = 50;
|
||||||
|
|
||||||
private const float border_width = 3;
|
private const float border_width = 3;
|
||||||
|
|
||||||
private const double animate_in_duration = 150;
|
private const double animate_in_duration = 200;
|
||||||
private const double animate_out_duration = 500;
|
private const double animate_out_duration = 500;
|
||||||
|
|
||||||
|
private readonly Box fill;
|
||||||
|
private readonly Container main;
|
||||||
|
|
||||||
public Nub()
|
public Nub()
|
||||||
{
|
{
|
||||||
Box fill;
|
Size = new Vector2(EXPANDED_SIZE, HEIGHT);
|
||||||
|
|
||||||
Size = new Vector2(COLLAPSED_SIZE, 12);
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
BorderColour = Color4.White;
|
main = new CircularContainer
|
||||||
BorderThickness = border_width;
|
{
|
||||||
|
BorderColour = Color4.White,
|
||||||
Masking = true;
|
BorderThickness = border_width,
|
||||||
|
Masking = true,
|
||||||
Children = new[]
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
fill = new Box
|
fill = new Box
|
||||||
{
|
{
|
||||||
@ -44,31 +53,34 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Alpha = 0,
|
Alpha = 0,
|
||||||
AlwaysPresent = true,
|
AlwaysPresent = true,
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
|
},
|
||||||
Current.ValueChanged += filled =>
|
|
||||||
{
|
|
||||||
fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint);
|
|
||||||
this.TransformTo(nameof(BorderThickness), filled.NewValue ? 8.5f : border_width, 200, Easing.OutQuint);
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(OsuColour colours)
|
private void load([CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
|
||||||
{
|
{
|
||||||
AccentColour = colours.Pink;
|
AccentColour = colourProvider?.Highlight1 ?? colours.Pink;
|
||||||
GlowingAccentColour = colours.PinkLighter;
|
GlowingAccentColour = colourProvider?.Highlight1.Lighten(0.2f) ?? colours.PinkLighter;
|
||||||
GlowColour = colours.PinkDarker;
|
GlowColour = colourProvider?.Highlight1 ?? colours.PinkLighter;
|
||||||
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
main.EdgeEffect = new EdgeEffectParameters
|
||||||
{
|
{
|
||||||
Colour = GlowColour.Opacity(0),
|
Colour = GlowColour.Opacity(0),
|
||||||
Type = EdgeEffectType.Glow,
|
Type = EdgeEffectType.Glow,
|
||||||
Radius = 10,
|
Radius = 8,
|
||||||
Roundness = 8,
|
Roundness = 5,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Current.BindValueChanged(onCurrentValueChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
private bool glowing;
|
private bool glowing;
|
||||||
|
|
||||||
public bool Glowing
|
public bool Glowing
|
||||||
@ -80,28 +92,17 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
if (value)
|
if (value)
|
||||||
{
|
{
|
||||||
this.FadeColour(GlowingAccentColour, animate_in_duration, Easing.OutQuint);
|
main.FadeColour(GlowingAccentColour, animate_in_duration, Easing.OutQuint);
|
||||||
FadeEdgeEffectTo(1, animate_in_duration, Easing.OutQuint);
|
main.FadeEdgeEffectTo(0.2f, animate_in_duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
FadeEdgeEffectTo(0, animate_out_duration);
|
main.FadeEdgeEffectTo(0, animate_out_duration, Easing.OutQuint);
|
||||||
this.FadeColour(AccentColour, animate_out_duration);
|
main.FadeColour(AccentColour, animate_out_duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool Expanded
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value)
|
|
||||||
this.ResizeTo(new Vector2(EXPANDED_SIZE, 12), animate_in_duration, Easing.OutQuint);
|
|
||||||
else
|
|
||||||
this.ResizeTo(new Vector2(COLLAPSED_SIZE, 12), animate_out_duration, Easing.OutQuint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly Bindable<bool> current = new Bindable<bool>();
|
private readonly Bindable<bool> current = new Bindable<bool>();
|
||||||
|
|
||||||
public Bindable<bool> Current
|
public Bindable<bool> Current
|
||||||
@ -126,7 +127,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
accentColour = value;
|
accentColour = value;
|
||||||
if (!Glowing)
|
if (!Glowing)
|
||||||
Colour = value;
|
main.Colour = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -139,7 +140,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
glowingAccentColour = value;
|
glowingAccentColour = value;
|
||||||
if (Glowing)
|
if (Glowing)
|
||||||
Colour = value;
|
main.Colour = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -152,10 +153,22 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
glowColour = value;
|
glowColour = value;
|
||||||
|
|
||||||
var effect = EdgeEffect;
|
var effect = main.EdgeEffect;
|
||||||
effect.Colour = Glowing ? value : value.Opacity(0);
|
effect.Colour = Glowing ? value : value.Opacity(0);
|
||||||
EdgeEffect = effect;
|
main.EdgeEffect = effect;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onCurrentValueChanged(ValueChangedEvent<bool> filled)
|
||||||
|
{
|
||||||
|
fill.FadeTo(filled.NewValue ? 1 : 0, 200, Easing.OutQuint);
|
||||||
|
|
||||||
|
if (filled.NewValue)
|
||||||
|
main.ResizeWidthTo(1, animate_in_duration, Easing.OutElasticHalf);
|
||||||
|
else
|
||||||
|
main.ResizeWidthTo(0.9f, animate_out_duration, Easing.OutElastic);
|
||||||
|
|
||||||
|
main.TransformTo(nameof(BorderThickness), filled.NewValue ? 8.5f : border_width, 200, Easing.OutQuint);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,16 +9,11 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osuTK.Graphics;
|
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
public class OsuCheckbox : Checkbox
|
public class OsuCheckbox : Checkbox
|
||||||
{
|
{
|
||||||
public Color4 CheckedColor { get; set; } = Color4.Cyan;
|
|
||||||
public Color4 UncheckedColor { get; set; } = Color4.White;
|
|
||||||
public int FadeDuration { get; set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether to play sounds when the state changes as a result of user interaction.
|
/// Whether to play sounds when the state changes as a result of user interaction.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -104,14 +99,12 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
Nub.Glowing = true;
|
Nub.Glowing = true;
|
||||||
Nub.Expanded = true;
|
|
||||||
return base.OnHover(e);
|
return base.OnHover(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
Nub.Glowing = false;
|
Nub.Glowing = false;
|
||||||
Nub.Expanded = false;
|
|
||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,11 +3,13 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
@ -16,6 +18,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
@ -52,35 +55,64 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
accentColour = value;
|
accentColour = value;
|
||||||
leftBox.Colour = value;
|
leftBox.Colour = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Colour4 backgroundColour;
|
||||||
|
|
||||||
|
public Color4 BackgroundColour
|
||||||
|
{
|
||||||
|
get => backgroundColour;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
backgroundColour = value;
|
||||||
rightBox.Colour = value;
|
rightBox.Colour = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public OsuSliderBar()
|
public OsuSliderBar()
|
||||||
{
|
{
|
||||||
Height = 12;
|
Height = Nub.HEIGHT;
|
||||||
RangePadding = 20;
|
RangePadding = Nub.EXPANDED_SIZE / 2;
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Padding = new MarginPadding { Horizontal = 2 },
|
||||||
|
Child = new CircularContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 5f,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
leftBox = new Box
|
leftBox = new Box
|
||||||
{
|
{
|
||||||
Height = 2,
|
Height = 5,
|
||||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||||
Position = new Vector2(2, 0),
|
|
||||||
RelativeSizeAxes = Axes.None,
|
RelativeSizeAxes = Axes.None,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
},
|
},
|
||||||
rightBox = new Box
|
rightBox = new Box
|
||||||
{
|
{
|
||||||
Height = 2,
|
Height = 5,
|
||||||
EdgeSmoothness = new Vector2(0, 0.5f),
|
EdgeSmoothness = new Vector2(0, 0.5f),
|
||||||
Position = new Vector2(-2, 0),
|
|
||||||
RelativeSizeAxes = Axes.None,
|
RelativeSizeAxes = Axes.None,
|
||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreRight,
|
Origin = Anchor.CentreRight,
|
||||||
Alpha = 0.5f,
|
Alpha = 0.5f,
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
nubContainer = new Container
|
nubContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
@ -88,7 +120,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
RelativePositionAxes = Axes.X,
|
RelativePositionAxes = Axes.X,
|
||||||
Expanded = true,
|
Current = { Value = true }
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
new HoverClickSounds()
|
new HoverClickSounds()
|
||||||
@ -97,11 +129,12 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; };
|
Current.DisabledChanged += disabled => { Alpha = disabled ? 0.3f : 1; };
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(AudioManager audio, OsuColour colours)
|
private void load(AudioManager audio, [CanBeNull] OverlayColourProvider colourProvider, OsuColour colours)
|
||||||
{
|
{
|
||||||
sample = audio.Samples.Get(@"UI/notch-tick");
|
sample = audio.Samples.Get(@"UI/notch-tick");
|
||||||
AccentColour = colours.Pink;
|
AccentColour = colourProvider?.Highlight1 ?? colours.Pink;
|
||||||
|
BackgroundColour = colourProvider?.Background5 ?? colours.Pink.Opacity(0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -119,26 +152,25 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
Nub.Glowing = true;
|
updateGlow();
|
||||||
return base.OnHover(e);
|
return base.OnHover(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
Nub.Glowing = false;
|
updateGlow();
|
||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnMouseDown(MouseDownEvent e)
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
{
|
{
|
||||||
Nub.Current.Value = true;
|
updateGlow();
|
||||||
return base.OnMouseDown(e);
|
base.OnDragEnd(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnMouseUp(MouseUpEvent e)
|
private void updateGlow()
|
||||||
{
|
{
|
||||||
Nub.Current.Value = false;
|
Nub.Glowing = IsHovered || IsDragged;
|
||||||
base.OnMouseUp(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnUserChange(T value)
|
protected override void OnUserChange(T value)
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterfaceV2
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
{
|
{
|
||||||
@ -23,10 +25,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(OsuColour colours)
|
private void load([CanBeNull] OverlayColourProvider overlayColourProvider, OsuColour colours)
|
||||||
{
|
{
|
||||||
BackgroundColour = colours.Blue3;
|
BackgroundColour = overlayColourProvider?.Highlight1 ?? colours.Blue3;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
|
@ -374,7 +374,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
UserJoined?.Invoke(user);
|
UserJoined?.Invoke(user);
|
||||||
RoomUpdated?.Invoke();
|
RoomUpdated?.Invoke();
|
||||||
}, false);
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) =>
|
Task IMultiplayerClient.UserLeft(MultiplayerRoomUser user) =>
|
||||||
|
@ -643,7 +643,7 @@ namespace osu.Game
|
|||||||
SkinManager.PostNotification = n => Notifications.Post(n);
|
SkinManager.PostNotification = n => Notifications.Post(n);
|
||||||
|
|
||||||
BeatmapManager.PostNotification = n => Notifications.Post(n);
|
BeatmapManager.PostNotification = n => Notifications.Post(n);
|
||||||
BeatmapManager.PresentImport = items => PresentBeatmap(items.First().Value);
|
BeatmapManager.PostImport = items => PresentBeatmap(items.First().Value);
|
||||||
|
|
||||||
ScoreManager.PostNotification = n => Notifications.Post(n);
|
ScoreManager.PostNotification = n => Notifications.Post(n);
|
||||||
ScoreManager.PostImport = items => PresentScore(items.First().Value);
|
ScoreManager.PostImport = items => PresentScore(items.First().Value);
|
||||||
|
@ -187,8 +187,6 @@ namespace osu.Game
|
|||||||
|
|
||||||
dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client"));
|
dependencies.Cache(realmFactory = new RealmContextFactory(Storage, "client"));
|
||||||
|
|
||||||
AddInternal(realmFactory);
|
|
||||||
|
|
||||||
dependencies.CacheAs(Storage);
|
dependencies.CacheAs(Storage);
|
||||||
|
|
||||||
var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures")));
|
var largeStore = new LargeTextureStore(Host.CreateTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures")));
|
||||||
@ -529,6 +527,7 @@ namespace osu.Game
|
|||||||
LocalConfig?.Dispose();
|
LocalConfig?.Dispose();
|
||||||
|
|
||||||
contextFactory?.FlushConnections();
|
contextFactory?.FlushConnections();
|
||||||
|
realmFactory?.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Settings
|
|||||||
{
|
{
|
||||||
protected override Drawable CreateControl() => new TSlider
|
protected override Drawable CreateControl() => new TSlider
|
||||||
{
|
{
|
||||||
Margin = new MarginPadding { Top = 5, Bottom = 5 },
|
Margin = new MarginPadding { Vertical = 10 },
|
||||||
RelativeSizeAxes = Axes.X
|
RelativeSizeAxes = Axes.X
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ using osu.Game.Rulesets.Scoring;
|
|||||||
|
|
||||||
namespace osu.Game.Scoring
|
namespace osu.Game.Scoring
|
||||||
{
|
{
|
||||||
public class ScoreManager : IModelManager<ScoreInfo>, IModelFileManager<ScoreInfo, ScoreFileInfo>, IModelDownloader<ScoreInfo>, ICanAcceptFiles, IPostImports<ScoreInfo>
|
public class ScoreManager : IModelManager<ScoreInfo>, IModelFileManager<ScoreInfo, ScoreFileInfo>, IModelDownloader<ScoreInfo>, ICanAcceptFiles
|
||||||
{
|
{
|
||||||
private readonly Scheduler scheduler;
|
private readonly Scheduler scheduler;
|
||||||
private readonly Func<BeatmapDifficultyCache> difficulties;
|
private readonly Func<BeatmapDifficultyCache> difficulties;
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
{
|
{
|
||||||
Nub.AccentColour = colours.Yellow;
|
Nub.AccentColour = colours.Yellow;
|
||||||
Nub.GlowingAccentColour = colours.YellowLighter;
|
Nub.GlowingAccentColour = colours.YellowLighter;
|
||||||
Nub.GlowColour = colours.YellowDarker;
|
Nub.GlowColour = colours.YellowDark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Screens.Play.PlayerSettings
|
|||||||
AccentColour = colours.Yellow;
|
AccentColour = colours.Yellow;
|
||||||
Nub.AccentColour = colours.Yellow;
|
Nub.AccentColour = colours.Yellow;
|
||||||
Nub.GlowingAccentColour = colours.YellowLighter;
|
Nub.GlowingAccentColour = colours.YellowLighter;
|
||||||
Nub.GlowColour = colours.YellowDarker;
|
Nub.GlowColour = colours.YellowDark;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
263
osu.Game/Stores/RealmRulesetStore.cs
Normal file
263
osu.Game/Stores/RealmRulesetStore.cs
Normal file
@ -0,0 +1,263 @@
|
|||||||
|
// 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.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Reflection;
|
||||||
|
using osu.Framework;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Platform;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Models;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
|
namespace osu.Game.Stores
|
||||||
|
{
|
||||||
|
public class RealmRulesetStore : IDisposable
|
||||||
|
{
|
||||||
|
private readonly RealmContextFactory realmFactory;
|
||||||
|
|
||||||
|
private const string ruleset_library_prefix = @"osu.Game.Rulesets";
|
||||||
|
|
||||||
|
private readonly Dictionary<Assembly, Type> loadedAssemblies = new Dictionary<Assembly, Type>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// All available rulesets.
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<IRulesetInfo> AvailableRulesets => availableRulesets;
|
||||||
|
|
||||||
|
private readonly List<IRulesetInfo> availableRulesets = new List<IRulesetInfo>();
|
||||||
|
|
||||||
|
public RealmRulesetStore(RealmContextFactory realmFactory, Storage? storage = null)
|
||||||
|
{
|
||||||
|
this.realmFactory = realmFactory;
|
||||||
|
|
||||||
|
// On android in release configuration assemblies are loaded from the apk directly into memory.
|
||||||
|
// We cannot read assemblies from cwd, so should check loaded assemblies instead.
|
||||||
|
loadFromAppDomain();
|
||||||
|
|
||||||
|
// This null check prevents Android from attempting to load the rulesets from disk,
|
||||||
|
// as the underlying path "AppContext.BaseDirectory", despite being non-nullable, it returns null on android.
|
||||||
|
// See https://github.com/xamarin/xamarin-android/issues/3489.
|
||||||
|
if (RuntimeInfo.StartupDirectory != null)
|
||||||
|
loadFromDisk();
|
||||||
|
|
||||||
|
// the event handler contains code for resolving dependency on the game assembly for rulesets located outside the base game directory.
|
||||||
|
// It needs to be attached to the assembly lookup event before the actual call to loadUserRulesets() else rulesets located out of the base game directory will fail
|
||||||
|
// to load as unable to locate the game core assembly.
|
||||||
|
AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetDependencyAssembly;
|
||||||
|
|
||||||
|
var rulesetStorage = storage?.GetStorageForDirectory(@"rulesets");
|
||||||
|
if (rulesetStorage != null)
|
||||||
|
loadUserRulesets(rulesetStorage);
|
||||||
|
|
||||||
|
addMissingRulesets();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a ruleset using a known ID.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="id">The ruleset's internal ID.</param>
|
||||||
|
/// <returns>A ruleset, if available, else null.</returns>
|
||||||
|
public IRulesetInfo? GetRuleset(int id) => AvailableRulesets.FirstOrDefault(r => r.OnlineID == id);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a ruleset using a known short name.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="shortName">The ruleset's short name.</param>
|
||||||
|
/// <returns>A ruleset, if available, else null.</returns>
|
||||||
|
public IRulesetInfo? GetRuleset(string shortName) => AvailableRulesets.FirstOrDefault(r => r.ShortName == shortName);
|
||||||
|
|
||||||
|
private Assembly? resolveRulesetDependencyAssembly(object? sender, ResolveEventArgs args)
|
||||||
|
{
|
||||||
|
var asm = new AssemblyName(args.Name);
|
||||||
|
|
||||||
|
// the requesting assembly may be located out of the executable's base directory, thus requiring manual resolving of its dependencies.
|
||||||
|
// this attempts resolving the ruleset dependencies on game core and framework assemblies by returning assemblies with the same assembly name
|
||||||
|
// already loaded in the AppDomain.
|
||||||
|
var domainAssembly = AppDomain.CurrentDomain.GetAssemblies()
|
||||||
|
// Given name is always going to be equally-or-more qualified than the assembly name.
|
||||||
|
.Where(a =>
|
||||||
|
{
|
||||||
|
string? name = a.GetName().Name;
|
||||||
|
if (name == null)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return args.Name.Contains(name, StringComparison.Ordinal);
|
||||||
|
})
|
||||||
|
// Pick the greatest assembly version.
|
||||||
|
.OrderByDescending(a => a.GetName().Version)
|
||||||
|
.FirstOrDefault();
|
||||||
|
|
||||||
|
if (domainAssembly != null)
|
||||||
|
return domainAssembly;
|
||||||
|
|
||||||
|
return loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == asm.FullName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMissingRulesets()
|
||||||
|
{
|
||||||
|
realmFactory.Context.Write(realm =>
|
||||||
|
{
|
||||||
|
var rulesets = realm.All<RealmRuleset>();
|
||||||
|
|
||||||
|
List<Ruleset> instances = loadedAssemblies.Values
|
||||||
|
.Select(r => Activator.CreateInstance(r) as Ruleset)
|
||||||
|
.Where(r => r != null)
|
||||||
|
.Select(r => r.AsNonNull())
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
// add all legacy rulesets first to ensure they have exclusive choice of primary key.
|
||||||
|
foreach (var r in instances.Where(r => r is ILegacyRuleset))
|
||||||
|
{
|
||||||
|
if (realm.All<RealmRuleset>().FirstOrDefault(rr => rr.OnlineID == r.RulesetInfo.ID) == null)
|
||||||
|
realm.Add(new RealmRuleset(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.ID));
|
||||||
|
}
|
||||||
|
|
||||||
|
// add any other rulesets which have assemblies present but are not yet in the database.
|
||||||
|
foreach (var r in instances.Where(r => !(r is ILegacyRuleset)))
|
||||||
|
{
|
||||||
|
if (rulesets.FirstOrDefault(ri => ri.InstantiationInfo.Equals(r.RulesetInfo.InstantiationInfo, StringComparison.Ordinal)) == null)
|
||||||
|
{
|
||||||
|
var existingSameShortName = rulesets.FirstOrDefault(ri => ri.ShortName == r.RulesetInfo.ShortName);
|
||||||
|
|
||||||
|
if (existingSameShortName != null)
|
||||||
|
{
|
||||||
|
// even if a matching InstantiationInfo was not found, there may be an existing ruleset with the same ShortName.
|
||||||
|
// this generally means the user or ruleset provider has renamed their dll but the underlying ruleset is *likely* the same one.
|
||||||
|
// in such cases, update the instantiation info of the existing entry to point to the new one.
|
||||||
|
existingSameShortName.InstantiationInfo = r.RulesetInfo.InstantiationInfo;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
realm.Add(new RealmRuleset(r.RulesetInfo.ShortName, r.RulesetInfo.Name, r.RulesetInfo.InstantiationInfo, r.RulesetInfo.ID));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<RealmRuleset> detachedRulesets = new List<RealmRuleset>();
|
||||||
|
|
||||||
|
// perform a consistency check and detach final rulesets from realm for cross-thread runtime usage.
|
||||||
|
foreach (var r in rulesets)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var type = Type.GetType(r.InstantiationInfo);
|
||||||
|
|
||||||
|
if (type == null)
|
||||||
|
throw new InvalidOperationException(@"Type resolution failure.");
|
||||||
|
|
||||||
|
var rInstance = (Activator.CreateInstance(type) as Ruleset)?.RulesetInfo;
|
||||||
|
|
||||||
|
if (rInstance == null)
|
||||||
|
throw new InvalidOperationException(@"Instantiation failure.");
|
||||||
|
|
||||||
|
r.Name = rInstance.Name;
|
||||||
|
r.ShortName = rInstance.ShortName;
|
||||||
|
r.InstantiationInfo = rInstance.InstantiationInfo;
|
||||||
|
r.Available = true;
|
||||||
|
|
||||||
|
detachedRulesets.Add(r.Clone());
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
r.Available = false;
|
||||||
|
Logger.Log($"Could not load ruleset {r}: {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
availableRulesets.AddRange(detachedRulesets);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFromAppDomain()
|
||||||
|
{
|
||||||
|
foreach (var ruleset in AppDomain.CurrentDomain.GetAssemblies())
|
||||||
|
{
|
||||||
|
string? rulesetName = ruleset.GetName().Name;
|
||||||
|
|
||||||
|
if (rulesetName == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!rulesetName.StartsWith(ruleset_library_prefix, StringComparison.InvariantCultureIgnoreCase) || rulesetName.Contains(@"Tests"))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
addRuleset(ruleset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadUserRulesets(Storage rulesetStorage)
|
||||||
|
{
|
||||||
|
var rulesets = rulesetStorage.GetFiles(@".", @$"{ruleset_library_prefix}.*.dll");
|
||||||
|
|
||||||
|
foreach (var ruleset in rulesets.Where(f => !f.Contains(@"Tests")))
|
||||||
|
loadRulesetFromFile(rulesetStorage.GetFullPath(ruleset));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadFromDisk()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var files = Directory.GetFiles(RuntimeInfo.StartupDirectory, @$"{ruleset_library_prefix}.*.dll");
|
||||||
|
|
||||||
|
foreach (string file in files.Where(f => !Path.GetFileName(f).Contains("Tests")))
|
||||||
|
loadRulesetFromFile(file);
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, $"Could not load rulesets from directory {RuntimeInfo.StartupDirectory}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadRulesetFromFile(string file)
|
||||||
|
{
|
||||||
|
var filename = Path.GetFileNameWithoutExtension(file);
|
||||||
|
|
||||||
|
if (loadedAssemblies.Values.Any(t => Path.GetFileNameWithoutExtension(t.Assembly.Location) == filename))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
addRuleset(Assembly.LoadFrom(file));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, $"Failed to load ruleset {filename}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRuleset(Assembly assembly)
|
||||||
|
{
|
||||||
|
if (loadedAssemblies.ContainsKey(assembly))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// the same assembly may be loaded twice in the same AppDomain (currently a thing in certain Rider versions https://youtrack.jetbrains.com/issue/RIDER-48799).
|
||||||
|
// as a failsafe, also compare by FullName.
|
||||||
|
if (loadedAssemblies.Any(a => a.Key.FullName == assembly.FullName))
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
loadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset)));
|
||||||
|
}
|
||||||
|
catch (Exception e)
|
||||||
|
{
|
||||||
|
Logger.Error(e, $"Failed to add ruleset {assembly}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
Dispose(true);
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected virtual void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetDependencyAssembly;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,7 +53,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
public MultiplayerRoomUser AddUser(User user, bool markAsPlaying = false)
|
public MultiplayerRoomUser AddUser(User user, bool markAsPlaying = false)
|
||||||
{
|
{
|
||||||
var roomUser = new MultiplayerRoomUser(user.Id) { User = user };
|
var roomUser = new MultiplayerRoomUser(user.Id) { User = user };
|
||||||
((IMultiplayerClient)this).UserJoined(roomUser);
|
|
||||||
|
addUser(roomUser);
|
||||||
|
|
||||||
if (markAsPlaying)
|
if (markAsPlaying)
|
||||||
PlayingUserIds.Add(user.Id);
|
PlayingUserIds.Add(user.Id);
|
||||||
@ -61,7 +62,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
return roomUser;
|
return roomUser;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddNullUser() => ((IMultiplayerClient)this).UserJoined(new MultiplayerRoomUser(TestUserLookupCache.NULL_USER_ID));
|
public void AddNullUser() => addUser(new MultiplayerRoomUser(TestUserLookupCache.NULL_USER_ID));
|
||||||
|
|
||||||
|
private void addUser(MultiplayerRoomUser user)
|
||||||
|
{
|
||||||
|
((IMultiplayerClient)this).UserJoined(user).Wait();
|
||||||
|
|
||||||
|
// We want the user to be immediately available for testing, so force a scheduler update to run the update-bound continuation.
|
||||||
|
Scheduler.Update();
|
||||||
|
}
|
||||||
|
|
||||||
public void RemoveUser(User user)
|
public void RemoveUser(User user)
|
||||||
{
|
{
|
||||||
|
Loading…
Reference in New Issue
Block a user