mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 17:07:38 +08:00
Merge branch 'master' into flag-fit
This commit is contained in:
commit
25145cdb62
41
osu.Game.Rulesets.Osu.Tests/TestPlayfieldBorder.cs
Normal file
41
osu.Game.Rulesets.Osu.Tests/TestPlayfieldBorder.cs
Normal file
@ -0,0 +1,41 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Tests.Visual;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
public class TestPlayfieldBorder : OsuTestScene
|
||||
{
|
||||
public TestPlayfieldBorder()
|
||||
{
|
||||
Bindable<PlayfieldBorderStyle> playfieldBorderStyle = new Bindable<PlayfieldBorderStyle>();
|
||||
|
||||
AddStep("add drawables", () =>
|
||||
{
|
||||
Child = new Container
|
||||
{
|
||||
Size = new Vector2(400, 300),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PlayfieldBorder
|
||||
{
|
||||
PlayfieldBorderStyle = { BindTarget = playfieldBorderStyle }
|
||||
}
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("Set none", () => playfieldBorderStyle.Value = PlayfieldBorderStyle.None);
|
||||
AddStep("Set corners", () => playfieldBorderStyle.Value = PlayfieldBorderStyle.Corners);
|
||||
AddStep("Set full", () => playfieldBorderStyle.Value = PlayfieldBorderStyle.Full);
|
||||
}
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets.Configuration;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Configuration
|
||||
{
|
||||
@ -19,6 +20,7 @@ namespace osu.Game.Rulesets.Osu.Configuration
|
||||
Set(OsuRulesetSetting.SnakingInSliders, true);
|
||||
Set(OsuRulesetSetting.SnakingOutSliders, true);
|
||||
Set(OsuRulesetSetting.ShowCursorTrail, true);
|
||||
Set(OsuRulesetSetting.PlayfieldBorderStyle, PlayfieldBorderStyle.None);
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,6 +28,7 @@ namespace osu.Game.Rulesets.Osu.Configuration
|
||||
{
|
||||
SnakingInSliders,
|
||||
SnakingOutSliders,
|
||||
ShowCursorTrail
|
||||
ShowCursorTrail,
|
||||
PlayfieldBorderStyle,
|
||||
}
|
||||
}
|
||||
|
@ -56,7 +56,18 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
LayerBelowRuleset.Add(distanceSnapGridContainer = new Container { RelativeSizeAxes = Axes.Both });
|
||||
LayerBelowRuleset.AddRange(new Drawable[]
|
||||
{
|
||||
new PlayfieldBorder
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
PlayfieldBorderStyle = { Value = PlayfieldBorderStyle.Corners }
|
||||
},
|
||||
distanceSnapGridContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
});
|
||||
|
||||
selectedHitObjects = EditorBeatmap.SelectedHitObjects.GetBoundCopy();
|
||||
selectedHitObjects.CollectionChanged += (_, __) => updateDistanceSnapGrid();
|
||||
|
@ -17,12 +17,16 @@ using osu.Game.Rulesets.Osu.UI.Cursor;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
public class OsuPlayfield : Playfield
|
||||
{
|
||||
private readonly PlayfieldBorder playfieldBorder;
|
||||
private readonly ProxyContainer approachCircles;
|
||||
private readonly ProxyContainer spinnerProxies;
|
||||
private readonly JudgementContainer<DrawableOsuJudgement> judgementLayer;
|
||||
@ -33,12 +37,19 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
protected override GameplayCursorContainer CreateCursor() => new OsuCursorContainer();
|
||||
|
||||
private readonly Bindable<bool> playfieldBorderStyle = new BindableBool();
|
||||
|
||||
private readonly IDictionary<HitResult, DrawablePool<DrawableOsuJudgement>> poolDictionary = new Dictionary<HitResult, DrawablePool<DrawableOsuJudgement>>();
|
||||
|
||||
public OsuPlayfield()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
playfieldBorder = new PlayfieldBorder
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = 3
|
||||
},
|
||||
spinnerProxies = new ProxyContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
@ -76,6 +87,12 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
AddRangeInternal(poolDictionary.Values);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuRulesetConfigManager config)
|
||||
{
|
||||
config?.BindWith(OsuRulesetSetting.PlayfieldBorderStyle, playfieldBorder.PlayfieldBorderStyle);
|
||||
}
|
||||
|
||||
public override void Add(DrawableHitObject h)
|
||||
{
|
||||
h.OnNewResult += onNewResult;
|
||||
|
@ -5,6 +5,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.UI
|
||||
{
|
||||
@ -39,6 +40,11 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
LabelText = "Cursor trail",
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
|
||||
},
|
||||
new SettingsEnumDropdown<PlayfieldBorderStyle>
|
||||
{
|
||||
LabelText = "Playfield border style",
|
||||
Current = config.GetBindable<PlayfieldBorderStyle>(OsuRulesetSetting.PlayfieldBorderStyle),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -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 System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneDrawableStoryboardSprite : SkinnableTestScene
|
||||
{
|
||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||
|
||||
[Cached]
|
||||
private Storyboard storyboard { get; set; } = new Storyboard();
|
||||
|
||||
[Test]
|
||||
public void TestSkinSpriteDisallowedByDefault()
|
||||
{
|
||||
const string lookup_name = "hitcircleoverlay";
|
||||
|
||||
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = false);
|
||||
|
||||
AddStep("create sprites", () => SetContents(
|
||||
() => createSprite(lookup_name, Anchor.TopLeft, Vector2.Zero)));
|
||||
|
||||
assertSpritesFromSkin(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllowLookupFromSkin()
|
||||
{
|
||||
const string lookup_name = "hitcircleoverlay";
|
||||
|
||||
AddStep("allow skin lookup", () => storyboard.UseSkinSprites = true);
|
||||
|
||||
AddStep("create sprites", () => SetContents(
|
||||
() => createSprite(lookup_name, Anchor.Centre, Vector2.Zero)));
|
||||
|
||||
assertSpritesFromSkin(true);
|
||||
}
|
||||
|
||||
private DrawableStoryboardSprite createSprite(string lookupName, Anchor origin, Vector2 initialPosition)
|
||||
=> new DrawableStoryboardSprite(
|
||||
new StoryboardSprite(lookupName, origin, initialPosition)
|
||||
).With(s =>
|
||||
{
|
||||
s.LifetimeStart = double.MinValue;
|
||||
s.LifetimeEnd = double.MaxValue;
|
||||
});
|
||||
|
||||
private void assertSpritesFromSkin(bool fromSkin) =>
|
||||
AddAssert($"sprites are {(fromSkin ? "from skin" : "from storyboard")}",
|
||||
() => this.ChildrenOfType<DrawableStoryboardSprite>()
|
||||
.All(sprite => sprite.ChildrenOfType<SkinnableSprite>().Any() == fromSkin));
|
||||
}
|
||||
}
|
@ -89,17 +89,17 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Test]
|
||||
public void TestExternalHideDoesntAffectConfig()
|
||||
{
|
||||
bool originalConfigValue = false;
|
||||
HUDVisibilityMode originalConfigValue = HUDVisibilityMode.HideDuringBreaks;
|
||||
|
||||
createNew();
|
||||
|
||||
AddStep("get original config value", () => originalConfigValue = config.Get<bool>(OsuSetting.ShowInterface));
|
||||
AddStep("get original config value", () => originalConfigValue = config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
|
||||
|
||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||
AddAssert("config unchanged", () => originalConfigValue == config.Get<bool>(OsuSetting.ShowInterface));
|
||||
AddAssert("config unchanged", () => originalConfigValue == config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
|
||||
|
||||
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
|
||||
AddAssert("config unchanged", () => originalConfigValue == config.Get<bool>(OsuSetting.ShowInterface));
|
||||
AddAssert("config unchanged", () => originalConfigValue == config.Get<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
@ -35,6 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private TestPlayerLoaderContainer container;
|
||||
private TestPlayer player;
|
||||
|
||||
private bool epilepsyWarning;
|
||||
|
||||
[Resolved]
|
||||
private AudioManager audioManager { get; set; }
|
||||
|
||||
@ -59,6 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
beforeLoadAction?.Invoke();
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
Beatmap.Value.BeatmapInfo.EpilepsyWarning = epilepsyWarning;
|
||||
|
||||
foreach (var mod in SelectedMods.Value.OfType<IApplicableToTrack>())
|
||||
mod.ApplyToTrack(Beatmap.Value.Track);
|
||||
@ -251,6 +255,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("wait for player load", () => player.IsLoaded);
|
||||
}
|
||||
|
||||
[TestCase(true)]
|
||||
[TestCase(false)]
|
||||
public void TestEpilepsyWarning(bool warning)
|
||||
{
|
||||
AddStep("change epilepsy warning", () => epilepsyWarning = warning);
|
||||
AddStep("load dummy beatmap", () => ResetPlayer(false));
|
||||
|
||||
AddUntilStep("wait for current", () => loader.IsCurrentScreen());
|
||||
|
||||
AddAssert($"epilepsy warning {(warning ? "present" : "absent")}", () => this.ChildrenOfType<EpilepsyWarning>().Any() == warning);
|
||||
}
|
||||
|
||||
private class TestPlayerLoaderContainer : Container
|
||||
{
|
||||
[Cached]
|
||||
|
@ -3,14 +3,13 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays.Dashboard.Friends;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Framework.Allocation;
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dashboard.Friends;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
@ -36,7 +35,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestOffline()
|
||||
{
|
||||
AddStep("Populate", () => display.Users = getUsers());
|
||||
AddStep("Populate with offline test users", () => display.Users = getUsers());
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -80,14 +79,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private class TestFriendDisplay : FriendDisplay
|
||||
{
|
||||
public void Fetch()
|
||||
{
|
||||
base.APIStateChanged(API, APIState.Online);
|
||||
}
|
||||
|
||||
public override void APIStateChanged(IAPIProvider api, APIState state)
|
||||
{
|
||||
}
|
||||
public void Fetch() => PerformFetch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestOnlineStateVisibility()
|
||||
{
|
||||
AddStep("set status to online", () => ((DummyAPIAccess)API).State = APIState.Online);
|
||||
AddStep("set status to online", () => ((DummyAPIAccess)API).SetState(APIState.Online));
|
||||
|
||||
AddUntilStep("children are visible", () => onlineView.ViewTarget.IsPresent);
|
||||
AddUntilStep("loading animation is not visible", () => !onlineView.LoadingSpinner.IsPresent);
|
||||
@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestOfflineStateVisibility()
|
||||
{
|
||||
AddStep("set status to offline", () => ((DummyAPIAccess)API).State = APIState.Offline);
|
||||
AddStep("set status to offline", () => ((DummyAPIAccess)API).SetState(APIState.Offline));
|
||||
|
||||
AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
|
||||
AddUntilStep("loading animation is not visible", () => !onlineView.LoadingSpinner.IsPresent);
|
||||
@ -50,7 +50,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestConnectingStateVisibility()
|
||||
{
|
||||
AddStep("set status to connecting", () => ((DummyAPIAccess)API).State = APIState.Connecting);
|
||||
AddStep("set status to connecting", () => ((DummyAPIAccess)API).SetState(APIState.Connecting));
|
||||
|
||||
AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
|
||||
AddUntilStep("loading animation is visible", () => onlineView.LoadingSpinner.IsPresent);
|
||||
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
[Test]
|
||||
public void TestFailingStateVisibility()
|
||||
{
|
||||
AddStep("set status to failing", () => ((DummyAPIAccess)API).State = APIState.Failing);
|
||||
AddStep("set status to failing", () => ((DummyAPIAccess)API).SetState(APIState.Failing));
|
||||
|
||||
AddUntilStep("children are not visible", () => !onlineView.ViewTarget.IsPresent);
|
||||
AddUntilStep("loading animation is visible", () => onlineView.LoadingSpinner.IsPresent);
|
||||
|
@ -18,13 +18,13 @@ namespace osu.Game.Tests.Visual.Ranking
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 1.23 }),
|
||||
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 2.34 }),
|
||||
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 3.45 }),
|
||||
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 4.56 }),
|
||||
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 5.67 }),
|
||||
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 6.78 }),
|
||||
new StarRatingDisplay(new BeatmapInfo { StarDifficulty = 10.11 }),
|
||||
new StarRatingDisplay(new StarDifficulty(1.23, 0)),
|
||||
new StarRatingDisplay(new StarDifficulty(2.34, 0)),
|
||||
new StarRatingDisplay(new StarDifficulty(3.45, 0)),
|
||||
new StarRatingDisplay(new StarDifficulty(4.56, 0)),
|
||||
new StarRatingDisplay(new StarDifficulty(5.67, 0)),
|
||||
new StarRatingDisplay(new StarDifficulty(6.78, 0)),
|
||||
new StarRatingDisplay(new StarDifficulty(10.11, 0)),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
@ -40,6 +41,12 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
this.rulesets = rulesets;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestManyPanels()
|
||||
{
|
||||
loadBeatmaps(count: 5000, randomDifficulties: true);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKeyRepeat()
|
||||
{
|
||||
@ -707,21 +714,22 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
checkVisibleItemCount(true, 15);
|
||||
}
|
||||
|
||||
private void loadBeatmaps(List<BeatmapSetInfo> beatmapSets = null, Func<FilterCriteria> initialCriteria = null, Action<BeatmapCarousel> carouselAdjust = null)
|
||||
private void loadBeatmaps(List<BeatmapSetInfo> beatmapSets = null, Func<FilterCriteria> initialCriteria = null, Action<BeatmapCarousel> carouselAdjust = null, int? count = null, bool randomDifficulties = false)
|
||||
{
|
||||
createCarousel(carouselAdjust);
|
||||
|
||||
if (beatmapSets == null)
|
||||
{
|
||||
beatmapSets = new List<BeatmapSetInfo>();
|
||||
|
||||
for (int i = 1; i <= set_count; i++)
|
||||
beatmapSets.Add(createTestBeatmapSet(i));
|
||||
}
|
||||
|
||||
bool changed = false;
|
||||
AddStep($"Load {(beatmapSets.Count > 0 ? beatmapSets.Count.ToString() : "some")} beatmaps", () =>
|
||||
|
||||
createCarousel(c =>
|
||||
{
|
||||
carouselAdjust?.Invoke(c);
|
||||
|
||||
if (beatmapSets == null)
|
||||
{
|
||||
beatmapSets = new List<BeatmapSetInfo>();
|
||||
|
||||
for (int i = 1; i <= (count ?? set_count); i++)
|
||||
beatmapSets.Add(createTestBeatmapSet(i, randomDifficulties));
|
||||
}
|
||||
|
||||
carousel.Filter(initialCriteria?.Invoke() ?? new FilterCriteria());
|
||||
carousel.BeatmapSetsChanged = () => changed = true;
|
||||
carousel.BeatmapSets = beatmapSets;
|
||||
@ -807,7 +815,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
private bool selectedBeatmapVisible()
|
||||
{
|
||||
var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected);
|
||||
var currentlySelected = carousel.Items.FirstOrDefault(s => s.Item is CarouselBeatmap && s.Item.State.Value == CarouselItemState.Selected);
|
||||
if (currentlySelected == null)
|
||||
return true;
|
||||
|
||||
@ -820,7 +828,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
AddAssert("Selection is visible", selectedBeatmapVisible);
|
||||
}
|
||||
|
||||
private BeatmapSetInfo createTestBeatmapSet(int id)
|
||||
private BeatmapSetInfo createTestBeatmapSet(int id, bool randomDifficultyCount = false)
|
||||
{
|
||||
return new BeatmapSetInfo
|
||||
{
|
||||
@ -834,42 +842,37 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
Title = $"test set #{id}!",
|
||||
AuthorString = string.Concat(Enumerable.Repeat((char)('z' - Math.Min(25, id - 1)), 5))
|
||||
},
|
||||
Beatmaps = new List<BeatmapInfo>(new[]
|
||||
{
|
||||
new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = id * 10,
|
||||
Version = "Normal",
|
||||
StarDifficulty = 2,
|
||||
BaseDifficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = 3.5f,
|
||||
}
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = id * 10 + 1,
|
||||
Version = "Hard",
|
||||
StarDifficulty = 5,
|
||||
BaseDifficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = 5,
|
||||
}
|
||||
},
|
||||
new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = id * 10 + 2,
|
||||
Version = "Insane",
|
||||
StarDifficulty = 6,
|
||||
BaseDifficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = 7,
|
||||
}
|
||||
},
|
||||
}),
|
||||
Beatmaps = getBeatmaps(randomDifficultyCount ? RNG.Next(1, 20) : 3).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
private IEnumerable<BeatmapInfo> getBeatmaps(int count)
|
||||
{
|
||||
int id = 0;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
float diff = (float)i / count * 10;
|
||||
|
||||
string version = "Normal";
|
||||
if (diff > 6.6)
|
||||
version = "Insane";
|
||||
else if (diff > 3.3)
|
||||
version = "Hard";
|
||||
|
||||
yield return new BeatmapInfo
|
||||
{
|
||||
OnlineBeatmapID = id++ * 10,
|
||||
Version = version,
|
||||
StarDifficulty = diff,
|
||||
BaseDifficulty = new BeatmapDifficulty
|
||||
{
|
||||
OverallDifficulty = diff,
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private BeatmapSetInfo createTestBeatmapSetWithManyDifficulties(int id)
|
||||
{
|
||||
var toReturn = new BeatmapSetInfo
|
||||
@ -908,10 +911,25 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
private class TestBeatmapCarousel : BeatmapCarousel
|
||||
{
|
||||
public new List<DrawableCarouselItem> Items => base.Items;
|
||||
|
||||
public bool PendingFilterTask => PendingFilter != null;
|
||||
|
||||
public IEnumerable<DrawableCarouselItem> Items
|
||||
{
|
||||
get
|
||||
{
|
||||
foreach (var item in ScrollableContent)
|
||||
{
|
||||
yield return item;
|
||||
|
||||
if (item is DrawableCarouselBeatmapSet set)
|
||||
{
|
||||
foreach (var difficulty in set.DrawableBeatmaps)
|
||||
yield return difficulty;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<BeatmapSetInfo> GetLoadableBeatmaps() => Enumerable.Empty<BeatmapSetInfo>();
|
||||
}
|
||||
}
|
||||
|
@ -507,7 +507,7 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
var selectedPanel = songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmapSet>().First(s => s.Item.State.Value == CarouselItemState.Selected);
|
||||
|
||||
// special case for converts checked here.
|
||||
return selectedPanel.ChildrenOfType<DrawableCarouselBeatmapSet.FilterableDifficultyIcon>().All(i =>
|
||||
return selectedPanel.ChildrenOfType<FilterableDifficultyIcon>().All(i =>
|
||||
i.IsFiltered || i.Item.Beatmap.Ruleset.ID == targetRuleset || i.Item.Beatmap.Ruleset.ID == 0);
|
||||
});
|
||||
|
||||
@ -606,10 +606,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
set = songSelect.Carousel.ChildrenOfType<DrawableCarouselBeatmapSet>().First();
|
||||
});
|
||||
|
||||
DrawableCarouselBeatmapSet.FilterableDifficultyIcon difficultyIcon = null;
|
||||
FilterableDifficultyIcon difficultyIcon = null;
|
||||
AddStep("Find an icon", () =>
|
||||
{
|
||||
difficultyIcon = set.ChildrenOfType<DrawableCarouselBeatmapSet.FilterableDifficultyIcon>()
|
||||
difficultyIcon = set.ChildrenOfType<FilterableDifficultyIcon>()
|
||||
.First(icon => getDifficultyIconIndex(set, icon) != getCurrentBeatmapIndex());
|
||||
});
|
||||
|
||||
@ -634,13 +634,13 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
}));
|
||||
|
||||
BeatmapInfo filteredBeatmap = null;
|
||||
DrawableCarouselBeatmapSet.FilterableDifficultyIcon filteredIcon = null;
|
||||
FilterableDifficultyIcon filteredIcon = null;
|
||||
|
||||
AddStep("Get filtered icon", () =>
|
||||
{
|
||||
filteredBeatmap = songSelect.Carousel.SelectedBeatmapSet.Beatmaps.First(b => b.BPM < maxBPM);
|
||||
int filteredBeatmapIndex = getBeatmapIndex(filteredBeatmap.BeatmapSet, filteredBeatmap);
|
||||
filteredIcon = set.ChildrenOfType<DrawableCarouselBeatmapSet.FilterableDifficultyIcon>().ElementAt(filteredBeatmapIndex);
|
||||
filteredIcon = set.ChildrenOfType<FilterableDifficultyIcon>().ElementAt(filteredBeatmapIndex);
|
||||
});
|
||||
|
||||
AddStep("Click on a filtered difficulty", () =>
|
||||
@ -674,10 +674,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
return set != null;
|
||||
});
|
||||
|
||||
DrawableCarouselBeatmapSet.FilterableDifficultyIcon difficultyIcon = null;
|
||||
FilterableDifficultyIcon difficultyIcon = null;
|
||||
AddStep("Find an icon for different ruleset", () =>
|
||||
{
|
||||
difficultyIcon = set.ChildrenOfType<DrawableCarouselBeatmapSet.FilterableDifficultyIcon>()
|
||||
difficultyIcon = set.ChildrenOfType<FilterableDifficultyIcon>()
|
||||
.First(icon => icon.Item.Beatmap.Ruleset.ID == 3);
|
||||
});
|
||||
|
||||
@ -725,10 +725,10 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
return set != null;
|
||||
});
|
||||
|
||||
DrawableCarouselBeatmapSet.FilterableGroupedDifficultyIcon groupIcon = null;
|
||||
FilterableGroupedDifficultyIcon groupIcon = null;
|
||||
AddStep("Find group icon for different ruleset", () =>
|
||||
{
|
||||
groupIcon = set.ChildrenOfType<DrawableCarouselBeatmapSet.FilterableGroupedDifficultyIcon>()
|
||||
groupIcon = set.ChildrenOfType<FilterableGroupedDifficultyIcon>()
|
||||
.First(icon => icon.Items.First().Beatmap.Ruleset.ID == 3);
|
||||
});
|
||||
|
||||
@ -821,9 +821,9 @@ namespace osu.Game.Tests.Visual.SongSelect
|
||||
|
||||
private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmap);
|
||||
|
||||
private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, DrawableCarouselBeatmapSet.FilterableDifficultyIcon icon)
|
||||
private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon)
|
||||
{
|
||||
return set.ChildrenOfType<DrawableCarouselBeatmapSet.FilterableDifficultyIcon>().ToList().FindIndex(i => i == icon);
|
||||
return set.ChildrenOfType<FilterableDifficultyIcon>().ToList().FindIndex(i => i == icon);
|
||||
}
|
||||
|
||||
private void addRulesetImportStep(int id) => AddStep($"import test map for ruleset {id}", () => importForRuleset(id));
|
||||
|
@ -92,6 +92,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public bool LetterboxInBreaks { get; set; }
|
||||
public bool WidescreenStoryboard { get; set; }
|
||||
public bool EpilepsyWarning { get; set; }
|
||||
|
||||
// Editor
|
||||
// This bookmarks stuff is necessary because DB doesn't know how to store int[]
|
||||
|
@ -63,7 +63,7 @@ namespace osu.Game.Beatmaps
|
||||
if (checkLocalCache(set, beatmap))
|
||||
return;
|
||||
|
||||
if (api?.State != APIState.Online)
|
||||
if (api?.State.Value != APIState.Online)
|
||||
return;
|
||||
|
||||
var req = new GetBeatmapRequest(beatmap);
|
||||
|
@ -47,7 +47,10 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
private readonly IReadOnlyList<Mod> mods;
|
||||
|
||||
private readonly bool shouldShowTooltip;
|
||||
private readonly IBindable<StarDifficulty> difficultyBindable = new Bindable<StarDifficulty>();
|
||||
|
||||
private readonly bool performBackgroundDifficultyLookup;
|
||||
|
||||
private readonly Bindable<StarDifficulty> difficultyBindable = new Bindable<StarDifficulty>();
|
||||
|
||||
private Drawable background;
|
||||
|
||||
@ -70,10 +73,12 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap to show the difficulty of.</param>
|
||||
/// <param name="shouldShowTooltip">Whether to display a tooltip when hovered.</param>
|
||||
public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true)
|
||||
/// <param name="performBackgroundDifficultyLookup">Whether to perform difficulty lookup (including calculation if necessary).</param>
|
||||
public DifficultyIcon([NotNull] BeatmapInfo beatmap, bool shouldShowTooltip = true, bool performBackgroundDifficultyLookup = true)
|
||||
{
|
||||
this.beatmap = beatmap ?? throw new ArgumentNullException(nameof(beatmap));
|
||||
this.shouldShowTooltip = shouldShowTooltip;
|
||||
this.performBackgroundDifficultyLookup = performBackgroundDifficultyLookup;
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
@ -112,9 +117,13 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
// the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment)
|
||||
Icon = (ruleset ?? beatmap.Ruleset)?.CreateInstance()?.CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.Regular.QuestionCircle }
|
||||
},
|
||||
new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0),
|
||||
};
|
||||
|
||||
if (performBackgroundDifficultyLookup)
|
||||
iconContainer.Add(new DelayedLoadUnloadWrapper(() => new DifficultyRetriever(beatmap, ruleset, mods) { StarDifficulty = { BindTarget = difficultyBindable } }, 0));
|
||||
else
|
||||
difficultyBindable.Value = new StarDifficulty(beatmap.StarDifficulty, 0);
|
||||
|
||||
difficultyBindable.BindValueChanged(difficulty => background.Colour = colours.ForDifficultyRating(difficulty.NewValue.DifficultyRating));
|
||||
}
|
||||
|
||||
|
@ -175,6 +175,10 @@ namespace osu.Game.Beatmaps.Formats
|
||||
case @"WidescreenStoryboard":
|
||||
beatmap.BeatmapInfo.WidescreenStoryboard = Parsing.ParseInt(pair.Value) == 1;
|
||||
break;
|
||||
|
||||
case @"EpilepsyWarning":
|
||||
beatmap.BeatmapInfo.EpilepsyWarning = Parsing.ParseInt(pair.Value) == 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,10 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
switch (section)
|
||||
{
|
||||
case Section.General:
|
||||
handleGeneral(storyboard, line);
|
||||
return;
|
||||
|
||||
case Section.Events:
|
||||
handleEvents(line);
|
||||
return;
|
||||
@ -60,6 +64,18 @@ namespace osu.Game.Beatmaps.Formats
|
||||
base.ParseLine(storyboard, section, line);
|
||||
}
|
||||
|
||||
private void handleGeneral(Storyboard storyboard, string line)
|
||||
{
|
||||
var pair = SplitKeyVal(line);
|
||||
|
||||
switch (pair.Key)
|
||||
{
|
||||
case "UseSkinSprites":
|
||||
storyboard.UseSkinSprites = pair.Value == "1";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleEvents(string line)
|
||||
{
|
||||
var depth = 0;
|
||||
|
20
osu.Game/Configuration/HUDVisibilityMode.cs
Normal file
20
osu.Game/Configuration/HUDVisibilityMode.cs
Normal file
@ -0,0 +1,20 @@
|
||||
// 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.ComponentModel;
|
||||
|
||||
namespace osu.Game.Configuration
|
||||
{
|
||||
public enum HUDVisibilityMode
|
||||
{
|
||||
Never,
|
||||
|
||||
[Description("Hide during gameplay")]
|
||||
HideDuringGameplay,
|
||||
|
||||
[Description("Hide during breaks")]
|
||||
HideDuringBreaks,
|
||||
|
||||
Always
|
||||
}
|
||||
}
|
@ -90,7 +90,7 @@ namespace osu.Game.Configuration
|
||||
|
||||
Set(OsuSetting.HitLighting, true);
|
||||
|
||||
Set(OsuSetting.ShowInterface, true);
|
||||
Set(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Always);
|
||||
Set(OsuSetting.ShowProgressGraph, true);
|
||||
Set(OsuSetting.ShowHealthDisplayWhenCantFail, true);
|
||||
Set(OsuSetting.FadePlayfieldWhenHealthLow, true);
|
||||
@ -190,7 +190,7 @@ namespace osu.Game.Configuration
|
||||
AlwaysPlayFirstComboBreak,
|
||||
ScoreMeter,
|
||||
FloatingComments,
|
||||
ShowInterface,
|
||||
HUDVisibilityMode,
|
||||
ShowProgressGraph,
|
||||
ShowHealthDisplayWhenCantFail,
|
||||
FadePlayfieldWhenHealthLow,
|
||||
|
@ -44,6 +44,11 @@ namespace osu.Game.Graphics.UserInterface
|
||||
// blocking scroll can cause weird behaviour when this layer is used within a ScrollContainer.
|
||||
case ScrollEvent _:
|
||||
return false;
|
||||
|
||||
// blocking touch events causes the ISourcedFromTouch versions to not be fired, potentially impeding behaviour of drawables *above* the loading layer that may utilise these.
|
||||
// note that this will not work well if touch handling elements are beneath this loading layer (something to consider for the future).
|
||||
case TouchEvent _:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
|
508
osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs
generated
Normal file
508
osu.Game/Migrations/20201019224408_AddEpilepsyWarning.Designer.cs
generated
Normal file
@ -0,0 +1,508 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using osu.Game.Database;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
[DbContext(typeof(OsuDbContext))]
|
||||
[Migration("20201019224408_AddEpilepsyWarning")]
|
||||
partial class AddEpilepsyWarning
|
||||
{
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<float>("ApproachRate");
|
||||
|
||||
b.Property<float>("CircleSize");
|
||||
|
||||
b.Property<float>("DrainRate");
|
||||
|
||||
b.Property<float>("OverallDifficulty");
|
||||
|
||||
b.Property<double>("SliderMultiplier");
|
||||
|
||||
b.Property<double>("SliderTickRate");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.ToTable("BeatmapDifficulty");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<double>("AudioLeadIn");
|
||||
|
||||
b.Property<double>("BPM");
|
||||
|
||||
b.Property<int>("BaseDifficultyID");
|
||||
|
||||
b.Property<int>("BeatDivisor");
|
||||
|
||||
b.Property<int>("BeatmapSetInfoID");
|
||||
|
||||
b.Property<bool>("Countdown");
|
||||
|
||||
b.Property<double>("DistanceSpacing");
|
||||
|
||||
b.Property<bool>("EpilepsyWarning");
|
||||
|
||||
b.Property<int>("GridSize");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<bool>("Hidden");
|
||||
|
||||
b.Property<double>("Length");
|
||||
|
||||
b.Property<bool>("LetterboxInBreaks");
|
||||
|
||||
b.Property<string>("MD5Hash");
|
||||
|
||||
b.Property<int?>("MetadataID");
|
||||
|
||||
b.Property<int?>("OnlineBeatmapID");
|
||||
|
||||
b.Property<string>("Path");
|
||||
|
||||
b.Property<int>("RulesetID");
|
||||
|
||||
b.Property<bool>("SpecialStyle");
|
||||
|
||||
b.Property<float>("StackLeniency");
|
||||
|
||||
b.Property<double>("StarDifficulty");
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.Property<string>("StoredBookmarks");
|
||||
|
||||
b.Property<double>("TimelineZoom");
|
||||
|
||||
b.Property<string>("Version");
|
||||
|
||||
b.Property<bool>("WidescreenStoryboard");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("BaseDifficultyID");
|
||||
|
||||
b.HasIndex("BeatmapSetInfoID");
|
||||
|
||||
b.HasIndex("Hash");
|
||||
|
||||
b.HasIndex("MD5Hash");
|
||||
|
||||
b.HasIndex("MetadataID");
|
||||
|
||||
b.HasIndex("OnlineBeatmapID")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("RulesetID");
|
||||
|
||||
b.ToTable("BeatmapInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Artist");
|
||||
|
||||
b.Property<string>("ArtistUnicode");
|
||||
|
||||
b.Property<string>("AudioFile");
|
||||
|
||||
b.Property<string>("AuthorString")
|
||||
.HasColumnName("Author");
|
||||
|
||||
b.Property<string>("BackgroundFile");
|
||||
|
||||
b.Property<int>("PreviewTime");
|
||||
|
||||
b.Property<string>("Source");
|
||||
|
||||
b.Property<string>("Tags");
|
||||
|
||||
b.Property<string>("Title");
|
||||
|
||||
b.Property<string>("TitleUnicode");
|
||||
|
||||
b.Property<string>("VideoFile");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.ToTable("BeatmapMetadata");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("BeatmapSetInfoID");
|
||||
|
||||
b.Property<int>("FileInfoID");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired();
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("BeatmapSetInfoID");
|
||||
|
||||
b.HasIndex("FileInfoID");
|
||||
|
||||
b.ToTable("BeatmapSetFileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<DateTimeOffset>("DateAdded");
|
||||
|
||||
b.Property<bool>("DeletePending");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<int?>("MetadataID");
|
||||
|
||||
b.Property<int?>("OnlineBeatmapSetID");
|
||||
|
||||
b.Property<bool>("Protected");
|
||||
|
||||
b.Property<int>("Status");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("DeletePending");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("MetadataID");
|
||||
|
||||
b.HasIndex("OnlineBeatmapSetID")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("BeatmapSetInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Key")
|
||||
.HasColumnName("Key");
|
||||
|
||||
b.Property<int?>("RulesetID");
|
||||
|
||||
b.Property<int?>("SkinInfoID");
|
||||
|
||||
b.Property<string>("StringValue")
|
||||
.HasColumnName("Value");
|
||||
|
||||
b.Property<int?>("Variant");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("SkinInfoID");
|
||||
|
||||
b.HasIndex("RulesetID", "Variant");
|
||||
|
||||
b.ToTable("Settings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<int>("ReferenceCount");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("ReferenceCount");
|
||||
|
||||
b.ToTable("FileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("IntAction")
|
||||
.HasColumnName("Action");
|
||||
|
||||
b.Property<string>("KeysString")
|
||||
.HasColumnName("Keys");
|
||||
|
||||
b.Property<int?>("RulesetID");
|
||||
|
||||
b.Property<int?>("Variant");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("IntAction");
|
||||
|
||||
b.HasIndex("RulesetID", "Variant");
|
||||
|
||||
b.ToTable("KeyBinding");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
|
||||
{
|
||||
b.Property<int?>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<bool>("Available");
|
||||
|
||||
b.Property<string>("InstantiationInfo");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.Property<string>("ShortName");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("Available");
|
||||
|
||||
b.HasIndex("ShortName")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("RulesetInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("FileInfoID");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<int?>("ScoreInfoID");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("FileInfoID");
|
||||
|
||||
b.HasIndex("ScoreInfoID");
|
||||
|
||||
b.ToTable("ScoreFileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<double>("Accuracy")
|
||||
.HasColumnType("DECIMAL(1,4)");
|
||||
|
||||
b.Property<int>("BeatmapInfoID");
|
||||
|
||||
b.Property<int>("Combo");
|
||||
|
||||
b.Property<DateTimeOffset>("Date");
|
||||
|
||||
b.Property<bool>("DeletePending");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<int>("MaxCombo");
|
||||
|
||||
b.Property<string>("ModsJson")
|
||||
.HasColumnName("Mods");
|
||||
|
||||
b.Property<long?>("OnlineScoreID");
|
||||
|
||||
b.Property<double?>("PP");
|
||||
|
||||
b.Property<int>("Rank");
|
||||
|
||||
b.Property<int>("RulesetID");
|
||||
|
||||
b.Property<string>("StatisticsJson")
|
||||
.HasColumnName("Statistics");
|
||||
|
||||
b.Property<long>("TotalScore");
|
||||
|
||||
b.Property<long?>("UserID")
|
||||
.HasColumnName("UserID");
|
||||
|
||||
b.Property<string>("UserString")
|
||||
.HasColumnName("User");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("BeatmapInfoID");
|
||||
|
||||
b.HasIndex("OnlineScoreID")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("RulesetID");
|
||||
|
||||
b.ToTable("ScoreInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<int>("FileInfoID");
|
||||
|
||||
b.Property<string>("Filename")
|
||||
.IsRequired();
|
||||
|
||||
b.Property<int>("SkinInfoID");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("FileInfoID");
|
||||
|
||||
b.HasIndex("SkinInfoID");
|
||||
|
||||
b.ToTable("SkinFileInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b =>
|
||||
{
|
||||
b.Property<int>("ID")
|
||||
.ValueGeneratedOnAdd();
|
||||
|
||||
b.Property<string>("Creator");
|
||||
|
||||
b.Property<bool>("DeletePending");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
||||
b.Property<string>("Name");
|
||||
|
||||
b.HasKey("ID");
|
||||
|
||||
b.HasIndex("DeletePending");
|
||||
|
||||
b.HasIndex("Hash")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("SkinInfo");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
|
||||
.WithMany()
|
||||
.HasForeignKey("BaseDifficultyID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
|
||||
.WithMany("Beatmaps")
|
||||
.HasForeignKey("BeatmapSetInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
|
||||
.WithMany("Beatmaps")
|
||||
.HasForeignKey("MetadataID");
|
||||
|
||||
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
|
||||
.WithMany()
|
||||
.HasForeignKey("RulesetID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("BeatmapSetInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
|
||||
.WithMany("BeatmapSets")
|
||||
.HasForeignKey("MetadataID");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Skinning.SkinInfo")
|
||||
.WithMany("Settings")
|
||||
.HasForeignKey("SkinInfoID");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreFileInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Scoring.ScoreInfo")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("ScoreInfoID");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap")
|
||||
.WithMany("Scores")
|
||||
.HasForeignKey("BeatmapInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
|
||||
.WithMany()
|
||||
.HasForeignKey("RulesetID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b =>
|
||||
{
|
||||
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
|
||||
.WithMany()
|
||||
.HasForeignKey("FileInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
|
||||
b.HasOne("osu.Game.Skinning.SkinInfo")
|
||||
.WithMany("Files")
|
||||
.HasForeignKey("SkinInfoID")
|
||||
.OnDelete(DeleteBehavior.Cascade);
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
23
osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs
Normal file
23
osu.Game/Migrations/20201019224408_AddEpilepsyWarning.cs
Normal file
@ -0,0 +1,23 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
namespace osu.Game.Migrations
|
||||
{
|
||||
public partial class AddEpilepsyWarning : Migration
|
||||
{
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "EpilepsyWarning",
|
||||
table: "BeatmapInfo",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
}
|
||||
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "EpilepsyWarning",
|
||||
table: "BeatmapInfo");
|
||||
}
|
||||
}
|
||||
}
|
@ -57,6 +57,8 @@ namespace osu.Game.Migrations
|
||||
|
||||
b.Property<double>("DistanceSpacing");
|
||||
|
||||
b.Property<bool>("EpilepsyWarning");
|
||||
|
||||
b.Property<int>("GridSize");
|
||||
|
||||
b.Property<string>("Hash");
|
||||
|
@ -78,26 +78,8 @@ namespace osu.Game.Online.API
|
||||
|
||||
private void onTokenChanged(ValueChangedEvent<OAuthToken> e) => config.Set(OsuSetting.Token, config.Get<bool>(OsuSetting.SavePassword) ? authentication.TokenString : string.Empty);
|
||||
|
||||
private readonly List<IOnlineComponent> components = new List<IOnlineComponent>();
|
||||
|
||||
internal new void Schedule(Action action) => base.Schedule(action);
|
||||
|
||||
/// <summary>
|
||||
/// Register a component to receive API events.
|
||||
/// Fires <see cref="IOnlineComponent.APIStateChanged"/> once immediately to ensure a correct state.
|
||||
/// </summary>
|
||||
/// <param name="component"></param>
|
||||
public void Register(IOnlineComponent component)
|
||||
{
|
||||
Schedule(() => components.Add(component));
|
||||
component.APIStateChanged(this, state);
|
||||
}
|
||||
|
||||
public void Unregister(IOnlineComponent component)
|
||||
{
|
||||
Schedule(() => components.Remove(component));
|
||||
}
|
||||
|
||||
public string AccessToken => authentication.RequestAccessToken();
|
||||
|
||||
/// <summary>
|
||||
@ -109,7 +91,7 @@ namespace osu.Game.Online.API
|
||||
{
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
switch (State)
|
||||
switch (State.Value)
|
||||
{
|
||||
case APIState.Failing:
|
||||
//todo: replace this with a ping request.
|
||||
@ -131,12 +113,12 @@ namespace osu.Game.Online.API
|
||||
// work to restore a connection...
|
||||
if (!HasLogin)
|
||||
{
|
||||
State = APIState.Offline;
|
||||
state.Value = APIState.Offline;
|
||||
Thread.Sleep(50);
|
||||
continue;
|
||||
}
|
||||
|
||||
State = APIState.Connecting;
|
||||
state.Value = APIState.Connecting;
|
||||
|
||||
// save the username at this point, if the user requested for it to be.
|
||||
config.Set(OsuSetting.Username, config.Get<bool>(OsuSetting.SaveUsername) ? ProvidedUsername : string.Empty);
|
||||
@ -162,20 +144,20 @@ namespace osu.Game.Online.API
|
||||
failureCount = 0;
|
||||
|
||||
//we're connected!
|
||||
State = APIState.Online;
|
||||
state.Value = APIState.Online;
|
||||
};
|
||||
|
||||
if (!handleRequest(userReq))
|
||||
{
|
||||
if (State == APIState.Connecting)
|
||||
State = APIState.Failing;
|
||||
if (State.Value == APIState.Connecting)
|
||||
state.Value = APIState.Failing;
|
||||
continue;
|
||||
}
|
||||
|
||||
// The Success callback event is fired on the main thread, so we should wait for that to run before proceeding.
|
||||
// Without this, we will end up circulating this Connecting loop multiple times and queueing up many web requests
|
||||
// before actually going online.
|
||||
while (State > APIState.Offline && State < APIState.Online)
|
||||
while (State.Value > APIState.Offline && State.Value < APIState.Online)
|
||||
Thread.Sleep(500);
|
||||
|
||||
break;
|
||||
@ -224,7 +206,7 @@ namespace osu.Game.Online.API
|
||||
|
||||
public void Login(string username, string password)
|
||||
{
|
||||
Debug.Assert(State == APIState.Offline);
|
||||
Debug.Assert(State.Value == APIState.Offline);
|
||||
|
||||
ProvidedUsername = username;
|
||||
this.password = password;
|
||||
@ -232,7 +214,7 @@ namespace osu.Game.Online.API
|
||||
|
||||
public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password)
|
||||
{
|
||||
Debug.Assert(State == APIState.Offline);
|
||||
Debug.Assert(State.Value == APIState.Offline);
|
||||
|
||||
var req = new RegistrationRequest
|
||||
{
|
||||
@ -276,7 +258,7 @@ namespace osu.Game.Online.API
|
||||
req.Perform(this);
|
||||
|
||||
// we could still be in initialisation, at which point we don't want to say we're Online yet.
|
||||
if (IsLoggedIn) State = APIState.Online;
|
||||
if (IsLoggedIn) state.Value = APIState.Online;
|
||||
|
||||
failureCount = 0;
|
||||
return true;
|
||||
@ -293,27 +275,12 @@ namespace osu.Game.Online.API
|
||||
}
|
||||
}
|
||||
|
||||
private APIState state;
|
||||
private readonly Bindable<APIState> state = new Bindable<APIState>();
|
||||
|
||||
public APIState State
|
||||
{
|
||||
get => state;
|
||||
private set
|
||||
{
|
||||
if (state == value)
|
||||
return;
|
||||
|
||||
APIState oldState = state;
|
||||
state = value;
|
||||
|
||||
log.Add($@"We just went {state}!");
|
||||
Schedule(() =>
|
||||
{
|
||||
components.ForEach(c => c.APIStateChanged(this, state));
|
||||
OnStateChange?.Invoke(oldState, state);
|
||||
});
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The current connectivity state of the API.
|
||||
/// </summary>
|
||||
public IBindable<APIState> State => state;
|
||||
|
||||
private bool handleWebException(WebException we)
|
||||
{
|
||||
@ -343,9 +310,9 @@ namespace osu.Game.Online.API
|
||||
// we might try again at an api level.
|
||||
return false;
|
||||
|
||||
if (State == APIState.Online)
|
||||
if (State.Value == APIState.Online)
|
||||
{
|
||||
State = APIState.Failing;
|
||||
state.Value = APIState.Failing;
|
||||
flushQueue();
|
||||
}
|
||||
|
||||
@ -362,10 +329,6 @@ namespace osu.Game.Online.API
|
||||
lock (queue) queue.Enqueue(request);
|
||||
}
|
||||
|
||||
public event StateChangeDelegate OnStateChange;
|
||||
|
||||
public delegate void StateChangeDelegate(APIState oldState, APIState newState);
|
||||
|
||||
private void flushQueue(bool failOldRequests = true)
|
||||
{
|
||||
lock (queue)
|
||||
@ -392,7 +355,7 @@ namespace osu.Game.Online.API
|
||||
// Scheduled prior to state change such that the state changed event is invoked with the correct user present
|
||||
Schedule(() => LocalUser.Value = createGuestUser());
|
||||
|
||||
State = APIState.Offline;
|
||||
state.Value = APIState.Offline;
|
||||
}
|
||||
|
||||
private static User createGuestUser() => new GuestUser();
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Bindables;
|
||||
@ -21,34 +20,23 @@ namespace osu.Game.Online.API
|
||||
|
||||
public Bindable<UserActivity> Activity { get; } = new Bindable<UserActivity>();
|
||||
|
||||
public bool IsLoggedIn => State == APIState.Online;
|
||||
public bool IsLoggedIn => State.Value == APIState.Online;
|
||||
|
||||
public string ProvidedUsername => LocalUser.Value.Username;
|
||||
|
||||
public string Endpoint => "http://localhost";
|
||||
|
||||
private APIState state = APIState.Online;
|
||||
|
||||
private readonly List<IOnlineComponent> components = new List<IOnlineComponent>();
|
||||
|
||||
/// <summary>
|
||||
/// Provide handling logic for an arbitrary API request.
|
||||
/// </summary>
|
||||
public Action<APIRequest> HandleRequest;
|
||||
|
||||
public APIState State
|
||||
{
|
||||
get => state;
|
||||
set
|
||||
{
|
||||
if (state == value)
|
||||
return;
|
||||
private readonly Bindable<APIState> state = new Bindable<APIState>(APIState.Online);
|
||||
|
||||
state = value;
|
||||
|
||||
Scheduler.Add(() => components.ForEach(c => c.APIStateChanged(this, value)));
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// The current connectivity state of the API.
|
||||
/// </summary>
|
||||
public IBindable<APIState> State => state;
|
||||
|
||||
public DummyAPIAccess()
|
||||
{
|
||||
@ -72,17 +60,6 @@ namespace osu.Game.Online.API
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Register(IOnlineComponent component)
|
||||
{
|
||||
Scheduler.Add(delegate { components.Add(component); });
|
||||
component.APIStateChanged(this, state);
|
||||
}
|
||||
|
||||
public void Unregister(IOnlineComponent component)
|
||||
{
|
||||
Scheduler.Add(delegate { components.Remove(component); });
|
||||
}
|
||||
|
||||
public void Login(string username, string password)
|
||||
{
|
||||
LocalUser.Value = new User
|
||||
@ -91,13 +68,13 @@ namespace osu.Game.Online.API
|
||||
Id = 1001,
|
||||
};
|
||||
|
||||
State = APIState.Online;
|
||||
state.Value = APIState.Online;
|
||||
}
|
||||
|
||||
public void Logout()
|
||||
{
|
||||
LocalUser.Value = new GuestUser();
|
||||
State = APIState.Offline;
|
||||
state.Value = APIState.Offline;
|
||||
}
|
||||
|
||||
public RegistrationRequest.RegistrationRequestErrors CreateAccount(string email, string username, string password)
|
||||
@ -105,5 +82,7 @@ namespace osu.Game.Online.API
|
||||
Thread.Sleep(200);
|
||||
return null;
|
||||
}
|
||||
|
||||
public void SetState(APIState newState) => state.Value = newState;
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,13 @@ namespace osu.Game.Online.API
|
||||
{
|
||||
/// <summary>
|
||||
/// The local user.
|
||||
/// This is not thread-safe and should be scheduled locally if consumed from a drawable component.
|
||||
/// </summary>
|
||||
Bindable<User> LocalUser { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The current user's activity.
|
||||
/// This is not thread-safe and should be scheduled locally if consumed from a drawable component.
|
||||
/// </summary>
|
||||
Bindable<UserActivity> Activity { get; }
|
||||
|
||||
@ -35,7 +37,11 @@ namespace osu.Game.Online.API
|
||||
/// </summary>
|
||||
string Endpoint { get; }
|
||||
|
||||
APIState State { get; }
|
||||
/// <summary>
|
||||
/// The current connection state of the API.
|
||||
/// This is not thread-safe and should be scheduled locally if consumed from a drawable component.
|
||||
/// </summary>
|
||||
IBindable<APIState> State { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Queue a new request.
|
||||
@ -61,18 +67,6 @@ namespace osu.Game.Online.API
|
||||
/// <param name="request">The request to perform.</param>
|
||||
Task PerformAsync(APIRequest request);
|
||||
|
||||
/// <summary>
|
||||
/// Register a component to receive state changes.
|
||||
/// </summary>
|
||||
/// <param name="component">The component to register.</param>
|
||||
void Register(IOnlineComponent component);
|
||||
|
||||
/// <summary>
|
||||
/// Unregisters a component to receive state changes.
|
||||
/// </summary>
|
||||
/// <param name="component">The component to unregister.</param>
|
||||
void Unregister(IOnlineComponent component);
|
||||
|
||||
/// <summary>
|
||||
/// Attempt to login using the provided credentials. This is a non-blocking operation.
|
||||
/// </summary>
|
||||
|
@ -1,10 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public interface IOnlineComponent
|
||||
{
|
||||
void APIStateChanged(IAPIProvider api, APIState state);
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
@ -22,7 +23,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
public abstract class Leaderboard<TScope, TScoreInfo> : Container, IOnlineComponent
|
||||
public abstract class Leaderboard<TScope, TScoreInfo> : Container
|
||||
{
|
||||
private const double fade_duration = 300;
|
||||
|
||||
@ -242,16 +243,13 @@ namespace osu.Game.Online.Leaderboards
|
||||
|
||||
private ScheduledDelegate pendingUpdateScores;
|
||||
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
api?.Register(this);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
api?.Unregister(this);
|
||||
apiState.BindTo(api.State);
|
||||
apiState.BindValueChanged(onlineStateChanged, true);
|
||||
}
|
||||
|
||||
public void RefreshScores() => UpdateScores();
|
||||
@ -260,9 +258,9 @@ namespace osu.Game.Online.Leaderboards
|
||||
|
||||
protected abstract bool IsOnlineScope { get; }
|
||||
|
||||
public void APIStateChanged(IAPIProvider api, APIState state)
|
||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||
{
|
||||
switch (state)
|
||||
switch (state.NewValue)
|
||||
{
|
||||
case APIState.Online:
|
||||
case APIState.Offline:
|
||||
@ -271,7 +269,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
protected void UpdateScores()
|
||||
{
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -14,7 +15,7 @@ namespace osu.Game.Online
|
||||
/// A <see cref="Container"/> for displaying online content which require a local user to be logged in.
|
||||
/// Shows its children only when the local user is logged in and supports displaying a placeholder if not.
|
||||
/// </summary>
|
||||
public abstract class OnlineViewContainer : Container, IOnlineComponent
|
||||
public abstract class OnlineViewContainer : Container
|
||||
{
|
||||
protected LoadingSpinner LoadingSpinner { get; private set; }
|
||||
|
||||
@ -34,8 +35,10 @@ namespace osu.Game.Online
|
||||
this.placeholderMessage = placeholderMessage;
|
||||
}
|
||||
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(IAPIProvider api)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
@ -46,18 +49,14 @@ namespace osu.Game.Online
|
||||
Alpha = 0,
|
||||
}
|
||||
};
|
||||
|
||||
apiState.BindTo(api.State);
|
||||
apiState.BindValueChanged(onlineStateChanged, true);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
API.Register(this);
|
||||
}
|
||||
|
||||
public virtual void APIStateChanged(IAPIProvider api, APIState state)
|
||||
{
|
||||
switch (state)
|
||||
switch (state.NewValue)
|
||||
{
|
||||
case APIState.Offline:
|
||||
PopContentOut(Content);
|
||||
@ -79,7 +78,7 @@ namespace osu.Game.Online
|
||||
placeholder.FadeOut(transform_duration / 2, Easing.OutQuint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Applies a transform to the online content to make it hidden.
|
||||
@ -90,11 +89,5 @@ namespace osu.Game.Online
|
||||
/// Applies a transform to the online content to make it visible.
|
||||
/// </summary>
|
||||
protected virtual void PopContentIn(Drawable content) => content.FadeIn(transform_duration, Easing.OutQuint);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
API?.Unregister(this);
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -17,7 +18,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public class AccountCreationOverlay : OsuFocusedOverlayContainer, IOnlineComponent
|
||||
public class AccountCreationOverlay : OsuFocusedOverlayContainer
|
||||
{
|
||||
private const float transition_time = 400;
|
||||
|
||||
@ -30,10 +31,13 @@ namespace osu.Game.Overlays
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, IAPIProvider api)
|
||||
{
|
||||
api.Register(this);
|
||||
apiState.BindTo(api.State);
|
||||
apiState.BindValueChanged(apiStateChanged, true);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -97,9 +101,9 @@ namespace osu.Game.Overlays
|
||||
this.FadeOut(100);
|
||||
}
|
||||
|
||||
public void APIStateChanged(IAPIProvider api, APIState state)
|
||||
private void apiStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||
{
|
||||
switch (state)
|
||||
switch (state.NewValue)
|
||||
{
|
||||
case APIState.Offline:
|
||||
case APIState.Failing:
|
||||
@ -112,6 +116,6 @@ namespace osu.Game.Overlays
|
||||
Hide();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,14 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
}
|
||||
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(IAPIProvider api)
|
||||
{
|
||||
apiState.BindTo(api.State);
|
||||
apiState.BindValueChanged(onlineStateChanged, true);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
@ -130,13 +135,13 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
}
|
||||
|
||||
public override void APIStateChanged(IAPIProvider api, APIState state)
|
||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||
{
|
||||
if (State.Value == Visibility.Hidden)
|
||||
return;
|
||||
|
||||
Header.Current.TriggerChange();
|
||||
}
|
||||
});
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
|
@ -12,7 +12,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public abstract class FullscreenOverlay<T> : WaveOverlayContainer, IOnlineComponent, INamedOverlayComponent
|
||||
public abstract class FullscreenOverlay<T> : WaveOverlayContainer, INamedOverlayComponent
|
||||
where T : OverlayHeader
|
||||
{
|
||||
public virtual string IconTexture => Header?.Title.IconTexture ?? string.Empty;
|
||||
@ -86,21 +86,5 @@ namespace osu.Game.Overlays
|
||||
protected virtual void PopOutComplete()
|
||||
{
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
API.Register(this);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
API?.Unregister(this);
|
||||
}
|
||||
|
||||
public virtual void APIStateChanged(IAPIProvider api, APIState state)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.API;
|
||||
@ -15,7 +16,7 @@ namespace osu.Game.Overlays
|
||||
/// Automatically performs a data fetch on load.
|
||||
/// </remarks>
|
||||
/// <typeparam name="T">The type of the API response.</typeparam>
|
||||
public abstract class OverlayView<T> : CompositeDrawable, IOnlineComponent
|
||||
public abstract class OverlayView<T> : CompositeDrawable
|
||||
where T : class
|
||||
{
|
||||
[Resolved]
|
||||
@ -29,10 +30,13 @@ namespace osu.Game.Overlays
|
||||
AutoSizeAxes = Axes.Y;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
base.LoadComplete();
|
||||
API.Register(this);
|
||||
apiState.BindTo(API.State);
|
||||
apiState.BindValueChanged(onlineStateChanged, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -59,20 +63,19 @@ namespace osu.Game.Overlays
|
||||
API.Queue(request);
|
||||
}
|
||||
|
||||
public virtual void APIStateChanged(IAPIProvider api, APIState state)
|
||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||
{
|
||||
switch (state)
|
||||
switch (state.NewValue)
|
||||
{
|
||||
case APIState.Online:
|
||||
PerformFetch();
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
request?.Cancel();
|
||||
API?.Unregister(this);
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
|
@ -37,10 +37,10 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||
LabelText = "Lighten playfield during breaks",
|
||||
Current = config.GetBindable<bool>(OsuSetting.LightenDuringBreaks)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
new SettingsEnumDropdown<HUDVisibilityMode>
|
||||
{
|
||||
LabelText = "Show score overlay",
|
||||
Current = config.GetBindable<bool>(OsuSetting.ShowInterface)
|
||||
LabelText = "HUD overlay visibility mode",
|
||||
Current = config.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
@ -78,7 +78,7 @@ namespace osu.Game.Overlays.Settings.Sections.Gameplay
|
||||
LabelText = "Score display mode",
|
||||
Current = config.GetBindable<ScoringMode>(OsuSetting.ScoreDisplayMode),
|
||||
Keywords = new[] { "scoring" }
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows)
|
||||
|
@ -13,6 +13,7 @@ using osu.Game.Online.API;
|
||||
using osuTK;
|
||||
using osu.Game.Users;
|
||||
using System.ComponentModel;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
@ -25,7 +26,7 @@ using Container = osu.Framework.Graphics.Containers.Container;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.General
|
||||
{
|
||||
public class LoginSettings : FillFlowContainer, IOnlineComponent
|
||||
public class LoginSettings : FillFlowContainer
|
||||
{
|
||||
private bool bounding = true;
|
||||
private LoginForm form;
|
||||
@ -41,6 +42,11 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
/// </summary>
|
||||
public Action RequestHide;
|
||||
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
public override RectangleF BoundingBox => bounding ? base.BoundingBox : RectangleF.Empty;
|
||||
|
||||
public bool Bounding
|
||||
@ -61,17 +67,18 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
Spacing = new Vector2(0f, 5f);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(permitNulls: true)]
|
||||
private void load(IAPIProvider api)
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
api?.Register(this);
|
||||
apiState.BindTo(api.State);
|
||||
apiState.BindValueChanged(onlineStateChanged, true);
|
||||
}
|
||||
|
||||
public void APIStateChanged(IAPIProvider api, APIState state) => Schedule(() =>
|
||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||
{
|
||||
form = null;
|
||||
|
||||
switch (state)
|
||||
switch (state.NewValue)
|
||||
{
|
||||
case APIState.Offline:
|
||||
Children = new Drawable[]
|
||||
@ -107,7 +114,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
|
||||
Origin = Anchor.TopCentre,
|
||||
TextAnchor = Anchor.TopCentre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Text = state == APIState.Failing ? "Connection is failing, will attempt to reconnect... " : "Attempting to connect... ",
|
||||
Text = state.NewValue == APIState.Failing ? "Connection is failing, will attempt to reconnect... " : "Attempting to connect... ",
|
||||
Margin = new MarginPadding { Top = 10, Bottom = 10 },
|
||||
},
|
||||
};
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
@ -14,10 +15,15 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Toolbar
|
||||
{
|
||||
public class ToolbarUserButton : ToolbarOverlayToggleButton, IOnlineComponent
|
||||
public class ToolbarUserButton : ToolbarOverlayToggleButton
|
||||
{
|
||||
private readonly UpdateableAvatar avatar;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
|
||||
public ToolbarUserButton()
|
||||
{
|
||||
AutoSizeAxes = Axes.X;
|
||||
@ -44,16 +50,17 @@ namespace osu.Game.Overlays.Toolbar
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(IAPIProvider api, LoginOverlay login)
|
||||
private void load(LoginOverlay login)
|
||||
{
|
||||
api.Register(this);
|
||||
apiState.BindTo(api.State);
|
||||
apiState.BindValueChanged(onlineStateChanged, true);
|
||||
|
||||
StateContainer = login;
|
||||
}
|
||||
|
||||
public void APIStateChanged(IAPIProvider api, APIState state)
|
||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||
{
|
||||
switch (state)
|
||||
switch (state.NewValue)
|
||||
{
|
||||
default:
|
||||
Text = @"Guest";
|
||||
@ -65,6 +72,6 @@ namespace osu.Game.Overlays.Toolbar
|
||||
avatar.User = api.LocalUser.Value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -100,11 +100,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
Children = new Drawable[]
|
||||
{
|
||||
// layers below playfield
|
||||
drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChildren(new Drawable[]
|
||||
{
|
||||
LayerBelowRuleset,
|
||||
new EditorPlayfieldBorder { RelativeSizeAxes = Axes.Both }
|
||||
}),
|
||||
drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer().WithChild(LayerBelowRuleset),
|
||||
drawableRulesetWrapper,
|
||||
// layers above playfield
|
||||
drawableRulesetWrapper.CreatePlayfieldAdjustmentContainer()
|
||||
|
149
osu.Game/Rulesets/UI/PlayfieldBorder.cs
Normal file
149
osu.Game/Rulesets/UI/PlayfieldBorder.cs
Normal file
@ -0,0 +1,149 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a border around the playfield.
|
||||
/// </summary>
|
||||
public class PlayfieldBorder : CompositeDrawable
|
||||
{
|
||||
public Bindable<PlayfieldBorderStyle> PlayfieldBorderStyle { get; } = new Bindable<PlayfieldBorderStyle>();
|
||||
|
||||
private const int fade_duration = 500;
|
||||
|
||||
private const float corner_length = 0.05f;
|
||||
private const float corner_thickness = 2;
|
||||
|
||||
public PlayfieldBorder()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Line(Direction.Horizontal)
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
},
|
||||
new Line(Direction.Horizontal)
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
},
|
||||
new Line(Direction.Horizontal)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
},
|
||||
new Line(Direction.Horizontal)
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
new Line(Direction.Vertical)
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
},
|
||||
new Line(Direction.Vertical)
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
},
|
||||
new Line(Direction.Vertical)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
},
|
||||
new Line(Direction.Vertical)
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
PlayfieldBorderStyle.BindValueChanged(updateStyle, true);
|
||||
}
|
||||
|
||||
private void updateStyle(ValueChangedEvent<PlayfieldBorderStyle> style)
|
||||
{
|
||||
switch (style.NewValue)
|
||||
{
|
||||
case UI.PlayfieldBorderStyle.None:
|
||||
this.FadeOut(fade_duration, Easing.OutQuint);
|
||||
foreach (var line in InternalChildren.OfType<Line>())
|
||||
line.TweenLength(0);
|
||||
|
||||
break;
|
||||
|
||||
case UI.PlayfieldBorderStyle.Corners:
|
||||
this.FadeIn(fade_duration, Easing.OutQuint);
|
||||
foreach (var line in InternalChildren.OfType<Line>())
|
||||
line.TweenLength(corner_length);
|
||||
|
||||
break;
|
||||
|
||||
case UI.PlayfieldBorderStyle.Full:
|
||||
this.FadeIn(fade_duration, Easing.OutQuint);
|
||||
foreach (var line in InternalChildren.OfType<Line>())
|
||||
line.TweenLength(0.5f);
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private class Line : Box
|
||||
{
|
||||
private readonly Direction direction;
|
||||
|
||||
public Line(Direction direction)
|
||||
{
|
||||
this.direction = direction;
|
||||
|
||||
Colour = Color4.White;
|
||||
// starting in relative avoids the framework thinking it knows best and setting the width to 1 initially.
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case Direction.Horizontal:
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Size = new Vector2(0, corner_thickness);
|
||||
break;
|
||||
|
||||
case Direction.Vertical:
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Size = new Vector2(corner_thickness, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void TweenLength(float value)
|
||||
{
|
||||
switch (direction)
|
||||
{
|
||||
case Direction.Horizontal:
|
||||
this.ResizeWidthTo(value, fade_duration, Easing.OutQuint);
|
||||
break;
|
||||
|
||||
case Direction.Vertical:
|
||||
this.ResizeHeightTo(value, fade_duration, Easing.OutQuint);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
12
osu.Game/Rulesets/UI/PlayfieldBorderStyle.cs
Normal file
12
osu.Game/Rulesets/UI/PlayfieldBorderStyle.cs
Normal file
@ -0,0 +1,12 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
public enum PlayfieldBorderStyle
|
||||
{
|
||||
None,
|
||||
Corners,
|
||||
Full
|
||||
}
|
||||
}
|
@ -1,32 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Edit.Compose.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a border around the playfield.
|
||||
/// </summary>
|
||||
public class EditorPlayfieldBorder : CompositeDrawable
|
||||
{
|
||||
public EditorPlayfieldBorder()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
Masking = true;
|
||||
BorderColour = Color4.White;
|
||||
BorderThickness = 2;
|
||||
|
||||
InternalChild = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -29,7 +29,7 @@ using osuTK;
|
||||
namespace osu.Game.Screens.Multi
|
||||
{
|
||||
[Cached]
|
||||
public class Multiplayer : OsuScreen, IOnlineComponent
|
||||
public class Multiplayer : OsuScreen
|
||||
{
|
||||
public override bool CursorVisible => (screenStack.CurrentScreen as IMultiplayerSubScreen)?.CursorVisible ?? true;
|
||||
|
||||
@ -146,15 +146,24 @@ namespace osu.Game.Screens.Multi
|
||||
screenStack.ScreenExited += screenExited;
|
||||
}
|
||||
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(IdleTracker idleTracker)
|
||||
{
|
||||
api.Register(this);
|
||||
apiState.BindTo(api.State);
|
||||
apiState.BindValueChanged(onlineStateChanged, true);
|
||||
|
||||
if (idleTracker != null)
|
||||
isIdle.BindTo(idleTracker.IsIdle);
|
||||
}
|
||||
|
||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||
{
|
||||
if (state.NewValue != APIState.Online)
|
||||
Schedule(forcefullyExit);
|
||||
});
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -199,12 +208,6 @@ namespace osu.Game.Screens.Multi
|
||||
Logger.Log($"Polling adjusted (listing: {roomManager.TimeBetweenListingPolls}, selection: {roomManager.TimeBetweenSelectionPolls})");
|
||||
}
|
||||
|
||||
public void APIStateChanged(IAPIProvider api, APIState state)
|
||||
{
|
||||
if (state != APIState.Online)
|
||||
Schedule(forcefullyExit);
|
||||
}
|
||||
|
||||
private void forcefullyExit()
|
||||
{
|
||||
// This is temporary since we don't currently have a way to force screens to be exited
|
||||
@ -371,12 +374,6 @@ namespace osu.Game.Screens.Multi
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
api?.Unregister(this);
|
||||
}
|
||||
|
||||
private class MultiplayerWaveContainer : WaveContainer
|
||||
{
|
||||
protected override bool StartHidden => true;
|
||||
|
@ -162,7 +162,7 @@ namespace osu.Game.Screens.Play
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Top = 20 },
|
||||
Current = mods
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
102
osu.Game/Screens/Play/EpilepsyWarning.cs
Normal file
102
osu.Game/Screens/Play/EpilepsyWarning.cs
Normal file
@ -0,0 +1,102 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Screens.Backgrounds;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
public class EpilepsyWarning : VisibilityContainer
|
||||
{
|
||||
public const double FADE_DURATION = 500;
|
||||
|
||||
private readonly BindableDouble trackVolumeOnEpilepsyWarning = new BindableDouble(1f);
|
||||
|
||||
private Track track;
|
||||
|
||||
public EpilepsyWarning()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Alpha = 0f;
|
||||
}
|
||||
|
||||
public BackgroundScreenBeatmap DimmableBackground { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, IBindable<WorkingBeatmap> beatmap)
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SpriteIcon
|
||||
{
|
||||
Colour = colours.Yellow,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.Solid.ExclamationTriangle,
|
||||
Size = new Vector2(50),
|
||||
},
|
||||
new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 25))
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
TextAnchor = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}.With(tfc =>
|
||||
{
|
||||
tfc.AddText("This beatmap contains scenes with ");
|
||||
tfc.AddText("rapidly flashing colours", s =>
|
||||
{
|
||||
s.Font = s.Font.With(weight: FontWeight.Bold);
|
||||
s.Colour = colours.Yellow;
|
||||
});
|
||||
tfc.AddText(".");
|
||||
|
||||
tfc.NewParagraph();
|
||||
tfc.AddText("Please take caution if you are affected by epilepsy.");
|
||||
}),
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
track = beatmap.Value.Track;
|
||||
track.AddAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning);
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
this.TransformBindableTo(trackVolumeOnEpilepsyWarning, 0.25, FADE_DURATION);
|
||||
|
||||
DimmableBackground?.FadeColour(OsuColour.Gray(0.5f), FADE_DURATION, Easing.OutQuint);
|
||||
|
||||
this.FadeIn(FADE_DURATION, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut() => this.FadeOut(FADE_DURATION);
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
track?.RemoveAdjustment(AdjustableProperty.Volume, trackVolumeOnEpilepsyWarning);
|
||||
}
|
||||
}
|
||||
}
|
@ -16,7 +16,6 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -53,7 +52,7 @@ namespace osu.Game.Screens.Play
|
||||
/// </summary>
|
||||
public Bindable<bool> ShowHud { get; } = new BindableBool();
|
||||
|
||||
private Bindable<bool> configShowHud;
|
||||
private Bindable<HUDVisibilityMode> configVisibilityMode;
|
||||
|
||||
private readonly Container visibilityContainer;
|
||||
|
||||
@ -66,7 +65,7 @@ namespace osu.Game.Screens.Play
|
||||
private readonly FillFlowContainer bottomRightElements;
|
||||
private readonly FillFlowContainer topRightElements;
|
||||
|
||||
private readonly Container mainUIElements;
|
||||
internal readonly IBindable<bool> IsBreakTime = new Bindable<bool>();
|
||||
|
||||
private IEnumerable<Drawable> hideTargets => new Drawable[] { visibilityContainer, KeyCounter };
|
||||
|
||||
@ -92,7 +91,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
mainUIElements = new Container
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
@ -170,9 +169,9 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
ModDisplay.Current.Value = mods;
|
||||
|
||||
configShowHud = config.GetBindable<bool>(OsuSetting.ShowInterface);
|
||||
configVisibilityMode = config.GetBindable<HUDVisibilityMode>(OsuSetting.HUDVisibilityMode);
|
||||
|
||||
if (!configShowHud.Value && !hasShownNotificationOnce)
|
||||
if (configVisibilityMode.Value == HUDVisibilityMode.Never && !hasShownNotificationOnce)
|
||||
{
|
||||
hasShownNotificationOnce = true;
|
||||
|
||||
@ -195,11 +194,8 @@ namespace osu.Game.Screens.Play
|
||||
ShowHealthbar.BindValueChanged(healthBar => HealthDisplay.FadeTo(healthBar.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING), true);
|
||||
ShowHud.BindValueChanged(visible => hideTargets.ForEach(d => d.FadeTo(visible.NewValue ? 1 : 0, FADE_DURATION, FADE_EASING)));
|
||||
|
||||
configShowHud.BindValueChanged(visible =>
|
||||
{
|
||||
if (!ShowHud.Disabled)
|
||||
ShowHud.Value = visible.NewValue;
|
||||
}, true);
|
||||
IsBreakTime.BindValueChanged(_ => updateVisibility());
|
||||
configVisibilityMode.BindValueChanged(_ => updateVisibility(), true);
|
||||
|
||||
replayLoaded.BindValueChanged(replayLoadedValueChanged, true);
|
||||
}
|
||||
@ -208,20 +204,41 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
base.Update();
|
||||
|
||||
float topRightOffset = 0;
|
||||
// HACK: for now align with the accuracy counter.
|
||||
// this is done for the sake of hacky legacy skins which extend the health bar to take up the full screen area.
|
||||
// it only works with the default skin due to padding offsetting it *just enough* to coexist.
|
||||
topRightElements.Y = ToLocalSpace(AccuracyCounter.Drawable.ScreenSpaceDrawQuad.BottomRight).Y;
|
||||
|
||||
// fetch the bottom-most position of any main ui element that is anchored to the top of the screen.
|
||||
// consider this kind of temporary.
|
||||
foreach (var d in mainUIElements)
|
||||
{
|
||||
if (d is SkinnableDrawable sd && (sd.Drawable.Anchor & Anchor.y0) > 0)
|
||||
topRightOffset = Math.Max(sd.Drawable.ScreenSpaceDrawQuad.BottomRight.Y, topRightOffset);
|
||||
}
|
||||
|
||||
topRightElements.Y = ToLocalSpace(new Vector2(0, topRightOffset)).Y;
|
||||
bottomRightElements.Y = -Progress.Height;
|
||||
}
|
||||
|
||||
private void updateVisibility()
|
||||
{
|
||||
if (ShowHud.Disabled)
|
||||
return;
|
||||
|
||||
switch (configVisibilityMode.Value)
|
||||
{
|
||||
case HUDVisibilityMode.Never:
|
||||
ShowHud.Value = false;
|
||||
break;
|
||||
|
||||
case HUDVisibilityMode.HideDuringBreaks:
|
||||
// always show during replay as we want the seek bar to be visible.
|
||||
ShowHud.Value = replayLoaded.Value || !IsBreakTime.Value;
|
||||
break;
|
||||
|
||||
case HUDVisibilityMode.HideDuringGameplay:
|
||||
// always show during replay as we want the seek bar to be visible.
|
||||
ShowHud.Value = replayLoaded.Value || IsBreakTime.Value;
|
||||
break;
|
||||
|
||||
case HUDVisibilityMode.Always:
|
||||
ShowHud.Value = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void replayLoadedValueChanged(ValueChangedEvent<bool> e)
|
||||
{
|
||||
PlayerSettingsOverlay.ReplayLoaded = e.NewValue;
|
||||
@ -238,6 +255,8 @@ namespace osu.Game.Screens.Play
|
||||
ModDisplay.Delay(2000).FadeOut(200);
|
||||
KeyCounter.Margin = new MarginPadding(10);
|
||||
}
|
||||
|
||||
updateVisibility();
|
||||
}
|
||||
|
||||
protected virtual void BindDrawableRuleset(DrawableRuleset drawableRuleset)
|
||||
@ -258,7 +277,9 @@ namespace osu.Game.Screens.Play
|
||||
switch (e.Key)
|
||||
{
|
||||
case Key.Tab:
|
||||
configShowHud.Value = !configShowHud.Value;
|
||||
configVisibilityMode.Value = configVisibilityMode.Value != HUDVisibilityMode.Never
|
||||
? HUDVisibilityMode.Never
|
||||
: HUDVisibilityMode.HideDuringGameplay;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -657,6 +657,7 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
// bind component bindables.
|
||||
Background.IsBreakTime.BindTo(breakTracker.IsBreakTime);
|
||||
HUDOverlay.IsBreakTime.BindTo(breakTracker.IsBreakTime);
|
||||
DimmableStoryboard.IsBreakTime.BindTo(breakTracker.IsBreakTime);
|
||||
|
||||
Background.StoryboardReplacesBackground.BindTo(storyboardReplacesBackground);
|
||||
|
@ -4,12 +4,14 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Transforms;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Threading;
|
||||
@ -90,6 +92,9 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
private ScheduledDelegate scheduledPushPlayer;
|
||||
|
||||
[CanBeNull]
|
||||
private EpilepsyWarning epilepsyWarning;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private NotificationOverlay notificationOverlay { get; set; }
|
||||
|
||||
@ -138,6 +143,15 @@ namespace osu.Game.Screens.Play
|
||||
},
|
||||
idleTracker = new IdleTracker(750)
|
||||
});
|
||||
|
||||
if (Beatmap.Value.BeatmapInfo.EpilepsyWarning)
|
||||
{
|
||||
AddInternal(epilepsyWarning = new EpilepsyWarning
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -153,6 +167,9 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
base.OnEntering(last);
|
||||
|
||||
if (epilepsyWarning != null)
|
||||
epilepsyWarning.DimmableBackground = Background;
|
||||
|
||||
content.ScaleTo(0.7f);
|
||||
Background?.FadeColour(Color4.White, 800, Easing.OutQuint);
|
||||
|
||||
@ -306,7 +323,29 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
contentOut();
|
||||
|
||||
this.Delay(250).Schedule(() =>
|
||||
TransformSequence<PlayerLoader> pushSequence = this.Delay(250);
|
||||
|
||||
// only show if the warning was created (i.e. the beatmap needs it)
|
||||
// and this is not a restart of the map (the warning expires after first load).
|
||||
if (epilepsyWarning?.IsAlive == true)
|
||||
{
|
||||
const double epilepsy_display_length = 3000;
|
||||
|
||||
pushSequence.Schedule(() =>
|
||||
{
|
||||
epilepsyWarning.State.Value = Visibility.Visible;
|
||||
|
||||
this.Delay(epilepsy_display_length).Schedule(() =>
|
||||
{
|
||||
epilepsyWarning.Hide();
|
||||
epilepsyWarning.Expire();
|
||||
});
|
||||
});
|
||||
|
||||
pushSequence.Delay(epilepsy_display_length);
|
||||
}
|
||||
|
||||
pushSequence.Schedule(() =>
|
||||
{
|
||||
if (!this.IsCurrentScreen()) return;
|
||||
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -51,7 +52,7 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(BeatmapDifficultyManager beatmapDifficultyManager)
|
||||
{
|
||||
var beatmap = score.Beatmap;
|
||||
var metadata = beatmap.BeatmapSet?.Metadata ?? beatmap.Metadata;
|
||||
@ -138,7 +139,7 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
Spacing = new Vector2(5, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new StarRatingDisplay(beatmap)
|
||||
new StarRatingDisplay(beatmapDifficultyManager.GetDifficulty(beatmap, score.Ruleset, score.Mods))
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
|
@ -22,29 +22,30 @@ namespace osu.Game.Screens.Ranking.Expanded
|
||||
/// </summary>
|
||||
public class StarRatingDisplay : CompositeDrawable
|
||||
{
|
||||
private readonly BeatmapInfo beatmap;
|
||||
private readonly StarDifficulty difficulty;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="StarRatingDisplay"/>.
|
||||
/// Creates a new <see cref="StarRatingDisplay"/> using an already computed <see cref="StarDifficulty"/>.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The <see cref="BeatmapInfo"/> to display the star difficulty of.</param>
|
||||
public StarRatingDisplay(BeatmapInfo beatmap)
|
||||
/// <param name="starDifficulty">The already computed <see cref="StarDifficulty"/> to display the star difficulty of.</param>
|
||||
public StarRatingDisplay(StarDifficulty starDifficulty)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
AutoSizeAxes = Axes.Both;
|
||||
difficulty = starDifficulty;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
private void load(OsuColour colours, BeatmapDifficultyManager difficultyManager)
|
||||
{
|
||||
var starRatingParts = beatmap.StarDifficulty.ToString("0.00", CultureInfo.InvariantCulture).Split('.');
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
var starRatingParts = difficulty.Stars.ToString("0.00", CultureInfo.InvariantCulture).Split('.');
|
||||
string wholePart = starRatingParts[0];
|
||||
string fractionPart = starRatingParts[1];
|
||||
string separator = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator;
|
||||
|
||||
ColourInfo backgroundColour = beatmap.DifficultyRating == DifficultyRating.ExpertPlus
|
||||
ColourInfo backgroundColour = difficulty.DifficultyRating == DifficultyRating.ExpertPlus
|
||||
? ColourInfo.GradientVertical(Color4Extensions.FromHex("#C1C1C1"), Color4Extensions.FromHex("#595959"))
|
||||
: (ColourInfo)colours.ForDifficultyRating(beatmap.DifficultyRating);
|
||||
: (ColourInfo)colours.ForDifficultyRating(difficulty.DifficultyRating);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
|
@ -1,29 +1,29 @@
|
||||
// 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 osuTK;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Configuration;
|
||||
using osuTK.Input;
|
||||
using osu.Framework.Utils;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Caching;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Screens.Select.Carousel;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.Select
|
||||
{
|
||||
@ -74,6 +74,18 @@ namespace osu.Game.Screens.Select
|
||||
public override bool PropagatePositionalInputSubTree => AllowSelection;
|
||||
public override bool PropagateNonPositionalInputSubTree => AllowSelection;
|
||||
|
||||
private (int first, int last) displayedRange;
|
||||
|
||||
/// <summary>
|
||||
/// Extend the range to retain already loaded pooled drawables.
|
||||
/// </summary>
|
||||
private const float distance_offscreen_before_unload = 1024;
|
||||
|
||||
/// <summary>
|
||||
/// Extend the range to update positions / retrieve pooled drawables outside of visible range.
|
||||
/// </summary>
|
||||
private const float distance_offscreen_to_preload = 512; // todo: adjust this appropriately once we can make set panel contents load while off-screen.
|
||||
|
||||
/// <summary>
|
||||
/// Whether carousel items have completed asynchronously loaded.
|
||||
/// </summary>
|
||||
@ -94,16 +106,13 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
CarouselRoot newRoot = new CarouselRoot(this);
|
||||
|
||||
beatmapSets.Select(createCarouselSet).Where(g => g != null).ForEach(newRoot.AddChild);
|
||||
|
||||
// preload drawables as the ctor overhead is quite high currently.
|
||||
_ = newRoot.Drawables;
|
||||
newRoot.AddChildren(beatmapSets.Select(createCarouselSet).Where(g => g != null));
|
||||
|
||||
root = newRoot;
|
||||
if (selectedBeatmapSet != null && !beatmapSets.Contains(selectedBeatmapSet.BeatmapSet))
|
||||
selectedBeatmapSet = null;
|
||||
|
||||
scrollableContent.Clear(false);
|
||||
ScrollableContent.Clear(false);
|
||||
itemsCache.Invalidate();
|
||||
scrollPositionCache.Invalidate();
|
||||
|
||||
@ -118,11 +127,12 @@ namespace osu.Game.Screens.Select
|
||||
});
|
||||
}
|
||||
|
||||
private readonly List<float> yPositions = new List<float>();
|
||||
private readonly List<CarouselItem> visibleItems = new List<CarouselItem>();
|
||||
|
||||
private readonly Cached itemsCache = new Cached();
|
||||
private readonly Cached scrollPositionCache = new Cached();
|
||||
|
||||
private readonly Container<DrawableCarouselItem> scrollableContent;
|
||||
protected readonly Container<DrawableCarouselItem> ScrollableContent;
|
||||
|
||||
public Bindable<bool> RightClickScrollingEnabled = new Bindable<bool>();
|
||||
|
||||
@ -130,8 +140,6 @@ namespace osu.Game.Screens.Select
|
||||
private readonly List<CarouselBeatmapSet> previouslyVisitedRandomSets = new List<CarouselBeatmapSet>();
|
||||
private readonly Stack<CarouselBeatmap> randomSelectedBeatmaps = new Stack<CarouselBeatmap>();
|
||||
|
||||
protected List<DrawableCarouselItem> Items = new List<DrawableCarouselItem>();
|
||||
|
||||
private CarouselRoot root;
|
||||
|
||||
private IBindable<WeakReference<BeatmapSetInfo>> itemUpdated;
|
||||
@ -139,6 +147,8 @@ namespace osu.Game.Screens.Select
|
||||
private IBindable<WeakReference<BeatmapInfo>> itemHidden;
|
||||
private IBindable<WeakReference<BeatmapInfo>> itemRestored;
|
||||
|
||||
private readonly DrawablePool<DrawableCarouselBeatmapSet> setPool = new DrawablePool<DrawableCarouselBeatmapSet>(100);
|
||||
|
||||
public BeatmapCarousel()
|
||||
{
|
||||
root = new CarouselRoot(this);
|
||||
@ -149,9 +159,13 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
Masking = false,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = scrollableContent = new Container<DrawableCarouselItem>
|
||||
Children = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
setPool,
|
||||
ScrollableContent = new Container<DrawableCarouselItem>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -178,7 +192,8 @@ namespace osu.Game.Screens.Select
|
||||
itemRestored = beatmaps.BeatmapRestored.GetBoundCopy();
|
||||
itemRestored.BindValueChanged(beatmapRestored);
|
||||
|
||||
loadBeatmapSets(GetLoadableBeatmaps());
|
||||
if (!beatmapSets.Any())
|
||||
loadBeatmapSets(GetLoadableBeatmaps());
|
||||
}
|
||||
|
||||
protected virtual IEnumerable<BeatmapSetInfo> GetLoadableBeatmaps() => beatmaps.GetAllUsableBeatmapSetsEnumerable(IncludedDetails.AllButFiles);
|
||||
@ -558,71 +573,101 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!itemsCache.IsValid)
|
||||
updateItems();
|
||||
bool revalidateItems = !itemsCache.IsValid;
|
||||
|
||||
// Remove all items that should no longer be on-screen
|
||||
scrollableContent.RemoveAll(p => p.Y < visibleUpperBound - p.DrawHeight || p.Y > visibleBottomBound || !p.IsPresent);
|
||||
// First we iterate over all non-filtered carousel items and populate their
|
||||
// vertical position data.
|
||||
if (revalidateItems)
|
||||
updateYPositions();
|
||||
|
||||
// Find index range of all items that should be on-screen
|
||||
Trace.Assert(Items.Count == yPositions.Count);
|
||||
// This data is consumed to find the currently displayable range.
|
||||
// This is the range we want to keep drawables for, and should exceed the visible range slightly to avoid drawable churn.
|
||||
var newDisplayRange = getDisplayRange();
|
||||
|
||||
int firstIndex = yPositions.BinarySearch(visibleUpperBound - DrawableCarouselItem.MAX_HEIGHT);
|
||||
if (firstIndex < 0) firstIndex = ~firstIndex;
|
||||
int lastIndex = yPositions.BinarySearch(visibleBottomBound);
|
||||
if (lastIndex < 0) lastIndex = ~lastIndex;
|
||||
|
||||
int notVisibleCount = 0;
|
||||
|
||||
// Add those items within the previously found index range that should be displayed.
|
||||
for (int i = firstIndex; i < lastIndex; ++i)
|
||||
// If the filtered items or visible range has changed, pooling requirements need to be checked.
|
||||
// This involves fetching new items from the pool, returning no-longer required items.
|
||||
if (revalidateItems || newDisplayRange != displayedRange)
|
||||
{
|
||||
DrawableCarouselItem item = Items[i];
|
||||
displayedRange = newDisplayRange;
|
||||
|
||||
if (!item.Item.Visible)
|
||||
if (visibleItems.Count > 0)
|
||||
{
|
||||
if (!item.IsPresent)
|
||||
notVisibleCount++;
|
||||
continue;
|
||||
}
|
||||
var toDisplay = visibleItems.GetRange(displayedRange.first, displayedRange.last - displayedRange.first + 1);
|
||||
|
||||
float depth = i + (item is DrawableCarouselBeatmapSet ? -Items.Count : 0);
|
||||
|
||||
// Only add if we're not already part of the content.
|
||||
if (!scrollableContent.Contains(item))
|
||||
{
|
||||
// Makes sure headers are always _below_ items,
|
||||
// and depth flows downward.
|
||||
item.Depth = depth;
|
||||
|
||||
switch (item.LoadState)
|
||||
foreach (var panel in ScrollableContent.Children)
|
||||
{
|
||||
case LoadState.NotLoaded:
|
||||
LoadComponentAsync(item);
|
||||
break;
|
||||
if (toDisplay.Remove(panel.Item))
|
||||
{
|
||||
// panel already displayed.
|
||||
continue;
|
||||
}
|
||||
|
||||
case LoadState.Loading:
|
||||
break;
|
||||
|
||||
default:
|
||||
scrollableContent.Add(item);
|
||||
break;
|
||||
// panel loaded as drawable but not required by visible range.
|
||||
// remove but only if too far off-screen
|
||||
if (panel.Y + panel.DrawHeight < visibleUpperBound - distance_offscreen_before_unload || panel.Y > visibleBottomBound + distance_offscreen_before_unload)
|
||||
{
|
||||
// may want a fade effect here (could be seen if a huge change happens, like a set with 20 difficulties becomes selected).
|
||||
panel.ClearTransforms();
|
||||
panel.Expire();
|
||||
}
|
||||
}
|
||||
|
||||
// Add those items within the previously found index range that should be displayed.
|
||||
foreach (var item in toDisplay)
|
||||
{
|
||||
var panel = setPool.Get(p => p.Item = item);
|
||||
|
||||
panel.Depth = item.CarouselYPosition;
|
||||
panel.Y = item.CarouselYPosition;
|
||||
|
||||
ScrollableContent.Add(panel);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
scrollableContent.ChangeChildDepth(item, depth);
|
||||
}
|
||||
}
|
||||
|
||||
// this is not actually useful right now, but once we have groups may well be.
|
||||
if (notVisibleCount > 50)
|
||||
itemsCache.Invalidate();
|
||||
// Finally, if the filtered items have changed, animate drawables to their new locations.
|
||||
// This is common if a selected/collapsed state has changed.
|
||||
if (revalidateItems)
|
||||
{
|
||||
foreach (DrawableCarouselItem panel in ScrollableContent.Children)
|
||||
{
|
||||
panel.MoveToY(panel.Item.CarouselYPosition, 800, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
// Update externally controlled state of currently visible items
|
||||
// (e.g. x-offset and opacity).
|
||||
foreach (DrawableCarouselItem p in scrollableContent.Children)
|
||||
updateItem(p);
|
||||
// Update externally controlled state of currently visible items (e.g. x-offset and opacity).
|
||||
// This is a per-frame update on all drawable panels.
|
||||
foreach (DrawableCarouselItem item in ScrollableContent.Children)
|
||||
{
|
||||
updateItem(item);
|
||||
|
||||
if (item is DrawableCarouselBeatmapSet set)
|
||||
{
|
||||
foreach (var diff in set.DrawableBeatmaps)
|
||||
updateItem(diff, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private readonly CarouselBoundsItem carouselBoundsItem = new CarouselBoundsItem();
|
||||
|
||||
private (int firstIndex, int lastIndex) getDisplayRange()
|
||||
{
|
||||
// Find index range of all items that should be on-screen
|
||||
carouselBoundsItem.CarouselYPosition = visibleUpperBound - distance_offscreen_to_preload;
|
||||
int firstIndex = visibleItems.BinarySearch(carouselBoundsItem);
|
||||
if (firstIndex < 0) firstIndex = ~firstIndex;
|
||||
|
||||
carouselBoundsItem.CarouselYPosition = visibleBottomBound + distance_offscreen_to_preload;
|
||||
int lastIndex = visibleItems.BinarySearch(carouselBoundsItem);
|
||||
if (lastIndex < 0) lastIndex = ~lastIndex;
|
||||
|
||||
// as we can't be 100% sure on the size of individual carousel drawables,
|
||||
// always play it safe and extend bounds by one.
|
||||
firstIndex = Math.Max(0, firstIndex - 1);
|
||||
lastIndex = Math.Clamp(lastIndex + 1, firstIndex, Math.Max(0, visibleItems.Count - 1));
|
||||
|
||||
return (firstIndex, lastIndex);
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
@ -633,15 +678,6 @@ namespace osu.Game.Screens.Select
|
||||
updateScrollPosition();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
// aggressively dispose "off-screen" items to reduce GC pressure.
|
||||
foreach (var i in Items)
|
||||
i.Dispose();
|
||||
}
|
||||
|
||||
private void beatmapRemoved(ValueChangedEvent<WeakReference<BeatmapSetInfo>> weakItem)
|
||||
{
|
||||
if (weakItem.NewValue.TryGetTarget(out var item))
|
||||
@ -698,79 +734,62 @@ namespace osu.Game.Screens.Select
|
||||
return set;
|
||||
}
|
||||
|
||||
private const float panel_padding = 5;
|
||||
|
||||
/// <summary>
|
||||
/// Computes the target Y positions for every item in the carousel.
|
||||
/// </summary>
|
||||
/// <returns>The Y position of the currently selected item.</returns>
|
||||
private void updateItems()
|
||||
private void updateYPositions()
|
||||
{
|
||||
Items = root.Drawables.ToList();
|
||||
|
||||
yPositions.Clear();
|
||||
visibleItems.Clear();
|
||||
|
||||
float currentY = visibleHalfHeight;
|
||||
DrawableCarouselBeatmapSet lastSet = null;
|
||||
|
||||
scrollTarget = null;
|
||||
|
||||
foreach (DrawableCarouselItem d in Items)
|
||||
foreach (CarouselItem item in root.Children)
|
||||
{
|
||||
if (d.IsPresent)
|
||||
if (item.Filtered.Value)
|
||||
continue;
|
||||
|
||||
switch (item)
|
||||
{
|
||||
switch (d)
|
||||
case CarouselBeatmapSet set:
|
||||
{
|
||||
case DrawableCarouselBeatmapSet set:
|
||||
{
|
||||
lastSet = set;
|
||||
visibleItems.Add(set);
|
||||
set.CarouselYPosition = currentY;
|
||||
|
||||
set.MoveToX(set.Item.State.Value == CarouselItemState.Selected ? -100 : 0, 500, Easing.OutExpo);
|
||||
set.MoveToY(currentY, 750, Easing.OutExpo);
|
||||
break;
|
||||
if (item.State.Value == CarouselItemState.Selected)
|
||||
{
|
||||
// scroll position at currentY makes the set panel appear at the very top of the carousel's screen space
|
||||
// move down by half of visible height (height of the carousel's visible extent, including semi-transparent areas)
|
||||
// then reapply the top semi-transparent area (because carousel's screen space starts below it)
|
||||
scrollTarget = currentY + DrawableCarouselBeatmapSet.HEIGHT - visibleHalfHeight + BleedTop;
|
||||
|
||||
foreach (var b in set.Beatmaps)
|
||||
{
|
||||
if (!b.Visible)
|
||||
continue;
|
||||
|
||||
if (b.State.Value == CarouselItemState.Selected)
|
||||
{
|
||||
scrollTarget += b.TotalHeight / 2;
|
||||
break;
|
||||
}
|
||||
|
||||
scrollTarget += b.TotalHeight;
|
||||
}
|
||||
}
|
||||
|
||||
case DrawableCarouselBeatmap beatmap:
|
||||
{
|
||||
if (beatmap.Item.State.Value == CarouselItemState.Selected)
|
||||
// scroll position at currentY makes the set panel appear at the very top of the carousel's screen space
|
||||
// move down by half of visible height (height of the carousel's visible extent, including semi-transparent areas)
|
||||
// then reapply the top semi-transparent area (because carousel's screen space starts below it)
|
||||
// and finally add half of the panel's own height to achieve vertical centering of the panel itself
|
||||
scrollTarget = currentY - visibleHalfHeight + BleedTop + beatmap.DrawHeight / 2;
|
||||
|
||||
void performMove(float y, float? startY = null)
|
||||
{
|
||||
if (startY != null) beatmap.MoveTo(new Vector2(0, startY.Value));
|
||||
beatmap.MoveToX(beatmap.Item.State.Value == CarouselItemState.Selected ? -50 : 0, 500, Easing.OutExpo);
|
||||
beatmap.MoveToY(y, 750, Easing.OutExpo);
|
||||
}
|
||||
|
||||
Debug.Assert(lastSet != null);
|
||||
|
||||
float? setY = null;
|
||||
if (!d.IsLoaded || beatmap.Alpha == 0) // can't use IsPresent due to DrawableCarouselItem override.
|
||||
setY = lastSet.Y + lastSet.DrawHeight + 5;
|
||||
|
||||
if (d.IsLoaded)
|
||||
performMove(currentY, setY);
|
||||
else
|
||||
{
|
||||
float y = currentY;
|
||||
d.OnLoadComplete += _ => performMove(y, setY);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
currentY += set.TotalHeight + panel_padding;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
yPositions.Add(currentY);
|
||||
|
||||
if (d.Item.Visible)
|
||||
currentY += d.DrawHeight + 5;
|
||||
}
|
||||
|
||||
currentY += visibleHalfHeight;
|
||||
scrollableContent.Height = currentY;
|
||||
ScrollableContent.Height = currentY;
|
||||
|
||||
if (BeatmapSetsLoaded && (selectedBeatmapSet == null || selectedBeatmap == null || selectedBeatmapSet.State.Value != CarouselItemState.Selected))
|
||||
{
|
||||
@ -821,21 +840,31 @@ namespace osu.Game.Screens.Select
|
||||
/// Update a item's x position and multiplicative alpha based on its y position and
|
||||
/// the current scroll position.
|
||||
/// </summary>
|
||||
/// <param name="p">The item to be updated.</param>
|
||||
private void updateItem(DrawableCarouselItem p)
|
||||
/// <param name="item">The item to be updated.</param>
|
||||
/// <param name="parent">For nested items, the parent of the item to be updated.</param>
|
||||
private void updateItem(DrawableCarouselItem item, DrawableCarouselItem parent = null)
|
||||
{
|
||||
float itemDrawY = p.Position.Y - visibleUpperBound + p.DrawHeight / 2;
|
||||
Vector2 posInScroll = ScrollableContent.ToLocalSpace(item.Header.ScreenSpaceDrawQuad.Centre);
|
||||
float itemDrawY = posInScroll.Y - visibleUpperBound;
|
||||
float dist = Math.Abs(1f - itemDrawY / visibleHalfHeight);
|
||||
|
||||
// Setting the origin position serves as an additive position on top of potential
|
||||
// local transformation we may want to apply (e.g. when a item gets selected, we
|
||||
// may want to smoothly transform it leftwards.)
|
||||
p.OriginPosition = new Vector2(-offsetX(dist, visibleHalfHeight), 0);
|
||||
// adjusting the item's overall X position can cause it to become masked away when
|
||||
// child items (difficulties) are still visible.
|
||||
item.Header.X = offsetX(dist, visibleHalfHeight) - (parent?.X ?? 0);
|
||||
|
||||
// We are applying a multiplicative alpha (which is internally done by nesting an
|
||||
// additional container and setting that container's alpha) such that we can
|
||||
// layer transformations on top, with a similar reasoning to the previous comment.
|
||||
p.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1));
|
||||
// layer alpha transformations on top.
|
||||
item.SetMultiplicativeAlpha(Math.Clamp(1.75f - 1.5f * dist, 0, 1));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A carousel item strictly used for binary search purposes.
|
||||
/// </summary>
|
||||
private class CarouselBoundsItem : CarouselItem
|
||||
{
|
||||
public override DrawableCarouselItem CreateDrawableRepresentation() =>
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private class CarouselRoot : CarouselGroupEagerSelect
|
||||
@ -869,6 +898,7 @@ namespace osu.Game.Screens.Select
|
||||
/// </summary>
|
||||
public bool UserScrolling { get; private set; }
|
||||
|
||||
// ReSharper disable once OptionalParameterHierarchyMismatch 2020.3 EAP4 bug. (https://youtrack.jetbrains.com/issue/RSRP-481535?p=RIDER-51910)
|
||||
protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default)
|
||||
{
|
||||
UserScrolling = true;
|
||||
|
@ -39,6 +39,11 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
private readonly IBindable<RulesetInfo> ruleset = new Bindable<RulesetInfo>();
|
||||
|
||||
[Resolved]
|
||||
private BeatmapDifficultyManager difficultyManager { get; set; }
|
||||
|
||||
private IBindable<StarDifficulty> beatmapDifficulty;
|
||||
|
||||
protected BufferedWedgeInfo Info;
|
||||
|
||||
public BeatmapInfoWedge()
|
||||
@ -88,6 +93,11 @@ namespace osu.Game.Screens.Select
|
||||
if (beatmap == value) return;
|
||||
|
||||
beatmap = value;
|
||||
|
||||
beatmapDifficulty?.UnbindAll();
|
||||
beatmapDifficulty = difficultyManager.GetBindableDifficulty(beatmap.BeatmapInfo);
|
||||
beatmapDifficulty.BindValueChanged(_ => updateDisplay());
|
||||
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
@ -113,7 +123,7 @@ namespace osu.Game.Screens.Select
|
||||
return;
|
||||
}
|
||||
|
||||
LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value)
|
||||
LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, beatmapDifficulty.Value)
|
||||
{
|
||||
Shear = -Shear,
|
||||
Depth = Info?.Depth + 1 ?? 0
|
||||
@ -141,12 +151,14 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
private readonly WorkingBeatmap beatmap;
|
||||
private readonly RulesetInfo ruleset;
|
||||
private readonly StarDifficulty starDifficulty;
|
||||
|
||||
public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset)
|
||||
public BufferedWedgeInfo(WorkingBeatmap beatmap, RulesetInfo userRuleset, StarDifficulty difficulty)
|
||||
: base(pixelSnapping: true)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
ruleset = userRuleset ?? beatmap.BeatmapInfo.Ruleset;
|
||||
starDifficulty = difficulty;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -190,7 +202,7 @@ namespace osu.Game.Screens.Select
|
||||
},
|
||||
},
|
||||
},
|
||||
new DifficultyColourBar(beatmapInfo)
|
||||
new DifficultyColourBar(starDifficulty)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 20,
|
||||
@ -226,7 +238,7 @@ namespace osu.Game.Screens.Select
|
||||
Shear = wedged_container_shear,
|
||||
Children = new[]
|
||||
{
|
||||
createStarRatingDisplay(beatmapInfo).With(display =>
|
||||
createStarRatingDisplay(starDifficulty).With(display =>
|
||||
{
|
||||
display.Anchor = Anchor.TopRight;
|
||||
display.Origin = Anchor.TopRight;
|
||||
@ -293,8 +305,8 @@ namespace osu.Game.Screens.Select
|
||||
StatusPill.Hide();
|
||||
}
|
||||
|
||||
private static Drawable createStarRatingDisplay(BeatmapInfo beatmapInfo) => beatmapInfo.StarDifficulty > 0
|
||||
? new StarRatingDisplay(beatmapInfo)
|
||||
private static Drawable createStarRatingDisplay(StarDifficulty difficulty) => difficulty.Stars > 0
|
||||
? new StarRatingDisplay(difficulty)
|
||||
{
|
||||
Margin = new MarginPadding { Bottom = 5 }
|
||||
}
|
||||
@ -447,11 +459,11 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
private class DifficultyColourBar : Container
|
||||
{
|
||||
private readonly BeatmapInfo beatmap;
|
||||
private readonly StarDifficulty difficulty;
|
||||
|
||||
public DifficultyColourBar(BeatmapInfo beatmap)
|
||||
public DifficultyColourBar(StarDifficulty difficulty)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
this.difficulty = difficulty;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -459,7 +471,7 @@ namespace osu.Game.Screens.Select
|
||||
{
|
||||
const float full_opacity_ratio = 0.7f;
|
||||
|
||||
var difficultyColour = colours.ForDifficultyRating(beatmap.DifficultyRating);
|
||||
var difficultyColour = colours.ForDifficultyRating(difficulty.DifficultyRating);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
|
@ -10,6 +10,8 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
public class CarouselBeatmap : CarouselItem
|
||||
{
|
||||
public override float TotalHeight => DrawableCarouselBeatmap.HEIGHT;
|
||||
|
||||
public readonly BeatmapInfo Beatmap;
|
||||
|
||||
public CarouselBeatmap(BeatmapInfo beatmap)
|
||||
@ -18,7 +20,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
State.Value = CarouselItemState.Collapsed;
|
||||
}
|
||||
|
||||
protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmap(this);
|
||||
public override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmap(this);
|
||||
|
||||
public override void Filter(FilterCriteria criteria)
|
||||
{
|
||||
|
@ -12,6 +12,21 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
public class CarouselBeatmapSet : CarouselGroupEagerSelect
|
||||
{
|
||||
public override float TotalHeight
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (State.Value)
|
||||
{
|
||||
case CarouselItemState.Selected:
|
||||
return DrawableCarouselBeatmapSet.HEIGHT + Children.Count(c => c.Visible) * DrawableCarouselBeatmap.HEIGHT;
|
||||
|
||||
default:
|
||||
return DrawableCarouselBeatmapSet.HEIGHT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<CarouselBeatmap> Beatmaps => InternalChildren.OfType<CarouselBeatmap>();
|
||||
|
||||
public BeatmapSetInfo BeatmapSet;
|
||||
@ -28,8 +43,6 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
.ForEach(AddChild);
|
||||
}
|
||||
|
||||
protected override DrawableCarouselItem CreateDrawableRepresentation() => new DrawableCarouselBeatmapSet(this);
|
||||
|
||||
protected override CarouselItem GetNextToSelect()
|
||||
{
|
||||
if (LastSelected == null)
|
||||
|
@ -11,7 +11,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
/// </summary>
|
||||
public class CarouselGroup : CarouselItem
|
||||
{
|
||||
protected override DrawableCarouselItem CreateDrawableRepresentation() => null;
|
||||
public override DrawableCarouselItem CreateDrawableRepresentation() => null;
|
||||
|
||||
public IReadOnlyList<CarouselItem> Children => InternalChildren;
|
||||
|
||||
@ -23,22 +23,6 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
/// </summary>
|
||||
private ulong currentChildID;
|
||||
|
||||
public override List<DrawableCarouselItem> Drawables
|
||||
{
|
||||
get
|
||||
{
|
||||
var drawables = base.Drawables;
|
||||
|
||||
// if we are explicitly not present, don't ever present children.
|
||||
// without this check, children drawables can potentially be presented without their group header.
|
||||
if (DrawableRepresentation.Value?.IsPresent == false) return drawables;
|
||||
|
||||
foreach (var c in InternalChildren)
|
||||
drawables.AddRange(c.Drawables);
|
||||
return drawables;
|
||||
}
|
||||
}
|
||||
|
||||
public virtual void RemoveChild(CarouselItem i)
|
||||
{
|
||||
InternalChildren.Remove(i);
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace osu.Game.Screens.Select.Carousel
|
||||
@ -54,6 +55,14 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
updateSelectedIndex();
|
||||
}
|
||||
|
||||
public void AddChildren(IEnumerable<CarouselItem> items)
|
||||
{
|
||||
foreach (var i in items)
|
||||
base.AddChild(i);
|
||||
|
||||
attemptSelection();
|
||||
}
|
||||
|
||||
public override void AddChild(CarouselItem i)
|
||||
{
|
||||
base.AddChild(i);
|
||||
|
114
osu.Game/Screens/Select/Carousel/CarouselHeader.cs
Normal file
114
osu.Game/Screens/Select/Carousel/CarouselHeader.cs
Normal file
@ -0,0 +1,114 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
public class CarouselHeader : Container
|
||||
{
|
||||
private SampleChannel sampleHover;
|
||||
|
||||
private readonly Box hoverLayer;
|
||||
|
||||
public Container BorderContainer;
|
||||
|
||||
public readonly Bindable<CarouselItemState> State = new Bindable<CarouselItemState>(CarouselItemState.NotSelected);
|
||||
|
||||
protected override Container<Drawable> Content { get; } = new Container { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
public CarouselHeader()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = DrawableCarouselItem.MAX_HEIGHT;
|
||||
|
||||
InternalChild = BorderContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 10,
|
||||
BorderColour = new Color4(221, 255, 255, 255),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Content,
|
||||
hoverLayer = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
Blending = BlendingParameters.Additive,
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, OsuColour colours)
|
||||
{
|
||||
sampleHover = audio.Samples.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}");
|
||||
hoverLayer.Colour = colours.Blue.Opacity(0.1f);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
State.BindValueChanged(updateState, true);
|
||||
}
|
||||
|
||||
private void updateState(ValueChangedEvent<CarouselItemState> state)
|
||||
{
|
||||
switch (state.NewValue)
|
||||
{
|
||||
case CarouselItemState.Collapsed:
|
||||
case CarouselItemState.NotSelected:
|
||||
BorderContainer.BorderThickness = 0;
|
||||
BorderContainer.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Offset = new Vector2(1),
|
||||
Radius = 10,
|
||||
Colour = Color4.Black.Opacity(100),
|
||||
};
|
||||
break;
|
||||
|
||||
case CarouselItemState.Selected:
|
||||
BorderContainer.BorderThickness = 2.5f;
|
||||
BorderContainer.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Colour = new Color4(130, 204, 255, 150),
|
||||
Radius = 20,
|
||||
Roundness = 10,
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
sampleHover?.Play();
|
||||
|
||||
hoverLayer.FadeIn(100, Easing.OutQuint);
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
hoverLayer.FadeOut(1000, Easing.OutQuint);
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
}
|
||||
}
|
@ -2,13 +2,19 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Bindables;
|
||||
|
||||
namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
public abstract class CarouselItem
|
||||
public abstract class CarouselItem : IComparable<CarouselItem>
|
||||
{
|
||||
public virtual float TotalHeight => 0;
|
||||
|
||||
/// <summary>
|
||||
/// An externally defined value used to determine this item's vertical display offset relative to the carousel.
|
||||
/// </summary>
|
||||
public float CarouselYPosition;
|
||||
|
||||
public readonly BindableBool Filtered = new BindableBool();
|
||||
|
||||
public readonly Bindable<CarouselItemState> State = new Bindable<CarouselItemState>(CarouselItemState.NotSelected);
|
||||
@ -18,23 +24,8 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
/// </summary>
|
||||
public bool Visible => State.Value != CarouselItemState.Collapsed && !Filtered.Value;
|
||||
|
||||
public virtual List<DrawableCarouselItem> Drawables
|
||||
{
|
||||
get
|
||||
{
|
||||
var items = new List<DrawableCarouselItem>();
|
||||
|
||||
var self = DrawableRepresentation.Value;
|
||||
if (self?.IsPresent == true) items.Add(self);
|
||||
|
||||
return items;
|
||||
}
|
||||
}
|
||||
|
||||
protected CarouselItem()
|
||||
{
|
||||
DrawableRepresentation = new Lazy<DrawableCarouselItem>(CreateDrawableRepresentation);
|
||||
|
||||
Filtered.ValueChanged += filtered =>
|
||||
{
|
||||
if (filtered.NewValue && State.Value == CarouselItemState.Selected)
|
||||
@ -42,23 +33,23 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
};
|
||||
}
|
||||
|
||||
protected readonly Lazy<DrawableCarouselItem> DrawableRepresentation;
|
||||
|
||||
/// <summary>
|
||||
/// Used as a default sort method for <see cref="CarouselItem"/>s of differing types.
|
||||
/// </summary>
|
||||
internal ulong ChildID;
|
||||
|
||||
/// <summary>
|
||||
/// Create a fresh drawable version of this item. If you wish to consume the current representation, use <see cref="DrawableRepresentation"/> instead.
|
||||
/// Create a fresh drawable version of this item.
|
||||
/// </summary>
|
||||
protected abstract DrawableCarouselItem CreateDrawableRepresentation();
|
||||
public abstract DrawableCarouselItem CreateDrawableRepresentation();
|
||||
|
||||
public virtual void Filter(FilterCriteria criteria)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual int CompareTo(FilterCriteria criteria, CarouselItem other) => ChildID.CompareTo(other.ChildID);
|
||||
|
||||
public int CompareTo(CarouselItem other) => CarouselYPosition.CompareTo(other.CarouselYPosition);
|
||||
}
|
||||
|
||||
public enum CarouselItemState
|
||||
|
@ -31,6 +31,15 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
public class DrawableCarouselBeatmap : DrawableCarouselItem, IHasContextMenu
|
||||
{
|
||||
public const float CAROUSEL_BEATMAP_SPACING = 5;
|
||||
|
||||
/// <summary>
|
||||
/// The height of a carousel beatmap, including vertical spacing.
|
||||
/// </summary>
|
||||
public const float HEIGHT = height + CAROUSEL_BEATMAP_SPACING;
|
||||
|
||||
private const float height = MAX_HEIGHT * 0.6f;
|
||||
|
||||
private readonly BeatmapInfo beatmap;
|
||||
|
||||
private Sprite background;
|
||||
@ -58,15 +67,16 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
private CancellationTokenSource starDifficultyCancellationSource;
|
||||
|
||||
public DrawableCarouselBeatmap(CarouselBeatmap panel)
|
||||
: base(panel)
|
||||
{
|
||||
beatmap = panel.Beatmap;
|
||||
Height *= 0.60f;
|
||||
Item = panel;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(BeatmapManager manager, SongSelect songSelect)
|
||||
{
|
||||
Header.Height = height;
|
||||
|
||||
if (songSelect != null)
|
||||
{
|
||||
startRequested = b => songSelect.FinaliseSelection(b);
|
||||
@ -77,7 +87,7 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
if (manager != null)
|
||||
hideRequested = manager.Hide;
|
||||
|
||||
Children = new Drawable[]
|
||||
Header.Children = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
@ -168,6 +178,8 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
base.Selected();
|
||||
|
||||
MovementContainer.MoveToX(-50, 500, Easing.OutExpo);
|
||||
|
||||
background.Colour = ColourInfo.GradientVertical(
|
||||
new Color4(20, 43, 51, 255),
|
||||
new Color4(40, 86, 102, 255));
|
||||
@ -179,6 +191,8 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
base.Deselected();
|
||||
|
||||
MovementContainer.MoveToX(0, 500, Easing.OutExpo);
|
||||
|
||||
background.Colour = new Color4(20, 43, 51, 255);
|
||||
triangles.Colour = OsuColour.Gray(0.5f);
|
||||
}
|
||||
|
@ -3,32 +3,24 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
public class DrawableCarouselBeatmapSet : DrawableCarouselItem, IHasContextMenu
|
||||
{
|
||||
public const float HEIGHT = MAX_HEIGHT;
|
||||
|
||||
private Action<BeatmapSetInfo> restoreHiddenRequested;
|
||||
private Action<int> viewDetails;
|
||||
|
||||
@ -41,99 +33,139 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
[Resolved(CanBeNull = true)]
|
||||
private ManageCollectionsDialog manageCollectionsDialog { get; set; }
|
||||
|
||||
private readonly BeatmapSetInfo beatmapSet;
|
||||
public IEnumerable<DrawableCarouselItem> DrawableBeatmaps => beatmapContainer?.Children ?? Enumerable.Empty<DrawableCarouselItem>();
|
||||
|
||||
public DrawableCarouselBeatmapSet(CarouselBeatmapSet set)
|
||||
: base(set)
|
||||
private Container<DrawableCarouselItem> beatmapContainer;
|
||||
|
||||
private BeatmapSetInfo beatmapSet;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager manager { get; set; }
|
||||
|
||||
protected override void FreeAfterUse()
|
||||
{
|
||||
beatmapSet = set.BeatmapSet;
|
||||
base.FreeAfterUse();
|
||||
|
||||
Item = null;
|
||||
|
||||
ClearTransforms();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(BeatmapManager manager, BeatmapSetOverlay beatmapOverlay)
|
||||
private void load(BeatmapSetOverlay beatmapOverlay)
|
||||
{
|
||||
restoreHiddenRequested = s => s.Beatmaps.ForEach(manager.Restore);
|
||||
|
||||
if (beatmapOverlay != null)
|
||||
viewDetails = beatmapOverlay.FetchAndShowBeatmapSet;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new DelayedLoadUnloadWrapper(() =>
|
||||
{
|
||||
var background = new PanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault()))
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
background.OnLoadComplete += d => d.FadeInFromZero(1000, Easing.OutQuint);
|
||||
|
||||
return background;
|
||||
}, 300, 5000
|
||||
),
|
||||
new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding { Top = 5, Left = 18, Right = 10, Bottom = 10 },
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true),
|
||||
Shadow = true,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true),
|
||||
Shadow = true,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Top = 5 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Right = 5 },
|
||||
TextSize = 11,
|
||||
TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 },
|
||||
Status = beatmapSet.Status
|
||||
},
|
||||
new FillFlowContainer<DifficultyIcon>
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(3),
|
||||
ChildrenEnumerable = getDifficultyIcons(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private const int maximum_difficulty_icons = 18;
|
||||
|
||||
private IEnumerable<DifficultyIcon> getDifficultyIcons()
|
||||
protected override void UpdateItem()
|
||||
{
|
||||
var beatmaps = ((CarouselBeatmapSet)Item).Beatmaps.ToList();
|
||||
base.UpdateItem();
|
||||
|
||||
return beatmaps.Count > maximum_difficulty_icons
|
||||
? (IEnumerable<DifficultyIcon>)beatmaps.GroupBy(b => b.Beatmap.Ruleset).Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Key))
|
||||
: beatmaps.Select(b => new FilterableDifficultyIcon(b));
|
||||
Content.Clear();
|
||||
beatmapContainer = null;
|
||||
|
||||
if (Item == null)
|
||||
return;
|
||||
|
||||
beatmapSet = ((CarouselBeatmapSet)Item).BeatmapSet;
|
||||
|
||||
DelayedLoadWrapper background;
|
||||
DelayedLoadWrapper mainFlow;
|
||||
|
||||
Header.Children = new Drawable[]
|
||||
{
|
||||
background = new DelayedLoadWrapper(() => new SetPanelBackground(manager.GetWorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault()))
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}, 300),
|
||||
mainFlow = new DelayedLoadWrapper(() => new SetPanelContent((CarouselBeatmapSet)Item), 100),
|
||||
};
|
||||
|
||||
background.DelayedLoadComplete += fadeContentIn;
|
||||
mainFlow.DelayedLoadComplete += fadeContentIn;
|
||||
}
|
||||
|
||||
private void fadeContentIn(Drawable d) => d.FadeInFromZero(750, Easing.OutQuint);
|
||||
|
||||
protected override void Deselected()
|
||||
{
|
||||
base.Deselected();
|
||||
|
||||
MovementContainer.MoveToX(0, 500, Easing.OutExpo);
|
||||
|
||||
if (beatmapContainer != null)
|
||||
{
|
||||
foreach (var beatmap in beatmapContainer)
|
||||
beatmap.MoveToY(0, 800, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Selected()
|
||||
{
|
||||
base.Selected();
|
||||
|
||||
MovementContainer.MoveToX(-100, 500, Easing.OutExpo);
|
||||
|
||||
updateBeatmapDifficulties();
|
||||
}
|
||||
|
||||
private void updateBeatmapDifficulties()
|
||||
{
|
||||
var carouselBeatmapSet = (CarouselBeatmapSet)Item;
|
||||
|
||||
var visibleBeatmaps = carouselBeatmapSet.Children.Where(c => c.Visible).ToArray();
|
||||
|
||||
// if we are already displaying all the correct beatmaps, only run animation updates.
|
||||
// note that the displayed beatmaps may change due to the applied filter.
|
||||
// a future optimisation could add/remove only changed difficulties rather than reinitialise.
|
||||
if (beatmapContainer != null && visibleBeatmaps.Length == beatmapContainer.Count && visibleBeatmaps.All(b => beatmapContainer.Any(c => c.Item == b)))
|
||||
{
|
||||
updateBeatmapYPositions();
|
||||
}
|
||||
else
|
||||
{
|
||||
// on selection we show our child beatmaps.
|
||||
// for now this is a simple drawable construction each selection.
|
||||
// can be improved in the future.
|
||||
beatmapContainer = new Container<DrawableCarouselItem>
|
||||
{
|
||||
X = 100,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ChildrenEnumerable = visibleBeatmaps.Select(c => c.CreateDrawableRepresentation())
|
||||
};
|
||||
|
||||
LoadComponentAsync(beatmapContainer, loaded =>
|
||||
{
|
||||
// make sure the pooled target hasn't changed.
|
||||
if (carouselBeatmapSet != Item)
|
||||
return;
|
||||
|
||||
Content.Child = loaded;
|
||||
updateBeatmapYPositions();
|
||||
});
|
||||
}
|
||||
|
||||
void updateBeatmapYPositions()
|
||||
{
|
||||
float yPos = DrawableCarouselBeatmap.CAROUSEL_BEATMAP_SPACING;
|
||||
|
||||
foreach (var panel in beatmapContainer.Children)
|
||||
{
|
||||
panel.MoveToY(yPos, 800, Easing.OutQuint);
|
||||
yPos += panel.Item.TotalHeight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public MenuItem[] ContextMenuItems
|
||||
{
|
||||
get
|
||||
{
|
||||
Debug.Assert(beatmapSet != null);
|
||||
|
||||
List<MenuItem> items = new List<MenuItem>();
|
||||
|
||||
if (Item.State.Value == CarouselItemState.NotSelected)
|
||||
@ -162,6 +194,8 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
|
||||
private MenuItem createCollectionMenuItem(BeatmapCollection collection)
|
||||
{
|
||||
Debug.Assert(beatmapSet != null);
|
||||
|
||||
TernaryState state;
|
||||
|
||||
var countExisting = beatmapSet.Beatmaps.Count(b => collection.Beatmaps.Contains(b));
|
||||
@ -196,116 +230,5 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
State = { Value = state }
|
||||
};
|
||||
}
|
||||
|
||||
private class PanelBackground : BufferedContainer
|
||||
{
|
||||
public PanelBackground(WorkingBeatmap working)
|
||||
{
|
||||
CacheDrawnFrameBuffer = true;
|
||||
RedrawOnScale = false;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BeatmapBackgroundSprite(working)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
FillMode = FillMode.Fill,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Depth = -1,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
// This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle
|
||||
Shear = new Vector2(0.8f, 0),
|
||||
Alpha = 0.5f,
|
||||
Children = new[]
|
||||
{
|
||||
// The left half with no gradient applied
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Width = 0.4f,
|
||||
},
|
||||
// Piecewise-linear gradient with 3 segments to make it appear smoother
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)),
|
||||
Width = 0.05f,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)),
|
||||
Width = 0.2f,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)),
|
||||
Width = 0.05f,
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class FilterableDifficultyIcon : DifficultyIcon
|
||||
{
|
||||
private readonly BindableBool filtered = new BindableBool();
|
||||
|
||||
public bool IsFiltered => filtered.Value;
|
||||
|
||||
public readonly CarouselBeatmap Item;
|
||||
|
||||
public FilterableDifficultyIcon(CarouselBeatmap item)
|
||||
: base(item.Beatmap)
|
||||
{
|
||||
filtered.BindTo(item.Filtered);
|
||||
filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100));
|
||||
filtered.TriggerChange();
|
||||
|
||||
Item = item;
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
Item.State.Value = CarouselItemState.Selected;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public class FilterableGroupedDifficultyIcon : GroupedDifficultyIcon
|
||||
{
|
||||
public readonly List<CarouselBeatmap> Items;
|
||||
|
||||
public FilterableGroupedDifficultyIcon(List<CarouselBeatmap> items, RulesetInfo ruleset)
|
||||
: base(items.Select(i => i.Beatmap).ToList(), ruleset, Color4.White)
|
||||
{
|
||||
Items = items;
|
||||
|
||||
foreach (var item in items)
|
||||
item.Filtered.BindValueChanged(_ => Scheduler.AddOnce(updateFilteredDisplay));
|
||||
|
||||
updateFilteredDisplay();
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
Items.First().State.Value = CarouselItemState.Selected;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateFilteredDisplay()
|
||||
{
|
||||
// for now, fade the whole group based on the ratio of hidden items.
|
||||
this.FadeTo(1 - 0.9f * ((float)Items.Count(i => i.Filtered.Value) / Items.Count), 100);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,106 +1,133 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
public abstract class DrawableCarouselItem : Container
|
||||
public abstract class DrawableCarouselItem : PoolableDrawable
|
||||
{
|
||||
public const float MAX_HEIGHT = 80;
|
||||
|
||||
public override bool RemoveWhenNotAlive => false;
|
||||
public override bool IsPresent => base.IsPresent || Item?.Visible == true;
|
||||
|
||||
public override bool IsPresent => base.IsPresent || Item.Visible;
|
||||
public readonly CarouselHeader Header;
|
||||
|
||||
public readonly CarouselItem Item;
|
||||
/// <summary>
|
||||
/// Optional content which sits below the header.
|
||||
/// </summary>
|
||||
protected readonly Container<Drawable> Content;
|
||||
|
||||
private Container nestedContainer;
|
||||
private Container borderContainer;
|
||||
protected readonly Container MovementContainer;
|
||||
|
||||
private Box hoverLayer;
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||
Header.ReceivePositionalInputAt(screenSpacePos);
|
||||
|
||||
protected override Container<Drawable> Content => nestedContainer;
|
||||
private CarouselItem item;
|
||||
|
||||
protected DrawableCarouselItem(CarouselItem item)
|
||||
public CarouselItem Item
|
||||
{
|
||||
Item = item;
|
||||
|
||||
Height = MAX_HEIGHT;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Alpha = 0;
|
||||
}
|
||||
|
||||
private SampleChannel sampleHover;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, OsuColour colours)
|
||||
{
|
||||
InternalChild = borderContainer = new Container
|
||||
get => item;
|
||||
set
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 10,
|
||||
BorderColour = new Color4(221, 255, 255, 255),
|
||||
Children = new Drawable[]
|
||||
if (item == value)
|
||||
return;
|
||||
|
||||
if (item != null)
|
||||
{
|
||||
nestedContainer = new Container
|
||||
item.Filtered.ValueChanged -= onStateChange;
|
||||
item.State.ValueChanged -= onStateChange;
|
||||
|
||||
Header.State.UnbindFrom(item.State);
|
||||
|
||||
if (item is CarouselGroup group)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
hoverLayer = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
Blending = BlendingParameters.Additive,
|
||||
},
|
||||
foreach (var c in group.Children)
|
||||
c.Filtered.ValueChanged -= onStateChange;
|
||||
}
|
||||
}
|
||||
|
||||
item = value;
|
||||
|
||||
if (IsLoaded)
|
||||
UpdateItem();
|
||||
}
|
||||
}
|
||||
|
||||
protected DrawableCarouselItem()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
|
||||
Alpha = 0;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
MovementContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Header = new CarouselHeader(),
|
||||
Content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
sampleHover = audio.Samples.Get($@"SongSelect/song-ping-variation-{RNG.Next(1, 5)}");
|
||||
hoverLayer.Colour = colours.Blue.Opacity(0.1f);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
sampleHover?.Play();
|
||||
|
||||
hoverLayer.FadeIn(100, Easing.OutQuint);
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
hoverLayer.FadeOut(1000, Easing.OutQuint);
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
public void SetMultiplicativeAlpha(float alpha) => borderContainer.Alpha = alpha;
|
||||
public void SetMultiplicativeAlpha(float alpha) => Header.BorderContainer.Alpha = alpha;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
ApplyState();
|
||||
Item.Filtered.ValueChanged += _ => Schedule(ApplyState);
|
||||
Item.State.ValueChanged += _ => Schedule(ApplyState);
|
||||
UpdateItem();
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
Content.Y = Header.Height;
|
||||
}
|
||||
|
||||
protected virtual void UpdateItem()
|
||||
{
|
||||
if (item == null)
|
||||
return;
|
||||
|
||||
Scheduler.AddOnce(ApplyState);
|
||||
|
||||
Item.Filtered.ValueChanged += onStateChange;
|
||||
Item.State.ValueChanged += onStateChange;
|
||||
|
||||
Header.State.BindTo(Item.State);
|
||||
|
||||
if (Item is CarouselGroup group)
|
||||
{
|
||||
foreach (var c in group.Children)
|
||||
c.Filtered.ValueChanged += onStateChange;
|
||||
}
|
||||
}
|
||||
|
||||
private void onStateChange(ValueChangedEvent<CarouselItemState> obj) => Scheduler.AddOnce(ApplyState);
|
||||
|
||||
private void onStateChange(ValueChangedEvent<bool> _) => Scheduler.AddOnce(ApplyState);
|
||||
|
||||
protected virtual void ApplyState()
|
||||
{
|
||||
if (!IsLoaded) return;
|
||||
// Use the fact that we know the precise height of the item from the model to avoid the need for AutoSize overhead.
|
||||
// Additionally, AutoSize doesn't work well due to content starting off-screen and being masked away.
|
||||
Height = Item.TotalHeight;
|
||||
|
||||
Debug.Assert(Item != null);
|
||||
|
||||
switch (Item.State.Value)
|
||||
{
|
||||
@ -121,30 +148,11 @@ namespace osu.Game.Screens.Select.Carousel
|
||||
|
||||
protected virtual void Selected()
|
||||
{
|
||||
Item.State.Value = CarouselItemState.Selected;
|
||||
|
||||
borderContainer.BorderThickness = 2.5f;
|
||||
borderContainer.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Glow,
|
||||
Colour = new Color4(130, 204, 255, 150),
|
||||
Radius = 20,
|
||||
Roundness = 10,
|
||||
};
|
||||
Debug.Assert(Item != null);
|
||||
}
|
||||
|
||||
protected virtual void Deselected()
|
||||
{
|
||||
Item.State.Value = CarouselItemState.NotSelected;
|
||||
|
||||
borderContainer.BorderThickness = 0;
|
||||
borderContainer.EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Offset = new Vector2(1),
|
||||
Radius = 10,
|
||||
Colour = Color4.Black.Opacity(100),
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
|
35
osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs
Normal file
35
osu.Game/Screens/Select/Carousel/FilterableDifficultyIcon.cs
Normal file
@ -0,0 +1,35 @@
|
||||
// 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.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
|
||||
namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
public class FilterableDifficultyIcon : DifficultyIcon
|
||||
{
|
||||
private readonly BindableBool filtered = new BindableBool();
|
||||
|
||||
public bool IsFiltered => filtered.Value;
|
||||
|
||||
public readonly CarouselBeatmap Item;
|
||||
|
||||
public FilterableDifficultyIcon(CarouselBeatmap item)
|
||||
: base(item.Beatmap, performBackgroundDifficultyLookup: false)
|
||||
{
|
||||
filtered.BindTo(item.Filtered);
|
||||
filtered.ValueChanged += isFiltered => Schedule(() => this.FadeTo(isFiltered.NewValue ? 0.1f : 1, 100));
|
||||
filtered.TriggerChange();
|
||||
|
||||
Item = item;
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
Item.State.Value = CarouselItemState.Selected;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
public class FilterableGroupedDifficultyIcon : GroupedDifficultyIcon
|
||||
{
|
||||
public readonly List<CarouselBeatmap> Items;
|
||||
|
||||
public FilterableGroupedDifficultyIcon(List<CarouselBeatmap> items, RulesetInfo ruleset)
|
||||
: base(items.Select(i => i.Beatmap).ToList(), ruleset, Color4.White)
|
||||
{
|
||||
Items = items;
|
||||
|
||||
foreach (var item in items)
|
||||
item.Filtered.BindValueChanged(_ => Scheduler.AddOnce(updateFilteredDisplay));
|
||||
|
||||
updateFilteredDisplay();
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
Items.First().State.Value = CarouselItemState.Selected;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateFilteredDisplay()
|
||||
{
|
||||
// for now, fade the whole group based on the ratio of hidden items.
|
||||
this.FadeTo(1 - 0.9f * ((float)Items.Count(i => i.Filtered.Value) / Items.Count), 100);
|
||||
}
|
||||
}
|
||||
}
|
72
osu.Game/Screens/Select/Carousel/SetPanelBackground.cs
Normal file
72
osu.Game/Screens/Select/Carousel/SetPanelBackground.cs
Normal file
@ -0,0 +1,72 @@
|
||||
// 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.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
public class SetPanelBackground : BufferedContainer
|
||||
{
|
||||
public SetPanelBackground(WorkingBeatmap working)
|
||||
{
|
||||
CacheDrawnFrameBuffer = true;
|
||||
RedrawOnScale = false;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BeatmapBackgroundSprite(working)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
FillMode = FillMode.Fill,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Depth = -1,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
// This makes the gradient not be perfectly horizontal, but diagonal at a ~40° angle
|
||||
Shear = new Vector2(0.8f, 0),
|
||||
Alpha = 0.5f,
|
||||
Children = new[]
|
||||
{
|
||||
// The left half with no gradient applied
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Width = 0.4f,
|
||||
},
|
||||
// Piecewise-linear gradient with 3 segments to make it appear smoother
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(Color4.Black, new Color4(0f, 0f, 0f, 0.9f)),
|
||||
Width = 0.05f,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.9f), new Color4(0f, 0f, 0f, 0.1f)),
|
||||
Width = 0.2f,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(new Color4(0f, 0f, 0f, 0.1f), new Color4(0, 0, 0, 0)),
|
||||
Width = 0.05f,
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
93
osu.Game/Screens/Select/Carousel/SetPanelContent.cs
Normal file
93
osu.Game/Screens/Select/Carousel/SetPanelContent.cs
Normal file
@ -0,0 +1,93 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.Select.Carousel
|
||||
{
|
||||
public class SetPanelContent : CompositeDrawable
|
||||
{
|
||||
private readonly CarouselBeatmapSet carouselSet;
|
||||
|
||||
public SetPanelContent(CarouselBeatmapSet carouselSet)
|
||||
{
|
||||
this.carouselSet = carouselSet;
|
||||
|
||||
// required to ensure we load as soon as any part of the panel comes on screen
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
var beatmapSet = carouselSet.BeatmapSet;
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
// required to ensure we load as soon as any part of the panel comes on screen
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding { Top = 5, Left = 18, Right = 10, Bottom = 10 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = new LocalisedString((beatmapSet.Metadata.TitleUnicode, beatmapSet.Metadata.Title)),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 22, italics: true),
|
||||
Shadow = true,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = new LocalisedString((beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist)),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 17, italics: true),
|
||||
Shadow = true,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Top = 5 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BeatmapSetOnlineStatusPill
|
||||
{
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Right = 5 },
|
||||
TextSize = 11,
|
||||
TextPadding = new MarginPadding { Horizontal = 8, Vertical = 2 },
|
||||
Status = beatmapSet.Status
|
||||
},
|
||||
new FillFlowContainer<DifficultyIcon>
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(3),
|
||||
ChildrenEnumerable = getDifficultyIcons(),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private const int maximum_difficulty_icons = 18;
|
||||
|
||||
private IEnumerable<DifficultyIcon> getDifficultyIcons()
|
||||
{
|
||||
var beatmaps = carouselSet.Beatmaps.ToList();
|
||||
|
||||
return beatmaps.Count > maximum_difficulty_icons
|
||||
? (IEnumerable<DifficultyIcon>)beatmaps.GroupBy(b => b.Beatmap.Ruleset).Select(group => new FilterableGroupedDifficultyIcon(group.ToList(), group.Key))
|
||||
: beatmaps.Select(b => new FilterableDifficultyIcon(b));
|
||||
}
|
||||
}
|
||||
}
|
@ -15,7 +15,7 @@ using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Screens.Select
|
||||
{
|
||||
public class DifficultyRecommender : Component, IOnlineComponent
|
||||
public class DifficultyRecommender : Component
|
||||
{
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
@ -28,10 +28,13 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
private readonly Dictionary<RulesetInfo, double> recommendedStarDifficulty = new Dictionary<RulesetInfo, double>();
|
||||
|
||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
api.Register(this);
|
||||
apiState.BindTo(api.State);
|
||||
apiState.BindValueChanged(onlineStateChanged, true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -72,21 +75,14 @@ namespace osu.Game.Screens.Select
|
||||
});
|
||||
}
|
||||
|
||||
public void APIStateChanged(IAPIProvider api, APIState state)
|
||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||
{
|
||||
switch (state)
|
||||
switch (state.NewValue)
|
||||
{
|
||||
case APIState.Online:
|
||||
calculateRecommendedDifficulties();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
api?.Unregister(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
public class DrawableStoryboard : Container<DrawableStoryboardLayer>
|
||||
{
|
||||
[Cached]
|
||||
public Storyboard Storyboard { get; }
|
||||
|
||||
protected override Container<DrawableStoryboardLayer> Content { get; }
|
||||
|
@ -2,18 +2,16 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osuTK;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Animations;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
public class DrawableStoryboardAnimation : TextureAnimation, IFlippable, IVectorScalable
|
||||
public class DrawableStoryboardAnimation : DrawableAnimation, IFlippable, IVectorScalable
|
||||
{
|
||||
public StoryboardAnimation Animation { get; }
|
||||
|
||||
@ -115,18 +113,13 @@ namespace osu.Game.Storyboards.Drawables
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IBindable<WorkingBeatmap> beatmap, TextureStore textureStore)
|
||||
private void load(TextureStore textureStore, Storyboard storyboard)
|
||||
{
|
||||
for (var frame = 0; frame < Animation.FrameCount; frame++)
|
||||
for (int frame = 0; frame < Animation.FrameCount; frame++)
|
||||
{
|
||||
var framePath = Animation.Path.Replace(".", frame + ".");
|
||||
string framePath = Animation.Path.Replace(".", frame + ".");
|
||||
|
||||
var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.Equals(framePath, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
|
||||
if (path == null)
|
||||
continue;
|
||||
|
||||
var texture = textureStore.Get(path);
|
||||
AddFrame(texture, Animation.FrameDelay);
|
||||
AddFrame(storyboard.CreateSpriteFromResourcePath(framePath, textureStore), Animation.FrameDelay);
|
||||
}
|
||||
|
||||
Animation.ApplyTransforms(this);
|
||||
|
@ -2,18 +2,16 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osuTK;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Storyboards.Drawables
|
||||
{
|
||||
public class DrawableStoryboardSprite : Sprite, IFlippable, IVectorScalable
|
||||
public class DrawableStoryboardSprite : CompositeDrawable, IFlippable, IVectorScalable
|
||||
{
|
||||
public StoryboardSprite Sprite { get; }
|
||||
|
||||
@ -111,16 +109,18 @@ namespace osu.Game.Storyboards.Drawables
|
||||
|
||||
LifetimeStart = sprite.StartTime;
|
||||
LifetimeEnd = sprite.EndTime;
|
||||
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IBindable<WorkingBeatmap> beatmap, TextureStore textureStore)
|
||||
private void load(TextureStore textureStore, Storyboard storyboard)
|
||||
{
|
||||
var path = beatmap.Value.BeatmapSetInfo?.Files?.Find(f => f.Filename.Equals(Sprite.Path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
|
||||
if (path == null)
|
||||
return;
|
||||
var drawable = storyboard.CreateSpriteFromResourcePath(Sprite.Path, textureStore);
|
||||
|
||||
if (drawable != null)
|
||||
InternalChild = drawable;
|
||||
|
||||
Texture = textureStore.Get(path);
|
||||
Sprite.ApplyTransforms(this);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,14 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards.Drawables;
|
||||
|
||||
namespace osu.Game.Storyboards
|
||||
@ -15,6 +20,11 @@ namespace osu.Game.Storyboards
|
||||
|
||||
public BeatmapInfo BeatmapInfo = new BeatmapInfo();
|
||||
|
||||
/// <summary>
|
||||
/// Whether the storyboard can fall back to skin sprites in case no matching storyboard sprites are found.
|
||||
/// </summary>
|
||||
public bool UseSkinSprites { get; set; }
|
||||
|
||||
public bool HasDrawable => Layers.Any(l => l.Elements.Any(e => e.IsDrawable));
|
||||
|
||||
public double FirstEventTime => Layers.Min(l => l.Elements.FirstOrDefault()?.StartTime ?? 0);
|
||||
@ -64,5 +74,19 @@ namespace osu.Game.Storyboards
|
||||
drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f);
|
||||
return drawable;
|
||||
}
|
||||
|
||||
public Drawable CreateSpriteFromResourcePath(string path, TextureStore textureStore)
|
||||
{
|
||||
Drawable drawable = null;
|
||||
var storyboardPath = BeatmapInfo.BeatmapSet?.Files?.Find(f => f.Filename.Equals(path, StringComparison.OrdinalIgnoreCase))?.FileInfo.StoragePath;
|
||||
|
||||
if (storyboardPath != null)
|
||||
drawable = new Sprite { Texture = textureStore.Get(storyboardPath) };
|
||||
// if the texture isn't available locally in the beatmap, some storyboards choose to source from the underlying skin lookup hierarchy.
|
||||
else if (UseSkinSprites)
|
||||
drawable = new SkinnableSprite(path);
|
||||
|
||||
return drawable;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user