1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-14 16:37:26 +08:00

Merge branch 'master' into path-type-menuitem

This commit is contained in:
Dean Herbert 2019-12-11 16:43:55 +09:00 committed by GitHub
commit cda6757f52
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
53 changed files with 481 additions and 299 deletions

View File

@ -54,6 +54,6 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2019.1205.0" /> <PackageReference Include="ppy.osu.Framework.Android" Version="2019.1210.1" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Catch.MathUtils
{ {
private const double int_to_real = 1.0 / (int.MaxValue + 1.0); private const double int_to_real = 1.0 / (int.MaxValue + 1.0);
private const uint int_mask = 0x7FFFFFFF; private const uint int_mask = 0x7FFFFFFF;
private const uint y = 842502087; private const uint y_initial = 842502087;
private const uint z = 3579807591; private const uint z_initial = 3579807591;
private const uint w = 273326509; private const uint w_initial = 273326509;
private uint _x, _y = y, _z = z, _w = w; private uint x, y = y_initial, z = z_initial, w = w_initial;
public FastRandom(int seed) public FastRandom(int seed)
{ {
_x = (uint)seed; x = (uint)seed;
} }
public FastRandom() public FastRandom()
@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Catch.MathUtils
/// <returns>The random value.</returns> /// <returns>The random value.</returns>
public uint NextUInt() public uint NextUInt()
{ {
uint t = _x ^ (_x << 11); uint t = x ^ (x << 11);
_x = _y; x = y;
_y = _z; y = z;
_z = _w; z = w;
return _w = _w ^ (_w >> 19) ^ t ^ (t >> 8); return w = w ^ (w >> 19) ^ t ^ (t >> 8);
} }
/// <summary> /// <summary>

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Catch.UI
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle") InternalChild = new SkinnableSprite("Gameplay/catch/fruit-catcher-idle", confineMode: ConfineMode.ScaleDownToFit)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,

View File

@ -128,8 +128,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
slider.Path.ControlPoints.Remove(c); slider.Path.ControlPoints.Remove(c);
} }
// If there are 0 remaining control points, treat the slider as being deleted // If there are 0 or 1 remaining control points, the slider is in a degenerate (single point) form and should be deleted
if (slider.Path.ControlPoints.Count == 0) if (slider.Path.ControlPoints.Count <= 1)
{ {
placementHandler?.Delete(slider); placementHandler?.Delete(slider);
return true; return true;
@ -163,7 +163,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
return new MenuItem[] return new MenuItem[]
{ {
new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints)}", MenuItemType.Destructive, () => deleteSelected()), new OsuMenuItem($"Delete {"control point".ToQuantity(selectedPoints, selectedPoints > 1 ? ShowQuantityAs.Numeric : ShowQuantityAs.None)}", MenuItemType.Destructive, () => deleteSelected()),
new OsuMenuItem("Type") new OsuMenuItem("Type")
{ {
Items = new[] Items = new[]

View File

@ -1,7 +1,6 @@
// 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.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input; using osu.Framework.Input;
@ -191,15 +190,5 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
Initial, Initial,
Body, Body,
} }
private class Segment
{
public readonly List<Vector2> ControlPoints = new List<Vector2>();
public Segment(Vector2 offset)
{
ControlPoints.Add(offset);
}
}
} }
} }

View File

@ -104,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
int insertionIndex = 0; int insertionIndex = 0;
float minDistance = float.MaxValue; float minDistance = float.MaxValue;
for (int i = 0; i < HitObject.Path.ControlPoints.Count - 2; i++) for (int i = 0; i < HitObject.Path.ControlPoints.Count - 1; i++)
{ {
float dist = new Line(HitObject.Path.ControlPoints[i].Position.Value, HitObject.Path.ControlPoints[i + 1].Position.Value).DistanceToPoint(position); float dist = new Line(HitObject.Path.ControlPoints[i].Position.Value, HitObject.Path.ControlPoints[i + 1].Position.Value).DistanceToPoint(position);

View File

@ -3,15 +3,14 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osuTK; using osuTK;
namespace osu.Game.Rulesets.Osu.Skinning namespace osu.Game.Rulesets.Osu.Skinning
{ {
public class LegacyCursor : CompositeDrawable public class LegacyCursor : OsuCursorSprite
{ {
private NonPlayfieldSprite cursor;
private bool spin; private bool spin;
public LegacyCursor() public LegacyCursor()
@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
{ {
spin = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorRotate)?.Value ?? true; spin = skin.GetConfig<OsuSkinConfiguration, bool>(OsuSkinConfiguration.CursorRotate)?.Value ?? true;
InternalChildren = new Drawable[] InternalChildren = new[]
{ {
new NonPlayfieldSprite new NonPlayfieldSprite
{ {
@ -35,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
}, },
cursor = new NonPlayfieldSprite ExpandTarget = new NonPlayfieldSprite
{ {
Texture = skin.GetTexture("cursor"), Texture = skin.GetTexture("cursor"),
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -47,7 +46,7 @@ namespace osu.Game.Rulesets.Osu.Skinning
protected override void LoadComplete() protected override void LoadComplete()
{ {
if (spin) if (spin)
cursor.Spin(10000, RotationDirection.Clockwise); ExpandTarget.Spin(10000, RotationDirection.Clockwise);
} }
} }
} }

View File

@ -20,7 +20,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private bool cursorExpand; private bool cursorExpand;
private Container expandTarget; private SkinnableDrawable cursorSprite;
private Drawable expandTarget => (cursorSprite.Drawable as OsuCursorSprite)?.ExpandTarget ?? cursorSprite;
public OsuCursor() public OsuCursor()
{ {
@ -37,12 +39,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
InternalChild = expandTarget = new Container InternalChild = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Child = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling) Child = cursorSprite = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.Cursor), _ => new DefaultCursor(), confineMode: ConfineMode.NoScaling)
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -62,7 +64,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad); public void Contract() => expandTarget.ScaleTo(released_scale, 100, Easing.OutQuad);
private class DefaultCursor : CompositeDrawable private class DefaultCursor : OsuCursorSprite
{ {
public DefaultCursor() public DefaultCursor()
{ {
@ -71,10 +73,12 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
Anchor = Anchor.Centre; Anchor = Anchor.Centre;
Origin = Anchor.Centre; Origin = Anchor.Centre;
InternalChildren = new Drawable[] InternalChildren = new[]
{ {
new CircularContainer ExpandTarget = new CircularContainer
{ {
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Masking = true, Masking = true,
BorderThickness = size / 6, BorderThickness = size / 6,

View File

@ -0,0 +1,17 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Rulesets.Osu.UI.Cursor
{
public abstract class OsuCursorSprite : CompositeDrawable
{
/// <summary>
/// The an optional piece of the cursor to expand when in a clicked state.
/// If null, the whole cursor will be affected by expansion.
/// </summary>
public Drawable ExpandTarget { get; protected set; }
}
}

View File

@ -57,8 +57,8 @@ namespace osu.Game.Tests.Visual.Gameplay
beforeLoadAction?.Invoke(); beforeLoadAction?.Invoke();
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
foreach (var mod in Mods.Value.OfType<IApplicableToClock>()) foreach (var mod in Mods.Value.OfType<IApplicableToTrack>())
mod.ApplyToClock(Beatmap.Value.Track); mod.ApplyToTrack(Beatmap.Value.Track);
InputManager.Child = container = new TestPlayerLoaderContainer( InputManager.Child = container = new TestPlayerLoaderContainer(
loader = new TestPlayerLoader(() => loader = new TestPlayerLoader(() =>

View File

@ -0,0 +1,65 @@
// 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 NUnit.Framework;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Configuration;
using osuTK;
namespace osu.Game.Tests.Visual.Settings
{
[TestFixture]
public class TestSceneSettingsSource : OsuTestScene
{
public TestSceneSettingsSource()
{
Children = new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(20),
Width = 0.5f,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding(50),
ChildrenEnumerable = new TestTargetClass().CreateSettingsControls()
},
};
}
private class TestTargetClass
{
[SettingSource("Sample bool", "Clicking this changes a setting")]
public BindableBool TickBindable { get; } = new BindableBool();
[SettingSource("Sample float", "Change something for a mod")]
public BindableFloat SliderBindable { get; } = new BindableFloat
{
MinValue = 0,
MaxValue = 10,
Default = 5,
Value = 7
};
[SettingSource("Sample enum", "Change something for a mod")]
public Bindable<TestEnum> EnumBindable { get; } = new Bindable<TestEnum>
{
Default = TestEnum.Value1,
Value = TestEnum.Value2
};
[SettingSource("Sample string", "Change something for a mod")]
public Bindable<string> StringBindable { get; } = new Bindable<string>();
}
private enum TestEnum
{
Value1,
Value2
}
}
}

View File

@ -95,6 +95,42 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("filter count is 1", () => songSelect.FilterCount == 1); AddAssert("filter count is 1", () => songSelect.FilterCount == 1);
} }
[Test]
public void TestNoFilterOnSimpleResume()
{
addRulesetImportStep(0);
addRulesetImportStep(0);
createSongSelect();
AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child")));
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
AddStep("return", () => songSelect.MakeCurrent());
AddUntilStep("wait for current", () => songSelect.IsCurrentScreen());
AddAssert("filter count is 1", () => songSelect.FilterCount == 1);
}
[Test]
public void TestFilterOnResumeAfterChange()
{
addRulesetImportStep(0);
addRulesetImportStep(0);
AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, false));
createSongSelect();
AddStep("push child screen", () => Stack.Push(new TestSceneOsuScreenStack.TestScreen("test child")));
AddUntilStep("wait for not current", () => !songSelect.IsCurrentScreen());
AddStep("change convert setting", () => config.Set(OsuSetting.ShowConvertedBeatmaps, true));
AddStep("return", () => songSelect.MakeCurrent());
AddUntilStep("wait for current", () => songSelect.IsCurrentScreen());
AddAssert("filter count is 2", () => songSelect.FilterCount == 2);
}
[Test] [Test]
public void TestAudioResuming() public void TestAudioResuming()
{ {

View File

@ -42,7 +42,7 @@ namespace osu.Game.Tests.Visual
AddAssert("Parallax is off", () => stack.ParallaxAmount == 0); AddAssert("Parallax is off", () => stack.ParallaxAmount == 0);
} }
private class TestScreen : ScreenWithBeatmapBackground public class TestScreen : ScreenWithBeatmapBackground
{ {
private readonly string screenText; private readonly string screenText;

View File

@ -83,22 +83,16 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
}; };
} }
private ScrollState _scrollState; private ScrollState scrollState;
private ScrollState scrollState private void setScrollState(ScrollState newstate)
{ {
get => _scrollState; if (scrollState == newstate)
set
{
if (_scrollState == value)
return; return;
_scrollState = value;
delayedStateChangeDelegate?.Cancel(); delayedStateChangeDelegate?.Cancel();
switch (value) switch (scrollState = newstate)
{ {
case ScrollState.Scrolling: case ScrollState.Scrolling:
resetSelected(); resetSelected();
@ -113,7 +107,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
speedTo(0f, 2000); speedTo(0f, 2000);
tracker.FadeIn(200); tracker.FadeIn(200);
delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Stopped, 2300); delayedStateChangeDelegate = Scheduler.AddDelayed(() => setScrollState(ScrollState.Stopped), 2300);
break; break;
case ScrollState.Stopped: case ScrollState.Stopped:
@ -153,7 +147,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
st.Selected = true; st.Selected = true;
OnSelected?.Invoke(st.Team); OnSelected?.Invoke(st.Team);
delayedStateChangeDelegate = Scheduler.AddDelayed(() => scrollState = ScrollState.Idle, 10000); delayedStateChangeDelegate = Scheduler.AddDelayed(() => setScrollState(ScrollState.Idle), 10000);
break; break;
case ScrollState.Idle: case ScrollState.Idle:
@ -166,7 +160,6 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
break; break;
} }
} }
}
public void AddTeam(TournamentTeam team) public void AddTeam(TournamentTeam team)
{ {
@ -176,7 +169,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
availableTeams.Add(team); availableTeams.Add(team);
RemoveAll(c => c is ScrollingTeam); RemoveAll(c => c is ScrollingTeam);
scrollState = ScrollState.Idle; setScrollState(ScrollState.Idle);
} }
public void AddTeams(IEnumerable<TournamentTeam> teams) public void AddTeams(IEnumerable<TournamentTeam> teams)
@ -192,7 +185,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
{ {
availableTeams.Clear(); availableTeams.Clear();
RemoveAll(c => c is ScrollingTeam); RemoveAll(c => c is ScrollingTeam);
scrollState = ScrollState.Idle; setScrollState(ScrollState.Idle);
} }
public void RemoveTeam(TournamentTeam team) public void RemoveTeam(TournamentTeam team)
@ -217,7 +210,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
if (availableTeams.Count == 0) if (availableTeams.Count == 0)
return; return;
scrollState = ScrollState.Scrolling; setScrollState(ScrollState.Scrolling);
} }
public void StopScrolling() public void StopScrolling()
@ -232,13 +225,13 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
return; return;
} }
scrollState = ScrollState.Stopping; setScrollState(ScrollState.Stopping);
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
scrollState = ScrollState.Idle; setScrollState(ScrollState.Idle);
} }
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()
@ -305,7 +298,7 @@ namespace osu.Game.Tournament.Screens.Drawings.Components
private void speedTo(float value, double duration = 0, Easing easing = Easing.None) => private void speedTo(float value, double duration = 0, Easing easing = Easing.None) =>
this.TransformTo(nameof(speed), value, duration, easing); this.TransformTo(nameof(speed), value, duration, easing);
private enum ScrollState protected enum ScrollState
{ {
None, None,
Idle, Idle,

View File

@ -1,6 +1,7 @@
// 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.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Bindables; using osu.Framework.Bindables;
@ -9,8 +10,8 @@ using osu.Game.Rulesets;
namespace osu.Game.Configuration namespace osu.Game.Configuration
{ {
public abstract class DatabasedConfigManager<T> : ConfigManager<T> public abstract class DatabasedConfigManager<TLookup> : ConfigManager<TLookup>
where T : struct where TLookup : struct, Enum
{ {
private readonly SettingsStore settings; private readonly SettingsStore settings;
@ -53,7 +54,7 @@ namespace osu.Game.Configuration
private readonly List<DatabasedSetting> dirtySettings = new List<DatabasedSetting>(); private readonly List<DatabasedSetting> dirtySettings = new List<DatabasedSetting>();
protected override void AddBindable<TBindable>(T lookup, Bindable<TBindable> bindable) protected override void AddBindable<TBindable>(TLookup lookup, Bindable<TBindable> bindable)
{ {
base.AddBindable(lookup, bindable); base.AddBindable(lookup, bindable);

View File

@ -1,12 +1,13 @@
// 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 osu.Framework.Configuration; using osu.Framework.Configuration;
namespace osu.Game.Configuration namespace osu.Game.Configuration
{ {
public class InMemoryConfigManager<T> : ConfigManager<T> public class InMemoryConfigManager<TLookup> : ConfigManager<TLookup>
where T : struct where TLookup : struct, Enum
{ {
public InMemoryConfigManager() public InMemoryConfigManager()
{ {

View File

@ -0,0 +1,110 @@
// 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.Reflection;
using JetBrains.Annotations;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Overlays.Settings;
namespace osu.Game.Configuration
{
/// <summary>
/// An attribute to mark a bindable as being exposed to the user via settings controls.
/// Can be used in conjunction with <see cref="SettingSourceExtensions.CreateSettingsControls"/> to automatically create UI controls.
/// </summary>
[MeansImplicitUse]
[AttributeUsage(AttributeTargets.Property)]
public class SettingSourceAttribute : Attribute
{
public string Label { get; }
public string Description { get; }
public SettingSourceAttribute(string label, string description = null)
{
Label = label ?? string.Empty;
Description = description ?? string.Empty;
}
}
public static class SettingSourceExtensions
{
public static IEnumerable<Drawable> CreateSettingsControls(this object obj)
{
foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
{
var attr = property.GetCustomAttribute<SettingSourceAttribute>(true);
if (attr == null)
continue;
var prop = property.GetValue(obj);
switch (prop)
{
case BindableNumber<float> bNumber:
yield return new SettingsSlider<float>
{
LabelText = attr.Label,
Bindable = bNumber
};
break;
case BindableNumber<double> bNumber:
yield return new SettingsSlider<double>
{
LabelText = attr.Label,
Bindable = bNumber
};
break;
case BindableNumber<int> bNumber:
yield return new SettingsSlider<int>
{
LabelText = attr.Label,
Bindable = bNumber
};
break;
case Bindable<bool> bBool:
yield return new SettingsCheckbox
{
LabelText = attr.Label,
Bindable = bBool
};
break;
case Bindable<string> bString:
yield return new SettingsTextBox
{
LabelText = attr.Label,
Bindable = bString
};
break;
case IBindable bindable:
var dropdownType = typeof(SettingsEnumDropdown<>).MakeGenericType(bindable.GetType().GetGenericArguments()[0]);
var dropdown = (Drawable)Activator.CreateInstance(dropdownType);
dropdown.GetType().GetProperty(nameof(IHasCurrentValue<object>.Current))?.SetValue(dropdown, obj);
yield return dropdown;
break;
default:
throw new InvalidOperationException($"{nameof(SettingSourceAttribute)} was attached to an unsupported type ({prop})");
}
}
}
}
}

View File

@ -24,7 +24,7 @@ namespace osu.Game.Graphics
/// </summary> /// </summary>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns> /// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public static TransformSequence<T> FadeAccent<T>(this T accentedDrawable, Color4 newColour, double duration = 0, Easing easing = Easing.None) public static TransformSequence<T> FadeAccent<T>(this T accentedDrawable, Color4 newColour, double duration = 0, Easing easing = Easing.None)
where T : IHasAccentColour where T : class, IHasAccentColour
=> accentedDrawable.TransformTo(nameof(accentedDrawable.AccentColour), newColour, duration, easing); => accentedDrawable.TransformTo(nameof(accentedDrawable.AccentColour), newColour, duration, easing);
/// <summary> /// <summary>

View File

@ -6,12 +6,10 @@ using System;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class OsuEnumDropdown<T> : OsuDropdown<T> public class OsuEnumDropdown<T> : OsuDropdown<T>
where T : struct, Enum
{ {
public OsuEnumDropdown() public OsuEnumDropdown()
{ {
if (!typeof(T).IsEnum)
throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument");
Items = (T[])Enum.GetValues(typeof(T)); Items = (T[])Enum.GetValues(typeof(T));
} }
} }

View File

@ -26,7 +26,7 @@ namespace osu.Game.Online
/// </summary> /// </summary>
protected readonly Bindable<DownloadState> State = new Bindable<DownloadState>(); protected readonly Bindable<DownloadState> State = new Bindable<DownloadState>();
protected readonly Bindable<double> Progress = new Bindable<double>(); protected readonly BindableNumber<double> Progress = new BindableNumber<double> { MinValue = 0, MaxValue = 1 };
protected DownloadTrackingComposite(TModel model = null) protected DownloadTrackingComposite(TModel model = null)
{ {

View File

@ -45,23 +45,25 @@ namespace osu.Game.Online.Multiplayer
[JsonProperty("beatmap")] [JsonProperty("beatmap")]
private APIBeatmap apiBeatmap { get; set; } private APIBeatmap apiBeatmap { get; set; }
private APIMod[] allowedModsBacking;
[JsonProperty("allowed_mods")] [JsonProperty("allowed_mods")]
private APIMod[] allowedMods private APIMod[] allowedMods
{ {
get => AllowedMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray(); get => AllowedMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray();
set => _allowedMods = value; set => allowedModsBacking = value;
} }
private APIMod[] requiredModsBacking;
[JsonProperty("required_mods")] [JsonProperty("required_mods")]
private APIMod[] requiredMods private APIMod[] requiredMods
{ {
get => RequiredMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray(); get => RequiredMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray();
set => _requiredMods = value; set => requiredModsBacking = value;
} }
private BeatmapInfo beatmap; private BeatmapInfo beatmap;
private APIMod[] _allowedMods;
private APIMod[] _requiredMods;
public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets) public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets)
{ {
@ -70,20 +72,20 @@ namespace osu.Game.Online.Multiplayer
Beatmap = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets); Beatmap = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets);
Ruleset = rulesets.GetRuleset(RulesetID); Ruleset = rulesets.GetRuleset(RulesetID);
if (_allowedMods != null) if (allowedModsBacking != null)
{ {
AllowedMods.Clear(); AllowedMods.Clear();
AllowedMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => _allowedMods.Any(m => m.Acronym == mod.Acronym))); AllowedMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => allowedModsBacking.Any(m => m.Acronym == mod.Acronym)));
_allowedMods = null; allowedModsBacking = null;
} }
if (_requiredMods != null) if (requiredModsBacking != null)
{ {
RequiredMods.Clear(); RequiredMods.Clear();
RequiredMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => _requiredMods.Any(m => m.Acronym == mod.Acronym))); RequiredMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => requiredModsBacking.Any(m => m.Acronym == mod.Acronym)));
_requiredMods = null; requiredModsBacking = null;
} }
} }

View File

@ -261,8 +261,8 @@ namespace osu.Game.Overlays
if (allowRateAdjustments) if (allowRateAdjustments)
{ {
foreach (var mod in mods.Value.OfType<IApplicableToClock>()) foreach (var mod in mods.Value.OfType<IApplicableToTrack>())
mod.ApplyToClock(track); mod.ApplyToTrack(track);
} }
} }

View File

@ -1,6 +1,7 @@
// 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 osu.Framework.Bindables; using osu.Framework.Bindables;
using osuTK; using osuTK;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -11,6 +12,7 @@ using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.SearchableList namespace osu.Game.Overlays.SearchableList
{ {
public class DisplayStyleControl<T> : Container public class DisplayStyleControl<T> : Container
where T : struct, Enum
{ {
public readonly SlimEnumDropdown<T> Dropdown; public readonly SlimEnumDropdown<T> Dropdown;
public readonly Bindable<PanelDisplayStyle> DisplayStyle = new Bindable<PanelDisplayStyle>(); public readonly Bindable<PanelDisplayStyle> DisplayStyle = new Bindable<PanelDisplayStyle>();

View File

@ -13,7 +13,9 @@ using osu.Framework.Graphics.Shapes;
namespace osu.Game.Overlays.SearchableList namespace osu.Game.Overlays.SearchableList
{ {
public abstract class SearchableListFilterControl<T, U> : Container public abstract class SearchableListFilterControl<TTab, TCategory> : Container
where TTab : struct, Enum
where TCategory : struct, Enum
{ {
private const float padding = 10; private const float padding = 10;
@ -21,12 +23,12 @@ namespace osu.Game.Overlays.SearchableList
private readonly Box tabStrip; private readonly Box tabStrip;
public readonly SearchTextBox Search; public readonly SearchTextBox Search;
public readonly PageTabControl<T> Tabs; public readonly PageTabControl<TTab> Tabs;
public readonly DisplayStyleControl<U> DisplayStyleControl; public readonly DisplayStyleControl<TCategory> DisplayStyleControl;
protected abstract Color4 BackgroundColour { get; } protected abstract Color4 BackgroundColour { get; }
protected abstract T DefaultTab { get; } protected abstract TTab DefaultTab { get; }
protected abstract U DefaultCategory { get; } protected abstract TCategory DefaultCategory { get; }
protected virtual Drawable CreateSupplementaryControls() => null; protected virtual Drawable CreateSupplementaryControls() => null;
/// <summary> /// <summary>
@ -36,9 +38,6 @@ namespace osu.Game.Overlays.SearchableList
protected SearchableListFilterControl() protected SearchableListFilterControl()
{ {
if (!typeof(T).IsEnum)
throw new InvalidOperationException("SearchableListFilterControl's sort tabs only support enums as the generic type argument");
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
var controls = CreateSupplementaryControls(); var controls = CreateSupplementaryControls();
@ -90,7 +89,7 @@ namespace osu.Game.Overlays.SearchableList
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Right = 225 }, Padding = new MarginPadding { Right = 225 },
Child = Tabs = new PageTabControl<T> Child = Tabs = new PageTabControl<TTab>
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}, },
@ -105,7 +104,7 @@ namespace osu.Game.Overlays.SearchableList
}, },
}, },
}, },
DisplayStyleControl = new DisplayStyleControl<U> DisplayStyleControl = new DisplayStyleControl<TCategory>
{ {
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,

View File

@ -14,6 +14,7 @@ using osu.Framework.Graphics.Sprites;
namespace osu.Game.Overlays.SearchableList namespace osu.Game.Overlays.SearchableList
{ {
public abstract class SearchableListHeader<T> : Container public abstract class SearchableListHeader<T> : Container
where T : struct, Enum
{ {
public readonly HeaderTabControl<T> Tabs; public readonly HeaderTabControl<T> Tabs;
@ -24,9 +25,6 @@ namespace osu.Game.Overlays.SearchableList
protected SearchableListHeader() protected SearchableListHeader()
{ {
if (!typeof(T).IsEnum)
throw new InvalidOperationException("BrowseHeader only supports enums as the generic type argument");
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = 90; Height = 90;

View File

@ -1,6 +1,7 @@
// 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 osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -16,19 +17,22 @@ namespace osu.Game.Overlays.SearchableList
public const float WIDTH_PADDING = 80; public const float WIDTH_PADDING = 80;
} }
public abstract class SearchableListOverlay<T, U, S> : SearchableListOverlay public abstract class SearchableListOverlay<THeader, TTab, TCategory> : SearchableListOverlay
where THeader : struct, Enum
where TTab : struct, Enum
where TCategory : struct, Enum
{ {
private readonly Container scrollContainer; private readonly Container scrollContainer;
protected readonly SearchableListHeader<T> Header; protected readonly SearchableListHeader<THeader> Header;
protected readonly SearchableListFilterControl<U, S> Filter; protected readonly SearchableListFilterControl<TTab, TCategory> Filter;
protected readonly FillFlowContainer ScrollFlow; protected readonly FillFlowContainer ScrollFlow;
protected abstract Color4 BackgroundColour { get; } protected abstract Color4 BackgroundColour { get; }
protected abstract Color4 TrianglesColourLight { get; } protected abstract Color4 TrianglesColourLight { get; }
protected abstract Color4 TrianglesColourDark { get; } protected abstract Color4 TrianglesColourDark { get; }
protected abstract SearchableListHeader<T> CreateHeader(); protected abstract SearchableListHeader<THeader> CreateHeader();
protected abstract SearchableListFilterControl<U, S> CreateFilterControl(); protected abstract SearchableListFilterControl<TTab, TCategory> CreateFilterControl();
protected SearchableListOverlay() protected SearchableListOverlay()
{ {

View File

@ -1,6 +1,7 @@
// 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 osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -11,6 +12,7 @@ using osuTK;
namespace osu.Game.Overlays.SearchableList namespace osu.Game.Overlays.SearchableList
{ {
public class SlimEnumDropdown<T> : OsuEnumDropdown<T> public class SlimEnumDropdown<T> : OsuEnumDropdown<T>
where T : struct, Enum
{ {
protected override DropdownHeader CreateHeader() => new SlimDropdownHeader(); protected override DropdownHeader CreateHeader() => new SlimDropdownHeader();

View File

@ -1,12 +1,14 @@
// 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 osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings namespace osu.Game.Overlays.Settings
{ {
public class SettingsEnumDropdown<T> : SettingsDropdown<T> public class SettingsEnumDropdown<T> : SettingsDropdown<T>
where T : struct, Enum
{ {
protected override OsuDropdown<T> CreateDropdown() => new DropdownControl(); protected override OsuDropdown<T> CreateDropdown() => new DropdownControl();

View File

@ -53,27 +53,10 @@ namespace osu.Game.Overlays.Settings
} }
} }
// hold a reference to the provided bindable so we don't have to in every settings section.
private Bindable<T> bindable;
public virtual Bindable<T> Bindable public virtual Bindable<T> Bindable
{ {
get => bindable; get => controlWithCurrent.Current;
set => controlWithCurrent.Current = value;
set
{
if (bindable != null)
controlWithCurrent?.Current.UnbindFrom(bindable);
bindable = value;
controlWithCurrent?.Current.BindTo(bindable);
if (ShowsDefaultIndicator)
{
restoreDefaultButton.Bindable = bindable.GetBoundCopy();
restoreDefaultButton.Bindable.TriggerChange();
}
}
} }
public virtual IEnumerable<string> FilterTerms => Keywords == null ? new[] { LabelText } : new List<string>(Keywords) { LabelText }.ToArray(); public virtual IEnumerable<string> FilterTerms => Keywords == null ? new[] { LabelText } : new List<string>(Keywords) { LabelText }.ToArray();
@ -110,7 +93,12 @@ namespace osu.Game.Overlays.Settings
private void load() private void load()
{ {
if (controlWithCurrent != null) if (controlWithCurrent != null)
{
controlWithCurrent.Current.DisabledChanged += disabled => { Colour = disabled ? Color4.Gray : Color4.White; }; controlWithCurrent.Current.DisabledChanged += disabled => { Colour = disabled ? Color4.Gray : Color4.White; };
if (ShowsDefaultIndicator)
restoreDefaultButton.Bindable = controlWithCurrent.Current;
}
} }
private class RestoreDefaultValueButton : Container, IHasTooltip private class RestoreDefaultValueButton : Container, IHasTooltip
@ -125,6 +113,7 @@ namespace osu.Game.Overlays.Settings
bindable = value; bindable = value;
bindable.ValueChanged += _ => UpdateState(); bindable.ValueChanged += _ => UpdateState();
bindable.DisabledChanged += _ => UpdateState(); bindable.DisabledChanged += _ => UpdateState();
UpdateState();
} }
} }

View File

@ -1,12 +1,13 @@
// 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 osu.Game.Configuration; using osu.Game.Configuration;
namespace osu.Game.Rulesets.Configuration namespace osu.Game.Rulesets.Configuration
{ {
public abstract class RulesetConfigManager<T> : DatabasedConfigManager<T>, IRulesetConfigManager public abstract class RulesetConfigManager<TLookup> : DatabasedConfigManager<TLookup>, IRulesetConfigManager
where T : struct where TLookup : struct, Enum
{ {
protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null) protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int? variant = null)
: base(settings, ruleset, variant) : base(settings, ruleset, variant)

View File

@ -4,8 +4,8 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Preprocessing;
using osu.Game.Rulesets.Difficulty.Skills; using osu.Game.Rulesets.Difficulty.Skills;
@ -41,10 +41,10 @@ namespace osu.Game.Rulesets.Difficulty
IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods); IBeatmap playableBeatmap = beatmap.GetPlayableBeatmap(ruleset.RulesetInfo, mods);
var clock = new StopwatchClock(); var track = new TrackVirtual(10000);
mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock)); mods.OfType<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
return calculate(playableBeatmap, mods, clock.Rate); return calculate(playableBeatmap, mods, track.Rate);
} }
/// <summary> /// <summary>

View File

@ -3,8 +3,8 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Audio.Track;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Scoring; using osu.Game.Scoring;
@ -35,9 +35,9 @@ namespace osu.Game.Rulesets.Difficulty
protected virtual void ApplyMods(Mod[] mods) protected virtual void ApplyMods(Mod[] mods)
{ {
var clock = new StopwatchClock(); var track = new TrackVirtual(10000);
mods.OfType<IApplicableToClock>().ForEach(m => m.ApplyToClock(clock)); mods.OfType<IApplicableToTrack>().ForEach(m => m.ApplyToTrack(track));
TimeRate = clock.Rate; TimeRate = track.Rate;
} }
public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null); public abstract double Calculate(Dictionary<string, double> categoryDifficulty = null);

View File

@ -1,15 +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 osu.Framework.Timing; using osu.Framework.Audio.Track;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
/// <summary> /// <summary>
/// An interface for mods that make adjustments to the track. /// An interface for mods that make adjustments to the track.
/// </summary> /// </summary>
public interface IApplicableToClock : IApplicableMod public interface IApplicableToTrack : IApplicableMod
{ {
void ApplyToClock(IAdjustableClock clock); void ApplyToTrack(Track track);
} }
} }

View File

@ -1,9 +1,8 @@
// 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 osu.Framework.Audio; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Timing;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
@ -14,12 +13,9 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage Icon => FontAwesome.Solid.Question; public override IconUsage Icon => FontAwesome.Solid.Question;
public override string Description => "Whoaaaaa..."; public override string Description => "Whoaaaaa...";
public override void ApplyToClock(IAdjustableClock clock) public override void ApplyToTrack(Track track)
{ {
if (clock is IHasPitchAdjust pitchAdjust) track.Frequency.Value *= RateAdjust;
pitchAdjust.PitchAdjust *= RateAdjust;
else
base.ApplyToClock(clock);
} }
} }
} }

View File

@ -8,7 +8,7 @@ using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
public abstract class ModDoubleTime : ModTimeAdjust, IApplicableToClock public abstract class ModDoubleTime : ModTimeAdjust
{ {
public override string Name => "Double Time"; public override string Name => "Double Time";
public override string Acronym => "DT"; public override string Acronym => "DT";

View File

@ -8,7 +8,7 @@ using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
public abstract class ModHalfTime : ModTimeAdjust, IApplicableToClock public abstract class ModHalfTime : ModTimeAdjust
{ {
public override string Name => "Half Time"; public override string Name => "Half Time";
public override string Acronym => "HT"; public override string Acronym => "HT";

View File

@ -1,9 +1,8 @@
// 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 osu.Framework.Audio; using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Timing;
using osu.Game.Graphics; using osu.Game.Graphics;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
@ -15,12 +14,9 @@ namespace osu.Game.Rulesets.Mods
public override IconUsage Icon => OsuIcon.ModNightcore; public override IconUsage Icon => OsuIcon.ModNightcore;
public override string Description => "Uguuuuuuuu..."; public override string Description => "Uguuuuuuuu...";
public override void ApplyToClock(IAdjustableClock clock) public override void ApplyToTrack(Track track)
{ {
if (clock is IHasPitchAdjust pitchAdjust) track.Frequency.Value *= RateAdjust;
pitchAdjust.PitchAdjust *= RateAdjust;
else
base.ApplyToClock(clock);
} }
} }
} }

View File

@ -2,23 +2,19 @@
// 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 osu.Framework.Audio; using osu.Framework.Audio.Track;
using osu.Framework.Timing;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
public abstract class ModTimeAdjust : Mod public abstract class ModTimeAdjust : Mod, IApplicableToTrack
{ {
public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) }; public override Type[] IncompatibleMods => new[] { typeof(ModTimeRamp) };
protected abstract double RateAdjust { get; } protected abstract double RateAdjust { get; }
public virtual void ApplyToClock(IAdjustableClock clock) public virtual void ApplyToTrack(Track track)
{ {
if (clock is IHasTempoAdjust tempo) track.Tempo.Value *= RateAdjust;
tempo.TempoAdjust *= RateAdjust;
else
clock.Rate *= RateAdjust;
} }
} }
} }

View File

@ -3,15 +3,14 @@
using System; using System;
using System.Linq; using System.Linq;
using osu.Framework.Audio; using osu.Framework.Audio.Track;
using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToClock, IApplicableToBeatmap public abstract class ModTimeRamp : Mod, IUpdatableByPlayfield, IApplicableToTrack, IApplicableToBeatmap
{ {
/// <summary> /// <summary>
/// The point in the beatmap at which the final ramping rate should be reached. /// The point in the beatmap at which the final ramping rate should be reached.
@ -24,11 +23,11 @@ namespace osu.Game.Rulesets.Mods
private double finalRateTime; private double finalRateTime;
private double beginRampTime; private double beginRampTime;
private IAdjustableClock clock; private Track track;
public virtual void ApplyToClock(IAdjustableClock clock) public virtual void ApplyToTrack(Track track)
{ {
this.clock = clock; this.track = track;
lastAdjust = 1; lastAdjust = 1;
@ -46,7 +45,7 @@ namespace osu.Game.Rulesets.Mods
public virtual void Update(Playfield playfield) public virtual void Update(Playfield playfield)
{ {
applyAdjustment((clock.CurrentTime - beginRampTime) / finalRateTime); applyAdjustment((track.CurrentTime - beginRampTime) / finalRateTime);
} }
private double lastAdjust = 1; private double lastAdjust = 1;
@ -59,23 +58,8 @@ namespace osu.Game.Rulesets.Mods
{ {
double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment)); double adjust = 1 + (Math.Sign(FinalRateAdjustment) * Math.Clamp(amount, 0, 1) * Math.Abs(FinalRateAdjustment));
switch (clock) track.Tempo.Value /= lastAdjust;
{ track.Tempo.Value *= adjust;
case IHasPitchAdjust pitch:
pitch.PitchAdjust /= lastAdjust;
pitch.PitchAdjust *= adjust;
break;
case IHasTempoAdjust tempo:
tempo.TempoAdjust /= lastAdjust;
tempo.TempoAdjust *= adjust;
break;
default:
clock.Rate /= lastAdjust;
clock.Rate *= adjust;
break;
}
lastAdjust = adjust; lastAdjust = adjust;
} }

View File

@ -257,14 +257,13 @@ namespace osu.Game.Rulesets.Objects.Legacy
{ {
if (type == PathType.PerfectCurve) if (type == PathType.PerfectCurve)
{ {
if (vertices.Length == 3) if (vertices.Length != 3)
type = PathType.Bezier;
else if (isLinear(vertices))
{ {
// osu-stable special-cased colinear perfect curves to a linear path // osu-stable special-cased colinear perfect curves to a linear path
if (isLinear(vertices))
type = PathType.Linear; type = PathType.Linear;
} }
else
type = PathType.Bezier;
} }
var points = new List<PathControlPoint>(vertices.Length) var points = new List<PathControlPoint>(vertices.Length)

View File

@ -30,8 +30,9 @@ namespace osu.Game.Rulesets.Objects
/// Creates a new <see cref="PathControlPoint"/>. /// Creates a new <see cref="PathControlPoint"/>.
/// </summary> /// </summary>
public PathControlPoint() public PathControlPoint()
: this(Vector2.Zero, null)
{ {
Position.ValueChanged += _ => Changed?.Invoke();
Type.ValueChanged += _ => Changed?.Invoke();
} }
/// <summary> /// <summary>
@ -40,12 +41,10 @@ namespace osu.Game.Rulesets.Objects
/// <param name="position">The initial position.</param> /// <param name="position">The initial position.</param>
/// <param name="type">The initial type.</param> /// <param name="type">The initial type.</param>
public PathControlPoint(Vector2 position, PathType? type = null) public PathControlPoint(Vector2 position, PathType? type = null)
: this()
{ {
Position.Value = position; Position.Value = position;
Type.Value = type; Type.Value = type;
Position.ValueChanged += _ => Changed?.Invoke();
Type.ValueChanged += _ => Changed?.Invoke();
} }
public bool Equals(PathControlPoint other) => Position.Value == other?.Position.Value && Type.Value == other.Type.Value; public bool Equals(PathControlPoint other) => Position.Value == other?.Position.Value && Type.Value == other.Type.Value;

View File

@ -511,15 +511,19 @@ namespace osu.Game.Rulesets.UI
public IEnumerable<string> GetAvailableResources() => throw new NotImplementedException(); public IEnumerable<string> GetAvailableResources() => throw new NotImplementedException();
public void AddAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); public void AddAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotImplementedException();
public void RemoveAdjustment(AdjustableProperty type, BindableDouble adjustBindable) => throw new NotImplementedException(); public void RemoveAdjustment(AdjustableProperty type, BindableNumber<double> adjustBindable) => throw new NotImplementedException();
public BindableDouble Volume => throw new NotImplementedException(); public BindableNumber<double> Volume => throw new NotImplementedException();
public BindableDouble Balance => throw new NotImplementedException(); public BindableNumber<double> Balance => throw new NotImplementedException();
public BindableDouble Frequency => throw new NotImplementedException(); public BindableNumber<double> Frequency => throw new NotImplementedException();
public BindableNumber<double> Tempo => throw new NotImplementedException();
public IBindable<double> GetAggregate(AdjustableProperty type) => throw new NotImplementedException();
public IBindable<double> AggregateVolume => throw new NotImplementedException(); public IBindable<double> AggregateVolume => throw new NotImplementedException();
@ -527,6 +531,8 @@ namespace osu.Game.Rulesets.UI
public IBindable<double> AggregateFrequency => throw new NotImplementedException(); public IBindable<double> AggregateFrequency => throw new NotImplementedException();
public IBindable<double> AggregateTempo => throw new NotImplementedException();
public int PlaybackConcurrency public int PlaybackConcurrency
{ {
get => throw new NotImplementedException(); get => throw new NotImplementedException();

View File

@ -28,9 +28,9 @@ namespace osu.Game.Screens.Play
private readonly IReadOnlyList<Mod> mods; private readonly IReadOnlyList<Mod> mods;
/// <summary> /// <summary>
/// The original source (usually a <see cref="WorkingBeatmap"/>'s track). /// The <see cref="WorkingBeatmap"/>'s track.
/// </summary> /// </summary>
private IAdjustableClock sourceClock; private Track track;
public readonly BindableBool IsPaused = new BindableBool(); public readonly BindableBool IsPaused = new BindableBool();
@ -72,8 +72,8 @@ namespace osu.Game.Screens.Play
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
sourceClock = (IAdjustableClock)beatmap.Track ?? new StopwatchClock(); track = beatmap.Track;
(sourceClock as IAdjustableAudioComponent)?.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.AddAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false }; adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
@ -127,11 +127,11 @@ namespace osu.Game.Screens.Play
{ {
Task.Run(() => Task.Run(() =>
{ {
sourceClock.Reset(); track.Reset();
Schedule(() => Schedule(() =>
{ {
adjustableClock.ChangeSource(sourceClock); adjustableClock.ChangeSource(track);
updateRate(); updateRate();
if (!IsPaused.Value) if (!IsPaused.Value)
@ -197,13 +197,13 @@ namespace osu.Game.Screens.Play
/// </summary> /// </summary>
public void StopUsingBeatmapClock() public void StopUsingBeatmapClock()
{ {
if (sourceClock != beatmap.Track) if (track != beatmap.Track)
return; return;
removeSourceClockAdjustments(); removeSourceClockAdjustments();
sourceClock = new TrackVirtual(beatmap.Track.Length); track = new TrackVirtual(beatmap.Track.Length);
adjustableClock.ChangeSource(sourceClock); adjustableClock.ChangeSource(track);
} }
protected override void Update() protected override void Update()
@ -218,18 +218,15 @@ namespace osu.Game.Screens.Play
private void updateRate() private void updateRate()
{ {
if (sourceClock == null) return; if (track == null) return;
speedAdjustmentsApplied = true; speedAdjustmentsApplied = true;
sourceClock.ResetSpeedAdjustments(); track.ResetSpeedAdjustments();
if (sourceClock is IHasTempoAdjust tempo) track.Tempo.Value = UserPlaybackRate.Value;
tempo.TempoAdjust = UserPlaybackRate.Value;
else
sourceClock.Rate = UserPlaybackRate.Value;
foreach (var mod in mods.OfType<IApplicableToClock>()) foreach (var mod in mods.OfType<IApplicableToTrack>())
mod.ApplyToClock(sourceClock); mod.ApplyToTrack(track);
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
@ -237,18 +234,18 @@ namespace osu.Game.Screens.Play
base.Dispose(isDisposing); base.Dispose(isDisposing);
removeSourceClockAdjustments(); removeSourceClockAdjustments();
sourceClock = null; track = null;
} }
private void removeSourceClockAdjustments() private void removeSourceClockAdjustments()
{ {
if (speedAdjustmentsApplied) if (speedAdjustmentsApplied)
{ {
sourceClock.ResetSpeedAdjustments(); track.ResetSpeedAdjustments();
speedAdjustmentsApplied = false; speedAdjustmentsApplied = false;
} }
(sourceClock as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); track.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust);
} }
} }
} }

View File

@ -188,26 +188,22 @@ namespace osu.Game.Screens.Play
InternalButtons.Add(button); InternalButtons.Add(button);
} }
private int _selectionIndex = -1; private int selectionIndex = -1;
private int selectionIndex private void setSelected(int value)
{ {
get => _selectionIndex; if (selectionIndex == value)
set
{
if (_selectionIndex == value)
return; return;
// Deselect the previously-selected button // Deselect the previously-selected button
if (_selectionIndex != -1) if (selectionIndex != -1)
InternalButtons[_selectionIndex].Selected.Value = false; InternalButtons[selectionIndex].Selected.Value = false;
_selectionIndex = value; selectionIndex = value;
// Select the newly-selected button // Select the newly-selected button
if (_selectionIndex != -1) if (selectionIndex != -1)
InternalButtons[_selectionIndex].Selected.Value = true; InternalButtons[selectionIndex].Selected.Value = true;
}
} }
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
@ -218,16 +214,16 @@ namespace osu.Game.Screens.Play
{ {
case Key.Up: case Key.Up:
if (selectionIndex == -1 || selectionIndex == 0) if (selectionIndex == -1 || selectionIndex == 0)
selectionIndex = InternalButtons.Count - 1; setSelected(InternalButtons.Count - 1);
else else
selectionIndex--; setSelected(selectionIndex - 1);
return true; return true;
case Key.Down: case Key.Down:
if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1) if (selectionIndex == -1 || selectionIndex == InternalButtons.Count - 1)
selectionIndex = 0; setSelected(0);
else else
selectionIndex++; setSelected(selectionIndex + 1);
return true; return true;
} }
} }
@ -266,9 +262,9 @@ namespace osu.Game.Screens.Play
private void buttonSelectionChanged(DialogButton button, bool isSelected) private void buttonSelectionChanged(DialogButton button, bool isSelected)
{ {
if (!isSelected) if (!isSelected)
selectionIndex = -1; setSelected(-1);
else else
selectionIndex = InternalButtons.IndexOf(button); setSelected(InternalButtons.IndexOf(button));
} }
private void updateRetryCount() private void updateRetryCount()

View File

@ -44,7 +44,7 @@ namespace osu.Game.Screens.Select
} }
public struct OptionalRange<T> : IEquatable<OptionalRange<T>> public struct OptionalRange<T> : IEquatable<OptionalRange<T>>
where T : struct, IComparable where T : struct
{ {
public bool HasFilter => Max != null || Min != null; public bool HasFilter => Max != null || Min != null;

View File

@ -170,7 +170,7 @@ namespace osu.Game.Screens.Select
} }
private static void updateCriteriaRange<T>(ref FilterCriteria.OptionalRange<T> range, string op, T value) private static void updateCriteriaRange<T>(ref FilterCriteria.OptionalRange<T> range, string op, T value)
where T : struct, IComparable where T : struct
{ {
switch (op) switch (op)
{ {

View File

@ -262,8 +262,10 @@ namespace osu.Game.Screens.Select
protected virtual void ApplyFilterToCarousel(FilterCriteria criteria) protected virtual void ApplyFilterToCarousel(FilterCriteria criteria)
{ {
if (this.IsCurrentScreen()) // if not the current screen, we want to get carousel in a good presentation state before displaying (resume or enter).
Carousel.Filter(criteria); bool shouldDebounce = this.IsCurrentScreen();
Schedule(() => Carousel.Filter(criteria, shouldDebounce));
} }
private DependencyContainer dependencies; private DependencyContainer dependencies;
@ -437,8 +439,6 @@ namespace osu.Game.Screens.Select
{ {
base.OnEntering(last); base.OnEntering(last);
Carousel.Filter(FilterControl.CreateCriteria(), false);
this.FadeInFromZero(250); this.FadeInFromZero(250);
FilterControl.Activate(); FilterControl.Activate();
} }

View File

@ -1,11 +1,12 @@
// 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 osu.Framework.Configuration; using osu.Framework.Configuration;
namespace osu.Game.Skinning namespace osu.Game.Skinning
{ {
public class SkinConfigManager<T> : ConfigManager<T> where T : struct public class SkinConfigManager<TLookup> : ConfigManager<TLookup> where TLookup : struct, Enum
{ {
protected override void PerformLoad() protected override void PerformLoad()
{ {

View File

@ -41,7 +41,7 @@ namespace osu.Game.Storyboards.Drawables
/// </summary> /// </summary>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns> /// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public static TransformSequence<T> TransformFlipH<T>(this T flippable, bool newValue, double delay = 0) public static TransformSequence<T> TransformFlipH<T>(this T flippable, bool newValue, double delay = 0)
where T : IFlippable where T : class, IFlippable
=> flippable.TransformTo(flippable.PopulateTransform(new TransformFlipH(), newValue, delay)); => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipH(), newValue, delay));
/// <summary> /// <summary>
@ -49,7 +49,7 @@ namespace osu.Game.Storyboards.Drawables
/// </summary> /// </summary>
/// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns> /// <returns>A <see cref="TransformSequence{T}"/> to which further transforms can be added.</returns>
public static TransformSequence<T> TransformFlipV<T>(this T flippable, bool newValue, double delay = 0) public static TransformSequence<T> TransformFlipV<T>(this T flippable, bool newValue, double delay = 0)
where T : IFlippable where T : class, IFlippable
=> flippable.TransformTo(flippable.PopulateTransform(new TransformFlipV(), newValue, delay)); => flippable.TransformTo(flippable.PopulateTransform(new TransformFlipV(), newValue, delay));
} }
} }

View File

@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual
base.Update(); base.Update();
// note that this will override any mod rate application // note that this will override any mod rate application
Beatmap.Value.Track.TempoAdjust = Clock.Rate; Beatmap.Value.Track.Tempo.Value = Clock.Rate;
} }
} }
} }

View File

@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual
/// </summary> /// </summary>
public abstract class ScreenTestScene : ManualInputManagerTestScene public abstract class ScreenTestScene : ManualInputManagerTestScene
{ {
private readonly OsuScreenStack stack; protected readonly OsuScreenStack Stack;
private readonly Container content; private readonly Container content;
@ -22,16 +22,16 @@ namespace osu.Game.Tests.Visual
{ {
base.Content.AddRange(new Drawable[] base.Content.AddRange(new Drawable[]
{ {
stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both }, Stack = new OsuScreenStack { RelativeSizeAxes = Axes.Both },
content = new Container { RelativeSizeAxes = Axes.Both } content = new Container { RelativeSizeAxes = Axes.Both }
}); });
} }
protected void LoadScreen(OsuScreen screen) protected void LoadScreen(OsuScreen screen)
{ {
if (stack.CurrentScreen != null) if (Stack.CurrentScreen != null)
stack.Exit(); Stack.Exit();
stack.Push(screen); Stack.Push(screen);
} }
} }
} }

View File

@ -23,7 +23,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" 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.Game.Resources" Version="2019.1010.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
<PackageReference Include="ppy.osu.Framework" Version="2019.1205.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.1210.1" />
<PackageReference Include="Sentry" Version="1.2.0" /> <PackageReference Include="Sentry" Version="1.2.0" />
<PackageReference Include="SharpCompress" Version="0.24.0" /> <PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />

View File

@ -74,7 +74,7 @@
</ItemGroup> </ItemGroup>
<ItemGroup Label="Package References"> <ItemGroup Label="Package References">
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" /> <PackageReference Include="ppy.osu.Game.Resources" Version="2019.1010.0" />
<PackageReference Include="ppy.osu.Framework.iOS" Version="2019.1205.0" /> <PackageReference Include="ppy.osu.Framework.iOS" Version="2019.1210.1" />
</ItemGroup> </ItemGroup>
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. --> <!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
<ItemGroup Label="Transitive Dependencies"> <ItemGroup Label="Transitive Dependencies">
@ -82,7 +82,7 @@
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" /> <PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" 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="2019.1205.0" /> <PackageReference Include="ppy.osu.Framework" Version="2019.1210.1" />
<PackageReference Include="SharpCompress" Version="0.24.0" /> <PackageReference Include="SharpCompress" Version="0.24.0" />
<PackageReference Include="NUnit" Version="3.12.0" /> <PackageReference Include="NUnit" Version="3.12.0" />
<PackageReference Include="SharpRaven" Version="2.4.0" /> <PackageReference Include="SharpRaven" Version="2.4.0" />