diff --git a/osu.Desktop/Updater/SimpleUpdateManager.cs b/osu.Desktop/Updater/SimpleUpdateManager.cs index e404ccd2b3..6956eb30b0 100644 --- a/osu.Desktop/Updater/SimpleUpdateManager.cs +++ b/osu.Desktop/Updater/SimpleUpdateManager.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using System.Linq; using System.Threading.Tasks; using Newtonsoft.Json; using osu.Framework; @@ -77,10 +76,10 @@ namespace osu.Desktop.Updater switch (RuntimeInfo.OS) { case RuntimeInfo.Platform.Windows: - bestAsset = release.Assets?.FirstOrDefault(f => f.Name.EndsWith(".exe")); + bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".exe")); break; case RuntimeInfo.Platform.MacOsx: - bestAsset = release.Assets?.FirstOrDefault(f => f.Name.EndsWith(".app.zip")); + bestAsset = release.Assets?.Find(f => f.Name.EndsWith(".app.zip")); break; } diff --git a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs index db80948c94..483ff2d409 100644 --- a/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs +++ b/osu.Game.Rulesets.Osu/Beatmaps/OsuBeatmapProcessor.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects.Types; @@ -23,60 +24,70 @@ namespace osu.Game.Rulesets.Osu.Beatmaps var osuBeatmap = (Beatmap)Beatmap; - // Reset stacking - foreach (var h in osuBeatmap.HitObjects) - h.StackHeight = 0; + if (osuBeatmap.HitObjects.Count > 0) + { + // Reset stacking + foreach (var h in osuBeatmap.HitObjects) + h.StackHeight = 0; - if (Beatmap.BeatmapInfo.BeatmapVersion >= 6) - applyStacking(osuBeatmap); - else - applyStackingOld(osuBeatmap); + if (Beatmap.BeatmapInfo.BeatmapVersion >= 6) + applyStacking(osuBeatmap, 0, osuBeatmap.HitObjects.Count - 1); + else + applyStackingOld(osuBeatmap); + } } - private void applyStacking(Beatmap beatmap) + private void applyStacking(Beatmap beatmap, int startIndex, int endIndex) { - // Extend the end index to include objects they are stacked on - int extendedEndIndex = beatmap.HitObjects.Count - 1; - for (int i = beatmap.HitObjects.Count - 1; i >= 0; i--) + if (startIndex > endIndex) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be greater than {nameof(endIndex)}."); + if (startIndex < 0) throw new ArgumentOutOfRangeException(nameof(startIndex), $"{nameof(startIndex)} cannot be less than 0."); + if (endIndex < 0) throw new ArgumentOutOfRangeException(nameof(endIndex), $"{nameof(endIndex)} cannot be less than 0."); + + int extendedEndIndex = endIndex; + if (endIndex < beatmap.HitObjects.Count - 1) { - int stackBaseIndex = i; - for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++) + // Extend the end index to include objects they are stacked on + for (int i = endIndex; i >= startIndex; i--) { - OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex]; - if (stackBaseObject is Spinner) break; - - OsuHitObject objectN = beatmap.HitObjects[n]; - if (objectN is Spinner) - continue; - - double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime; - double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency; - - if (objectN.StartTime - endTime > stackThreshold) - //We are no longer within stacking range of the next object. - break; - - if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance || - stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance) + int stackBaseIndex = i; + for (int n = stackBaseIndex + 1; n < beatmap.HitObjects.Count; n++) { - stackBaseIndex = n; + OsuHitObject stackBaseObject = beatmap.HitObjects[stackBaseIndex]; + if (stackBaseObject is Spinner) break; - // HitObjects after the specified update range haven't been reset yet - objectN.StackHeight = 0; + OsuHitObject objectN = beatmap.HitObjects[n]; + if (objectN is Spinner) + continue; + + double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime; + double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo.StackLeniency; + + if (objectN.StartTime - endTime > stackThreshold) + //We are no longer within stacking range of the next object. + break; + + if (Vector2Extensions.Distance(stackBaseObject.Position, objectN.Position) < stack_distance + || stackBaseObject is Slider && Vector2Extensions.Distance(stackBaseObject.EndPosition, objectN.Position) < stack_distance) + { + stackBaseIndex = n; + + // HitObjects after the specified update range haven't been reset yet + objectN.StackHeight = 0; + } } - } - if (stackBaseIndex > extendedEndIndex) - { - extendedEndIndex = stackBaseIndex; - if (extendedEndIndex == beatmap.HitObjects.Count - 1) - break; + if (stackBaseIndex > extendedEndIndex) + { + extendedEndIndex = stackBaseIndex; + if (extendedEndIndex == beatmap.HitObjects.Count - 1) + break; + } } } //Reverse pass for stack calculation. - int extendedStartIndex = 0; - for (int i = extendedEndIndex; i > 0; i--) + int extendedStartIndex = startIndex; + for (int i = extendedEndIndex; i > startIndex; i--) { int n = i; /* We should check every note which has not yet got a stack. @@ -155,7 +166,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps /* We have hit the first slider in a possible stack. * From this point on, we ALWAYS stack positive regardless. */ - while (--n >= 0) + while (--n >= startIndex) { OsuHitObject objectN = beatmap.HitObjects[n]; if (objectN is Spinner) continue; diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 16f0af9875..efa23f1a26 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -105,13 +105,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty double approachRateFactor = 1.0f; if (Attributes.ApproachRate > 10.33f) - approachRateFactor += 0.45f * (Attributes.ApproachRate - 10.33f); + approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f); else if (Attributes.ApproachRate < 8.0f) { - // HD is worth more with lower ar! - if (mods.Any(h => h is OsuModHidden)) - approachRateFactor += 0.02f * (8.0f - Attributes.ApproachRate); - else approachRateFactor += 0.01f * (8.0f - Attributes.ApproachRate); } @@ -119,7 +115,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty // We want to give more reward for lower AR when it comes to aim and HD. This nerfs high AR and buffs lower AR. if (mods.Any(h => h is OsuModHidden)) - aimValue *= 1.02 + (11.0f - Attributes.ApproachRate) / 50.0; // Gives a 1.04 bonus for AR10, a 1.06 bonus for AR9, a 1.02 bonus for AR11. + aimValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate); if (mods.Any(h => h is OsuModFlashlight)) { @@ -152,13 +148,19 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (beatmapMaxCombo > 0) speedValue *= Math.Min(Math.Pow(scoreMaxCombo, 0.8f) / Math.Pow(beatmapMaxCombo, 0.8f), 1.0f); + double approachRateFactor = 1.0f; + if (Attributes.ApproachRate > 10.33f) + approachRateFactor += 0.3f * (Attributes.ApproachRate - 10.33f); + + speedValue *= approachRateFactor; + if (mods.Any(m => m is OsuModHidden)) - speedValue *= 1.18f; + speedValue *= 1.0f + 0.04f * (12.0f - Attributes.ApproachRate); // Scale the speed value with accuracy _slightly_ - speedValue *= 0.5f + accuracy / 2.0f; + speedValue *= 0.02f + accuracy; // It is important to also consider accuracy difficulty when doing that - speedValue *= 0.98f + Math.Pow(Attributes.OverallDifficulty, 2) / 2500; + speedValue *= 0.96f + Math.Pow(Attributes.OverallDifficulty, 2) / 1600; return speedValue; } @@ -186,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty accuracyValue *= Math.Min(1.15f, Math.Pow(amountHitObjectsWithAccuracy / 1000.0f, 0.3f)); if (mods.Any(m => m is OsuModHidden)) - accuracyValue *= 1.02f; + accuracyValue *= 1.08f; if (mods.Any(m => m is OsuModFlashlight)) accuracyValue *= 1.02f; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 2af1de7355..44185fb83a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -215,7 +215,7 @@ namespace osu.Game.Rulesets.Osu.Objects var distanceProgress = d / length; var timeProgress = reversed ? 1 - distanceProgress : distanceProgress; - var firstSample = Samples.FirstOrDefault(s => s.Name == SampleInfo.HIT_NORMAL) + var firstSample = Samples.Find(s => s.Name == SampleInfo.HIT_NORMAL) ?? Samples.FirstOrDefault(); // TODO: remove this when guaranteed sort is present for samples (https://github.com/ppy/osu/issues/1933) var sampleList = new List(); diff --git a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs index 1412bd3cea..153e5733e5 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Drawables/DrawableSwell.cs @@ -135,7 +135,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables { if (userTriggered) { - var nextTick = ticks.FirstOrDefault(j => !j.IsHit); + var nextTick = ticks.Find(j => !j.IsHit); nextTick?.TriggerResult(HitResult.Great); diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index db66c01814..f156728981 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -148,7 +148,7 @@ namespace osu.Game.Tests.Visual private bool selectedBeatmapVisible() { - var currentlySelected = carousel.Items.FirstOrDefault(s => s.Item is CarouselBeatmap && s.Item.State == CarouselItemState.Selected); + var currentlySelected = carousel.Items.Find(s => s.Item is CarouselBeatmap && s.Item.State == CarouselItemState.Selected); if (currentlySelected == null) return true; return currentlySelected.Item.Visible; diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 61c2f47e7d..8aa3283af7 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -55,7 +55,7 @@ namespace osu.Game.Tests.Visual linkColour = colours.Blue; var chatManager = new ChannelManager(); - BindableCollection availableChannels = (BindableCollection)chatManager.AvailableChannels; + BindableList availableChannels = (BindableList)chatManager.AvailableChannels; availableChannels.Add(new Channel { Name = "#english"}); availableChannels.Add(new Channel { Name = "#japanese" }); Dependencies.Cache(chatManager); diff --git a/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs index 3e9f2fb3a4..6b5bc875f1 100644 --- a/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/TestCaseLoungeRoomsContainer.cs @@ -73,8 +73,8 @@ namespace osu.Game.Tests.Visual { public event Action RoomsUpdated; - public readonly BindableCollection Rooms = new BindableCollection(); - IBindableCollection IRoomManager.Rooms => Rooms; + public readonly BindableList Rooms = new BindableList(); + IBindableList IRoomManager.Rooms => Rooms; public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) => Rooms.Add(room); diff --git a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs b/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs index cf475de1f0..821bf84047 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs +++ b/osu.Game.Tests/Visual/TestCaseMatchLeaderboard.cs @@ -17,12 +17,13 @@ namespace osu.Game.Tests.Visual { public TestCaseMatchLeaderboard() { - Add(new MatchLeaderboard(new Room { RoomID = { Value = 3 } }) + Add(new MatchLeaderboard { Origin = Anchor.Centre, Anchor = Anchor.Centre, Size = new Vector2(550f, 450f), Scope = MatchLeaderboardScope.Overall, + Room = new Room { RoomID = { Value = 3 } } }); } diff --git a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs b/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs index 7fb9d4dded..6f084def48 100644 --- a/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseMatchSettingsOverlay.cs @@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual public event Action RoomsUpdated; - public IBindableCollection Rooms { get; } = null; + public IBindableList Rooms { get; } = null; public void CreateRoom(Room room, Action onSuccess = null, Action onError = null) { diff --git a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs index 2c56f08f42..66ae2d3012 100644 --- a/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs +++ b/osu.Game.Tests/Visual/TestCaseNotificationOverlay.cs @@ -49,7 +49,6 @@ namespace osu.Game.Tests.Visual manager.UnreadCount.ValueChanged += count => { displayedCount.Text = $"displayed count: {count}"; }; - setState(Visibility.Visible); AddStep(@"simple #1", sendHelloNotification); AddStep(@"simple #2", sendAmazingNotification); @@ -75,7 +74,6 @@ namespace osu.Game.Tests.Visual checkProgressingCount(0); - setState(Visibility.Visible); //AddStep(@"barrage", () => sendBarrage()); @@ -111,7 +109,7 @@ namespace osu.Game.Tests.Visual if (progressingNotifications.Count(n => n.State == ProgressNotificationState.Active) < 3) { - var p = progressingNotifications.FirstOrDefault(n => n.State == ProgressNotificationState.Queued); + var p = progressingNotifications.Find(n => n.State == ProgressNotificationState.Queued); if (p != null) p.State = ProgressNotificationState.Active; } diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 29060ceb12..369d28fc91 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -57,11 +57,19 @@ namespace osu.Game.Tests.Visual private class TestSongSelect : PlaySongSelect { + public Action StartRequested; + public new Bindable Ruleset => base.Ruleset; public WorkingBeatmap CurrentBeatmap => Beatmap.Value; public WorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap; public new BeatmapCarousel Carousel => base.Carousel; + + protected override bool OnStart() + { + StartRequested?.Invoke(); + return base.OnStart(); + } } private TestSongSelect songSelect; @@ -182,6 +190,27 @@ namespace osu.Game.Tests.Visual void onRulesetChange(RulesetInfo ruleset) => rulesetChangeIndex = actionIndex--; } + [Test] + public void TestStartAfterUnMatchingFilterDoesNotStart() + { + addManyTestMaps(); + AddUntilStep(() => songSelect.Carousel.SelectedBeatmap != null, "has selection"); + + bool startRequested = false; + + AddStep("set filter and finalize", () => + { + songSelect.StartRequested = () => startRequested = true; + + songSelect.Carousel.Filter(new FilterCriteria { SearchText = "somestringthatshouldn'tbematchable" }); + songSelect.FinaliseSelection(); + + songSelect.StartRequested = null; + }); + + AddAssert("start not requested", () => !startRequested); + } + private void importForRuleset(int id) => AddStep($"import test map for ruleset {id}", () => manager.Import(createTestBeatmapSet(getImportId(), rulesets.AvailableRulesets.Where(r => r.ID == id).ToArray()))); private static int importId; diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 0534fd9253..6ad5b2070e 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; @@ -9,6 +10,7 @@ using Newtonsoft.Json; using osu.Game.Database; using osu.Game.IO.Serialization; using osu.Game.Rulesets; +using osu.Game.Scoring; namespace osu.Game.Beatmaps { @@ -112,6 +114,11 @@ namespace osu.Game.Beatmaps [JsonProperty("difficulty_rating")] public double StarDifficulty { get; set; } + /// + /// Currently only populated for beatmap deletion. Use to query scores. + /// + public List Scores { get; set; } + public override string ToString() => $"{Metadata} [{Version}]"; public bool Equals(BeatmapInfo other) diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 8c541e9344..a5399ce8c7 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -36,7 +36,7 @@ namespace osu.Game.Beatmaps public string Hash { get; set; } - public string StoryboardFile => Files?.FirstOrDefault(f => f.Filename.EndsWith(".osb"))?.Filename; + public string StoryboardFile => Files?.Find(f => f.Filename.EndsWith(".osb"))?.Filename; public List Files { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 5bdc42cdf3..6817c0653d 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -64,7 +64,8 @@ namespace osu.Game.Beatmaps base.AddIncludesForDeletion(query) .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Metadata); + .Include(s => s.Metadata) + .Include(s => s.Beatmaps).ThenInclude(b => b.Scores); protected override IQueryable AddIncludesForConsumption(IQueryable query) => base.AddIncludesForConsumption(query) diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 5b76122616..e65409a1d1 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -151,7 +151,7 @@ namespace osu.Game.Beatmaps public bool WaveformLoaded => waveform.IsResultAvailable; public Waveform Waveform => waveform.Value; - protected virtual Waveform GetWaveform() => new Waveform(); + protected virtual Waveform GetWaveform() => new Waveform(null); private readonly RecyclableLazy waveform; public bool StoryboardLoaded => storyboard.IsResultAvailable; diff --git a/osu.Game/Configuration/DatabasedConfigManager.cs b/osu.Game/Configuration/DatabasedConfigManager.cs index 0ede6de0f2..334fed2b5a 100644 --- a/osu.Game/Configuration/DatabasedConfigManager.cs +++ b/osu.Game/Configuration/DatabasedConfigManager.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using System.Linq; using osu.Framework.Configuration; using osu.Game.Rulesets; @@ -43,7 +42,7 @@ namespace osu.Game.Configuration { base.AddBindable(lookup, bindable); - var setting = databasedSettings.FirstOrDefault(s => (int)s.Key == (int)(object)lookup); + var setting = databasedSettings.Find(s => (int)s.Key == (int)(object)lookup); if (setting != null) { bindable.Parse(setting.Value); diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 8975ab8a0e..1b279eee44 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -3,6 +3,7 @@ using osu.Framework.Configuration; using osu.Framework.Configuration.Tracking; +using osu.Framework.Extensions; using osu.Framework.Platform; using osu.Game.Overlays; using osu.Game.Rulesets.Scoring; @@ -96,15 +97,27 @@ namespace osu.Game.Configuration Set(OsuSetting.ScreenshotCaptureMenuCursor, false); Set(OsuSetting.SongSelectRightMouseScroll, false); + + Set(OsuSetting.Scaling, ScalingMode.Off); + + Set(OsuSetting.ScalingSizeX, 0.8f, 0.2f, 1f); + Set(OsuSetting.ScalingSizeY, 0.8f, 0.2f, 1f); + + Set(OsuSetting.ScalingPositionX, 0.5f, 0f, 1f); + Set(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f); + + Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f); } - public OsuConfigManager(Storage storage) : base(storage) + public OsuConfigManager(Storage storage) + : base(storage) { } public override TrackedSettings CreateTrackedSettings() => new TrackedSettings { - new TrackedSetting(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")) + new TrackedSetting(OsuSetting.MouseDisableButtons, v => new SettingDescription(!v, "gameplay mouse buttons", v ? "disabled" : "enabled")), + new TrackedSetting(OsuSetting.Scaling, m => new SettingDescription(m, "scaling", m.GetDescription())), }; } @@ -151,6 +164,12 @@ namespace osu.Game.Configuration BeatmapHitsounds, IncreaseFirstObjectVisibility, ScoreDisplayMode, - ExternalLinkWarning + ExternalLinkWarning, + Scaling, + ScalingPositionX, + ScalingPositionY, + ScalingSizeX, + ScalingSizeY, + UIScale } } diff --git a/osu.Game/Configuration/ScalingMode.cs b/osu.Game/Configuration/ScalingMode.cs new file mode 100644 index 0000000000..b907d55d82 --- /dev/null +++ b/osu.Game/Configuration/ScalingMode.cs @@ -0,0 +1,16 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Configuration +{ + public enum ScalingMode + { + Off, + Everything, + [Description("Excluding overlays")] + ExcludeOverlays, + Gameplay, + } +} diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 74315d2522..e4c18dfb3d 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -79,6 +79,7 @@ namespace osu.Game.Graphics.Containers { AddInternal(new DrawableLinkCompiler(drawables.OfType().ToList()) { + RelativeSizeAxes = Axes.Both, TooltipText = tooltipText ?? (url != text ? url : string.Empty), Action = action ?? (() => { @@ -122,5 +123,10 @@ namespace osu.Game.Graphics.Containers }), }); } + + // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. + // However due to https://github.com/ppy/osu-framework/issues/2073, it's possible for the compilers to be relative size in the flow's auto-size axes - an unsupported operation. + // Since the compilers don't display any content and don't affect the layout, it's simplest to exclude them from the flow. + public override IEnumerable FlowingChildren => base.FlowingChildren.Where(c => !(c is DrawableLinkCompiler)); } } diff --git a/osu.Game/Graphics/Containers/ScalingContainer.cs b/osu.Game/Graphics/Containers/ScalingContainer.cs new file mode 100644 index 0000000000..8d21d6de10 --- /dev/null +++ b/osu.Game/Graphics/Containers/ScalingContainer.cs @@ -0,0 +1,147 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Configuration; +using osu.Game.Graphics.Backgrounds; +using osuTK; + +namespace osu.Game.Graphics.Containers +{ + /// + /// Handles user-defined scaling, allowing application at multiple levels defined by . + /// + public class ScalingContainer : Container + { + private Bindable sizeX; + private Bindable sizeY; + private Bindable posX; + private Bindable posY; + + private readonly ScalingMode? targetMode; + + private Bindable scalingMode; + + private readonly Container content; + protected override Container Content => content; + + private readonly Container sizableContainer; + + private Drawable backgroundLayer; + + /// + /// Create a new instance. + /// + /// The mode which this container should be handling. Handles all modes if null. + public ScalingContainer(ScalingMode? targetMode = null) + { + this.targetMode = targetMode; + RelativeSizeAxes = Axes.Both; + + InternalChild = sizableContainer = new Container + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Both, + CornerRadius = 10, + Child = content = new ScalingDrawSizePreservingFillContainer(targetMode != ScalingMode.Gameplay) + }; + } + + private class ScalingDrawSizePreservingFillContainer : DrawSizePreservingFillContainer + { + private readonly bool applyUIScale; + private Bindable uiScale; + + public ScalingDrawSizePreservingFillContainer(bool applyUIScale) + { + this.applyUIScale = applyUIScale; + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager osuConfig) + { + if (applyUIScale) + { + uiScale = osuConfig.GetBindable(OsuSetting.UIScale); + uiScale.BindValueChanged(scaleChanged, true); + } + } + + private void scaleChanged(float value) + { + this.ScaleTo(new Vector2(value), 500, Easing.Out); + this.ResizeTo(new Vector2(1 / value), 500, Easing.Out); + } + } + + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + scalingMode = config.GetBindable(OsuSetting.Scaling); + scalingMode.ValueChanged += _ => updateSize(); + + sizeX = config.GetBindable(OsuSetting.ScalingSizeX); + sizeX.ValueChanged += _ => updateSize(); + + sizeY = config.GetBindable(OsuSetting.ScalingSizeY); + sizeY.ValueChanged += _ => updateSize(); + + posX = config.GetBindable(OsuSetting.ScalingPositionX); + posX.ValueChanged += _ => updateSize(); + + posY = config.GetBindable(OsuSetting.ScalingPositionY); + posY.ValueChanged += _ => updateSize(); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + updateSize(); + sizableContainer.FinishTransforms(); + } + + private bool requiresBackgroundVisible => (scalingMode == ScalingMode.Everything || scalingMode == ScalingMode.ExcludeOverlays) && (sizeX.Value != 1 || sizeY.Value != 1); + + private void updateSize() + { + if (targetMode == ScalingMode.Everything) + { + // the top level scaling container manages the background to be displayed while scaling. + if (requiresBackgroundVisible) + { + if (backgroundLayer == null) + LoadComponentAsync(backgroundLayer = new Background("Menu/menu-background-1") + { + Colour = OsuColour.Gray(0.1f), + Alpha = 0, + Depth = float.MaxValue + }, d => + { + AddInternal(d); + d.FadeTo(requiresBackgroundVisible ? 1 : 0, 4000, Easing.OutQuint); + }); + else + backgroundLayer.FadeIn(500); + } + else + backgroundLayer?.FadeOut(500); + } + + bool scaling = targetMode == null || scalingMode.Value == targetMode; + + var targetSize = scaling ? new Vector2(sizeX, sizeY) : Vector2.One; + var targetPosition = scaling ? new Vector2(posX, posY) * (Vector2.One - targetSize) : Vector2.Zero; + bool requiresMasking = scaling && targetSize != Vector2.One; + + if (requiresMasking) + sizableContainer.Masking = true; + + sizableContainer.MoveTo(targetPosition, 500, Easing.OutQuart); + sizableContainer.ResizeTo(targetSize, 500, Easing.OutQuart).OnComplete(_ => { sizableContainer.Masking = requiresMasking; }); + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs index a59abcbcee..2bd84ab2b4 100644 --- a/osu.Game/Graphics/UserInterface/OsuSliderBar.cs +++ b/osu.Game/Graphics/UserInterface/OsuSliderBar.cs @@ -8,7 +8,6 @@ using osuTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; -using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.Cursor; @@ -33,38 +32,7 @@ namespace osu.Game.Graphics.UserInterface private readonly Box leftBox; private readonly Box rightBox; - public virtual string TooltipText - { - get - { - var bindableDouble = CurrentNumber as BindableNumber; - var bindableFloat = CurrentNumber as BindableNumber; - var floatValue = bindableDouble?.Value ?? bindableFloat?.Value; - var floatPrecision = bindableDouble?.Precision ?? bindableFloat?.Precision; - - if (floatValue != null) - { - var floatMinValue = bindableDouble?.MinValue ?? bindableFloat.MinValue; - var floatMaxValue = bindableDouble?.MaxValue ?? bindableFloat.MaxValue; - - if (floatMaxValue == 1 && (floatMinValue == 0 || floatMinValue == -1)) - return floatValue.Value.ToString("P0"); - - var decimalPrecision = normalise((decimal)floatPrecision, max_decimal_digits); - - // Find the number of significant digits (we could have less than 5 after normalize()) - var significantDigits = findPrecision(decimalPrecision); - - return floatValue.Value.ToString($"N{significantDigits}"); - } - - var bindableInt = CurrentNumber as BindableNumber; - if (bindableInt != null) - return bindableInt.Value.ToString("N0"); - - return Current.Value.ToString(CultureInfo.InvariantCulture); - } - } + public virtual string TooltipText { get; private set; } private Color4 accentColour; public Color4 AccentColour @@ -136,21 +104,34 @@ namespace osu.Game.Graphics.UserInterface base.OnHoverLost(e); } - protected override void OnUserChange() + protected override bool OnMouseDown(MouseDownEvent e) { - base.OnUserChange(); - playSample(); + Nub.Current.Value = true; + return base.OnMouseDown(e); } - private void playSample() + protected override bool OnMouseUp(MouseUpEvent e) + { + Nub.Current.Value = false; + return base.OnMouseUp(e); + } + + protected override void OnUserChange(T value) + { + base.OnUserChange(value); + playSample(value); + updateTooltipText(value); + } + + private void playSample(T value) { if (Clock == null || Clock.CurrentTime - lastSampleTime <= 50) return; - if (Current.Value.Equals(lastSampleValue)) + if (value.Equals(lastSampleValue)) return; - lastSampleValue = Current.Value; + lastSampleValue = value; lastSampleTime = Clock.CurrentTime; sample.Frequency.Value = 1 + NormalizedValue * 0.2f; @@ -163,16 +144,28 @@ namespace osu.Game.Graphics.UserInterface sample.Play(); } - protected override bool OnMouseDown(MouseDownEvent e) + private void updateTooltipText(T value) { - Nub.Current.Value = true; - return base.OnMouseDown(e); - } + if (CurrentNumber.IsInteger) + TooltipText = ((int)Convert.ChangeType(value, typeof(int))).ToString("N0"); + else + { + double floatValue = (double)Convert.ChangeType(value, typeof(double)); + double floatMinValue = (double)Convert.ChangeType(CurrentNumber.MinValue, typeof(double)); + double floatMaxValue = (double)Convert.ChangeType(CurrentNumber.MaxValue, typeof(double)); - protected override bool OnMouseUp(MouseUpEvent e) - { - Nub.Current.Value = false; - return base.OnMouseUp(e); + if (floatMaxValue == 1 && floatMinValue >= -1) + TooltipText = floatValue.ToString("P0"); + else + { + var decimalPrecision = normalise((decimal)Convert.ChangeType(CurrentNumber.Precision, typeof(decimal)), max_decimal_digits); + + // Find the number of significant digits (we could have less than 5 after normalize()) + var significantDigits = findPrecision(decimalPrecision); + + TooltipText = floatValue.ToString($"N{significantDigits}"); + } + } } protected override void UpdateAfterChildren() diff --git a/osu.Game/Graphics/UserInterface/ProgressBar.cs b/osu.Game/Graphics/UserInterface/ProgressBar.cs index ee64c7c25c..6dca58f70d 100644 --- a/osu.Game/Graphics/UserInterface/ProgressBar.cs +++ b/osu.Game/Graphics/UserInterface/ProgressBar.cs @@ -62,6 +62,6 @@ namespace osu.Game.Graphics.UserInterface fill.Width = value * UsableWidth; } - protected override void OnUserChange() => OnSeek?.Invoke(Current); + protected override void OnUserChange(double value) => OnSeek?.Invoke(value); } } diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 8026847e3b..2dafedc3ac 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -14,7 +14,7 @@ namespace osu.Game.Migrations { #pragma warning disable 612, 618 modelBuilder - .HasAnnotation("ProductVersion", "2.1.4-rtm-31024"); + .HasAnnotation("ProductVersion", "2.2.0-rtm-35687"); modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => { @@ -215,6 +215,25 @@ namespace osu.Game.Migrations b.ToTable("Settings"); }); + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => { b.Property("ID") @@ -239,25 +258,6 @@ namespace osu.Game.Migrations b.ToTable("KeyBinding"); }); - modelBuilder.Entity("osu.Game.IO.FileInfo", b => - { - b.Property("ID") - .ValueGeneratedOnAdd(); - - b.Property("Hash"); - - b.Property("ReferenceCount"); - - b.HasKey("ID"); - - b.HasIndex("Hash") - .IsUnique(); - - b.HasIndex("ReferenceCount"); - - b.ToTable("FileInfo"); - }); - modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => { b.Property("ID") @@ -454,7 +454,7 @@ namespace osu.Game.Migrations modelBuilder.Entity("osu.Game.Scoring.ScoreInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapInfo", "Beatmap") - .WithMany() + .WithMany("Scores") .HasForeignKey("BeatmapInfoID") .OnDelete(DeleteBehavior.Cascade); diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 10b4e73419..db273dd00a 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -101,6 +101,9 @@ namespace osu.Game.Online.API //todo: replace this with a ping request. log.Add(@"In a failing state, waiting a bit before we try again..."); Thread.Sleep(5000); + + if (!IsLoggedIn) goto case APIState.Connecting; + if (queue.Count == 0) { log.Add(@"Queueing a ping request"); diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 4241b47cd3..d5deda960c 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -29,8 +29,8 @@ namespace osu.Game.Online.Chat @"#lobby" }; - private readonly BindableCollection availableChannels = new BindableCollection(); - private readonly BindableCollection joinedChannels = new BindableCollection(); + private readonly BindableList availableChannels = new BindableList(); + private readonly BindableList joinedChannels = new BindableList(); /// /// The currently opened channel @@ -40,12 +40,12 @@ namespace osu.Game.Online.Chat /// /// The Channels the player has joined /// - public IBindableCollection JoinedChannels => joinedChannels; + public IBindableList JoinedChannels => joinedChannels; /// /// The channels available for the player to join /// - public IBindableCollection AvailableChannels => availableChannels; + public IBindableList AvailableChannels => availableChannels; private IAPIProvider api; diff --git a/osu.Game/Online/Leaderboards/Leaderboard.cs b/osu.Game/Online/Leaderboards/Leaderboard.cs index f3bf16a05f..a6748fa983 100644 --- a/osu.Game/Online/Leaderboards/Leaderboard.cs +++ b/osu.Game/Online/Leaderboards/Leaderboard.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; @@ -30,6 +31,7 @@ namespace osu.Game.Online.Leaderboards private readonly LoadingAnimation loading; private ScheduledDelegate showScoresDelegate; + private CancellationTokenSource showScoresCancellationSource; private bool scoresLoadedOnce; @@ -49,6 +51,10 @@ namespace osu.Game.Online.Leaderboards loading.Hide(); + // schedule because we may not be loaded yet (LoadComponentAsync complains). + showScoresDelegate?.Cancel(); + showScoresCancellationSource?.Cancel(); + if (scores == null || !scores.Any()) return; @@ -58,8 +64,6 @@ namespace osu.Game.Online.Leaderboards scrollFlow = CreateScoreFlow(); scrollFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1)); - // schedule because we may not be loaded yet (LoadComponentAsync complains). - showScoresDelegate?.Cancel(); if (!IsLoaded) showScoresDelegate = Schedule(showScores); else @@ -77,7 +81,7 @@ namespace osu.Game.Online.Leaderboards } scrollContainer.ScrollTo(0f, false); - }); + }, (showScoresCancellationSource = new CancellationTokenSource()).Token); } } diff --git a/osu.Game/Online/Multiplayer/PlaylistItem.cs b/osu.Game/Online/Multiplayer/PlaylistItem.cs index 4155121bdf..63b5b95b9c 100644 --- a/osu.Game/Online/Multiplayer/PlaylistItem.cs +++ b/osu.Game/Online/Multiplayer/PlaylistItem.cs @@ -37,10 +37,10 @@ namespace osu.Game.Online.Multiplayer public RulesetInfo Ruleset { get; set; } [JsonIgnore] - public readonly BindableCollection AllowedMods = new BindableCollection(); + public readonly BindableList AllowedMods = new BindableList(); [JsonIgnore] - public readonly BindableCollection RequiredMods = new BindableCollection(); + public readonly BindableList RequiredMods = new BindableList(); [JsonProperty("beatmap")] private APIBeatmap apiBeatmap { get; set; } diff --git a/osu.Game/Online/Multiplayer/Room.cs b/osu.Game/Online/Multiplayer/Room.cs index 448f5ced91..5273c7acfb 100644 --- a/osu.Game/Online/Multiplayer/Room.cs +++ b/osu.Game/Online/Multiplayer/Room.cs @@ -24,7 +24,7 @@ namespace osu.Game.Online.Multiplayer public Bindable Host { get; private set; } = new Bindable(); [JsonProperty("playlist")] - public BindableCollection Playlist { get; set; } = new BindableCollection(); + public BindableList Playlist { get; set; } = new BindableList(); [JsonProperty("channel_id")] public Bindable ChannelId { get; private set; } = new Bindable(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index ad438a20c6..f33672ee23 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -26,6 +26,7 @@ using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; @@ -223,7 +224,7 @@ namespace osu.Game var databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID); // Use first beatmap available for current ruleset, else switch ruleset. - var first = databasedSet.Beatmaps.FirstOrDefault(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First(); + var first = databasedSet.Beatmaps.Find(b => b.Ruleset == ruleset.Value) ?? databasedSet.Beatmaps.First(); ruleset.Value = first.Ruleset; Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first); @@ -354,7 +355,14 @@ namespace osu.Game ActionRequested = action => volume.Adjust(action), ScrollActionRequested = (action, amount, isPrecise) => volume.Adjust(action, amount, isPrecise), }, - mainContent = new Container { RelativeSizeAxes = Axes.Both }, + screenContainer = new ScalingContainer(ScalingMode.ExcludeOverlays) + { + RelativeSizeAxes = Axes.Both, + }, + mainContent = new Container + { + RelativeSizeAxes = Axes.Both, + }, overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, idleTracker = new GameIdleTracker(6000) }); @@ -363,7 +371,7 @@ namespace osu.Game { screenStack.ModePushed += screenAdded; screenStack.Exited += screenRemoved; - mainContent.Add(screenStack); + screenContainer.Add(screenStack); }); loadComponentSingleFile(Toolbar = new Toolbar @@ -498,7 +506,7 @@ namespace osu.Game if (notifications.State == Visibility.Visible) offset -= ToolbarButton.WIDTH / 2; - screenStack.MoveToX(offset, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); + screenContainer.MoveToX(offset, SettingsOverlay.TRANSITION_LENGTH, Easing.OutQuint); } settings.StateChanged += _ => updateScreenOffset(); @@ -566,7 +574,7 @@ namespace osu.Game focused.StateChanged += s => { visibleOverlayCount += s == Visibility.Visible ? 1 : -1; - screenStack.FadeColour(visibleOverlayCount > 0 ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); + screenContainer.FadeColour(visibleOverlayCount > 0 ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint); }; } @@ -657,6 +665,7 @@ namespace osu.Game private OsuScreen currentScreen; private FrameworkConfigManager frameworkConfig; + private ScalingContainer screenContainer; protected override bool OnExiting() { @@ -696,7 +705,7 @@ namespace osu.Game ruleset.Disabled = applyBeatmapRulesetRestrictions; Beatmap.Disabled = applyBeatmapRulesetRestrictions; - mainContent.Padding = new MarginPadding { Top = ToolbarOffset }; + screenContainer.Padding = new MarginPadding { Top = ToolbarOffset }; MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; } diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 683fa30818..b6c642c9dc 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -24,6 +24,7 @@ using osu.Framework.Input; using osu.Framework.Logging; using osu.Game.Audio; using osu.Game.Database; +using osu.Game.Graphics.Containers; using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.IO; @@ -189,7 +190,7 @@ namespace osu.Game Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both } }; - base.Content.Add(new DrawSizePreservingFillContainer { Child = MenuCursorContainer }); + base.Content.Add(new ScalingContainer(ScalingMode.Everything) { Child = MenuCursorContainer }); KeyBindingStore.Register(globalBinding); dependencies.Cache(globalBinding); @@ -247,7 +248,8 @@ namespace osu.Game var extension = Path.GetExtension(paths.First())?.ToLowerInvariant(); foreach (var importer in fileImporters) - if (importer.HandledExtensions.Contains(extension)) importer.Import(paths); + if (importer.HandledExtensions.Contains(extension)) + importer.Import(paths); } public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); diff --git a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs index 94ee9d4bf6..c02215d690 100644 --- a/osu.Game/Overlays/Chat/Selection/ChannelSection.cs +++ b/osu.Game/Overlays/Chat/Selection/ChannelSection.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; using System.Linq; using osuTK; @@ -18,7 +19,7 @@ namespace osu.Game.Overlays.Chat.Selection public readonly FillFlowContainer ChannelFlow; public IEnumerable FilterableChildren => ChannelFlow.Children; - public IEnumerable FilterTerms => new[] { Header }; + public IEnumerable FilterTerms => Array.Empty(); public bool MatchingFilter { set diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs index 685244e06b..d59e2e033e 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/LayoutSettings.cs @@ -6,9 +6,14 @@ using System.Drawing; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Configuration; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.UserInterface; +using osuTK.Graphics; namespace osu.Game.Overlays.Settings.Sections.Graphics { @@ -16,24 +21,33 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { protected override string Header => "Layout"; - private FillFlowContainer letterboxSettings; + private FillFlowContainer> scalingSettings; - private Bindable letterboxing; + private Bindable scalingMode; private Bindable sizeFullscreen; private OsuGameBase game; private SettingsDropdown resolutionDropdown; private SettingsEnumDropdown windowModeDropdown; + private Bindable scalingPositionX; + private Bindable scalingPositionY; + private Bindable scalingSizeX; + private Bindable scalingSizeY; + private const int transition_duration = 400; [BackgroundDependencyLoader] - private void load(FrameworkConfigManager config, OsuGameBase game) + private void load(FrameworkConfigManager config, OsuConfigManager osuConfig, OsuGameBase game) { this.game = game; - letterboxing = config.GetBindable(FrameworkSetting.Letterboxing); + scalingMode = osuConfig.GetBindable(OsuSetting.Scaling); sizeFullscreen = config.GetBindable(FrameworkSetting.SizeFullscreen); + scalingSizeX = osuConfig.GetBindable(OsuSetting.ScalingSizeX); + scalingSizeY = osuConfig.GetBindable(OsuSetting.ScalingSizeY); + scalingPositionX = osuConfig.GetBindable(OsuSetting.ScalingPositionX); + scalingPositionY = osuConfig.GetBindable(OsuSetting.ScalingPositionY); Container resolutionSettingsContainer; @@ -49,12 +63,19 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y }, - new SettingsCheckbox + new SettingsSlider { - LabelText = "Letterboxing", - Bindable = letterboxing, + LabelText = "UI Scaling", + TransferValueOnCommit = true, + Bindable = osuConfig.GetBindable(OsuSetting.UIScale), + KeyboardStep = 0.01f }, - letterboxSettings = new FillFlowContainer + new SettingsEnumDropdown + { + LabelText = "Screen Scaling", + Bindable = osuConfig.GetBindable(OsuSetting.Scaling), + }, + scalingSettings = new FillFlowContainer> { Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, @@ -62,25 +83,38 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics AutoSizeDuration = transition_duration, AutoSizeEasing = Easing.OutQuint, Masking = true, - - Children = new Drawable[] + Children = new [] { - new SettingsSlider + new SettingsSlider { LabelText = "Horizontal position", - Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionX), + Bindable = scalingPositionX, KeyboardStep = 0.01f }, - new SettingsSlider + new SettingsSlider { LabelText = "Vertical position", - Bindable = config.GetBindable(FrameworkSetting.LetterboxPositionY), + Bindable = scalingPositionY, + KeyboardStep = 0.01f + }, + new SettingsSlider + { + LabelText = "Horizontal scale", + Bindable = scalingSizeX, + KeyboardStep = 0.01f + }, + new SettingsSlider + { + LabelText = "Vertical scale", + Bindable = scalingSizeY, KeyboardStep = 0.01f }, } }, }; + scalingSettings.ForEach(s => bindPreviewEvent(s.Bindable)); + var resolutions = getResolutions(); if (resolutions.Count > 1) @@ -105,16 +139,46 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics }, true); } - letterboxing.BindValueChanged(isVisible => + scalingMode.BindValueChanged(mode => { - letterboxSettings.ClearTransforms(); - letterboxSettings.AutoSizeAxes = isVisible ? Axes.Y : Axes.None; + scalingSettings.ClearTransforms(); + scalingSettings.AutoSizeAxes = mode != ScalingMode.Off ? Axes.Y : Axes.None; - if (!isVisible) - letterboxSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + if (mode == ScalingMode.Off) + scalingSettings.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + + scalingSettings.ForEach(s => s.TransferValueOnCommit = mode == ScalingMode.Everything); }, true); } + /// + /// Create a delayed bindable which only updates when a condition is met. + /// + /// The config bindable. + /// A bindable which will propagate updates with a delay. + private void bindPreviewEvent(Bindable bindable) + { + bindable.ValueChanged += v => + { + switch (scalingMode.Value) + { + case ScalingMode.Gameplay: + showPreview(); + break; + } + }; + } + + private Drawable preview; + private void showPreview() + { + if (preview?.IsAlive != true) + game.Add(preview = new ScalingPreview()); + + preview.FadeOutFromOne(1500); + preview.Expire(); + } + private IReadOnlyList getResolutions() { var resolutions = new List { new Size(9999, 9999) }; @@ -132,6 +196,24 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics return resolutions; } + private class ScalingPreview : ScalingContainer + { + public ScalingPreview() + { + Child = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + Alpha = 0.5f, + }; + } + } + + private class UIScaleSlider : OsuSliderBar + { + public override string TooltipText => base.TooltipText + "x"; + } + private class ResolutionSettingsDropdown : SettingsDropdown { protected override OsuDropdown CreateDropdown() => new ResolutionDropdownControl { Items = Items }; diff --git a/osu.Game/Overlays/Settings/SettingsLabel.cs b/osu.Game/Overlays/Settings/SettingsLabel.cs deleted file mode 100644 index 2df4073191..0000000000 --- a/osu.Game/Overlays/Settings/SettingsLabel.cs +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Game.Graphics; - -namespace osu.Game.Overlays.Settings -{ - public class SettingsLabel : SettingsItem - { - protected override Drawable CreateControl() => null; - - [BackgroundDependencyLoader] - private void load(OsuColour colour) - { - Colour = colour.Gray6; - } - } -} diff --git a/osu.Game/Overlays/Settings/SettingsSlider.cs b/osu.Game/Overlays/Settings/SettingsSlider.cs index a3698c36e6..39a974dd2e 100644 --- a/osu.Game/Overlays/Settings/SettingsSlider.cs +++ b/osu.Game/Overlays/Settings/SettingsSlider.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.UserInterface; @@ -23,14 +22,16 @@ namespace osu.Game.Overlays.Settings RelativeSizeAxes = Axes.X }; - public float KeyboardStep; - - [BackgroundDependencyLoader] - private void load() + public bool TransferValueOnCommit { - var slider = Control as U; - if (slider != null) - slider.KeyboardStep = KeyboardStep; + get => ((U)Control).TransferValueOnCommit; + set => ((U)Control).TransferValueOnCommit = value; + } + + public float KeyboardStep + { + get => ((U)Control).KeyboardStep; + set => ((U)Control).KeyboardStep = value; } } } diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index b863566967..78cc3592c7 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -31,7 +31,7 @@ namespace osu.Game.Scoring [Column(TypeName="DECIMAL(1,4)")] public double Accuracy { get; set; } - [JsonIgnore] + [JsonProperty(@"pp")] public double? PP { get; set; } [JsonProperty("max_combo")] diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenBlack.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenBlack.cs new file mode 100644 index 0000000000..c097d25178 --- /dev/null +++ b/osu.Game/Screens/Backgrounds/BackgroundScreenBlack.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Screens; +using osuTK.Graphics; + +namespace osu.Game.Screens.Backgrounds +{ + public class BackgroundScreenBlack : BackgroundScreen + { + public BackgroundScreenBlack() + { + Child = new Box + { + Colour = Color4.Black, + RelativeSizeAxes = Axes.Both, + }; + } + + protected override void OnEntering(Screen last) + { + Show(); + } + } +} diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs deleted file mode 100644 index 5e08db8907..0000000000 --- a/osu.Game/Screens/Backgrounds/BackgroundScreenEmpty.cs +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.Screens.Backgrounds -{ - public class BackgroundScreenEmpty : BackgroundScreen - { - } -} diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index aa63b02013..373f4d1682 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -238,11 +238,11 @@ namespace osu.Game.Screens.Edit.Compose.Components { case Key.Right: beatDivisor.Next(); - OnUserChange(); + OnUserChange(Current); return true; case Key.Left: beatDivisor.Previous(); - OnUserChange(); + OnUserChange(Current); return true; default: return false; @@ -279,7 +279,7 @@ namespace osu.Game.Screens.Edit.Compose.Components var xPosition = (ToLocalSpace(screenSpaceMousePosition).X - RangePadding) / UsableWidth; CurrentNumber.Value = availableDivisors.OrderBy(d => Math.Abs(getMappedPosition(d) - xPosition)).First(); - OnUserChange(); + OnUserChange(Current); } private float getMappedPosition(float divisor) => (float)Math.Pow((divisor - 1) / (availableDivisors.Last() - 1), 0.90f); diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index aa30b1a9f5..05ba1ab732 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -57,7 +57,7 @@ namespace osu.Game.Screens.Edit // Depending on beatSnapLength, we may snap to a beat that is beyond timingPoint's end time, but we want to instead snap to // the next timing point's start time - var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); + var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time); if (position > nextTimingPoint?.Time) position = nextTimingPoint.Time; @@ -123,7 +123,7 @@ namespace osu.Game.Screens.Edit if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First()) seekTime = timingPoint.Time; - var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); + var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time); if (seekTime > nextTimingPoint?.Time) seekTime = nextTimingPoint.Time; diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index fa01411a0f..8d9cd8dbe9 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Menu public override bool CursorVisible => false; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenEmpty(); + protected override BackgroundScreen CreateBackground() => new BackgroundScreenBlack(); private Bindable menuVoice; private Bindable menuMusic; diff --git a/osu.Game/Screens/Menu/MenuSideFlashes.cs b/osu.Game/Screens/Menu/MenuSideFlashes.cs index ec5528b13f..188e95ced5 100644 --- a/osu.Game/Screens/Menu/MenuSideFlashes.cs +++ b/osu.Game/Screens/Menu/MenuSideFlashes.cs @@ -58,6 +58,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.CentreLeft, RelativeSizeAxes = Axes.Y, Width = box_width * 2, + Height = 1.5f, // align off-screen to make sure our edges don't become visible during parallax. X = -box_width, Alpha = 0, @@ -70,6 +71,7 @@ namespace osu.Game.Screens.Menu Origin = Anchor.CentreRight, RelativeSizeAxes = Axes.Y, Width = box_width * 2, + Height = 1.5f, X = box_width, Alpha = 0, Blending = BlendingMode.Additive, diff --git a/osu.Game/Screens/Multi/IRoomManager.cs b/osu.Game/Screens/Multi/IRoomManager.cs index f0dbcb0e71..6af8a35208 100644 --- a/osu.Game/Screens/Multi/IRoomManager.cs +++ b/osu.Game/Screens/Multi/IRoomManager.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Multi /// /// All the active s. /// - IBindableCollection Rooms { get; } + IBindableList Rooms { get; } /// /// Creates a new . diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs index 47f5182c39..63730ff635 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomInspector.cs @@ -1,7 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Linq; +using System; using osu.Framework.Allocation; using osu.Framework.Configuration; using osu.Framework.Extensions.Color4Extensions; @@ -13,8 +13,9 @@ using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Online.Multiplayer; using osu.Game.Screens.Multi.Components; using osu.Game.Users; @@ -37,11 +38,10 @@ namespace osu.Game.Screens.Multi.Lounge.Components private Box statusStrip; private UpdateableBeatmapBackgroundSprite background; private ParticipantCountDisplay participantCount; - private FillFlowContainer topFlow, participantsFlow; private OsuSpriteText name, status; private BeatmapTypeInfo beatmapTypeInfo; - private ScrollContainer participantsScroll; private ParticipantInfo participantInfo; + private MatchParticipants participants; [Resolved] private BeatmapManager beatmaps { get; set; } @@ -58,141 +58,141 @@ namespace osu.Game.Screens.Multi.Lounge.Components RelativeSizeAxes = Axes.Both, Colour = OsuColour.FromHex(@"343138"), }, - topFlow = new FillFlowContainer + new GridContainer { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Children = new Drawable[] + RelativeSizeAxes = Axes.Both, + RowDimensions = new[] { - new Container + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Distributed), + }, + Content = new[] + { + new Drawable[] { - RelativeSizeAxes = Axes.X, - Height = 200, - Masking = true, - Children = new Drawable[] + new FillFlowContainer { - background = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }, - new Box + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4.Black.Opacity(0)), - }, - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(20), - Children = new Drawable[] + new Container { - participantCount = new ParticipantCountDisplay + RelativeSizeAxes = Axes.X, + Height = 200, + Masking = true, + Children = new Drawable[] { - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, + background = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.5f), Color4.Black.Opacity(0)), + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(20), + Children = new Drawable[] + { + participantCount = new ParticipantCountDisplay + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + name = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + TextSize = 30, + }, + }, + }, }, - name = new OsuSpriteText + }, + statusStrip = new Box + { + RelativeSizeAxes = Axes.X, + Height = 5, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - TextSize = 30, + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"28242d"), + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + LayoutDuration = transition_duration, + Padding = contentPadding, + Spacing = new Vector2(0f, 5f), + Children = new Drawable[] + { + status = new OsuSpriteText + { + TextSize = 14, + Font = @"Exo2.0-Bold", + }, + beatmapTypeInfo = new BeatmapTypeInfo(), + }, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = contentPadding, + Children = new Drawable[] + { + participantInfo = new ParticipantInfo(), }, }, }, }, }, - statusStrip = new Box + new Drawable[] { - RelativeSizeAxes = Axes.X, - Height = 5, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] + participants = new MatchParticipants { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"28242d"), - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - LayoutDuration = transition_duration, - Padding = contentPadding, - Spacing = new Vector2(0f, 5f), - Children = new Drawable[] - { - status = new OsuSpriteText - { - TextSize = 14, - Font = @"Exo2.0-Bold", - }, - beatmapTypeInfo = new BeatmapTypeInfo(), - }, - }, - }, - }, - new Container - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = contentPadding, - Children = new Drawable[] - { - participantInfo = new ParticipantInfo(), - }, - }, - }, - }, - participantsScroll = new OsuScrollContainer - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.X, - Padding = new MarginPadding { Top = contentPadding.Top, Left = 38, Right = 37 }, - Children = new[] - { - participantsFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - LayoutDuration = transition_duration, - Spacing = new Vector2(5f), - }, - }, - }, + RelativeSizeAxes = Axes.Both, + } + } + } + } }; participantInfo.Host.BindTo(bindings.Host); participantInfo.ParticipantCount.BindTo(bindings.ParticipantCount); participantInfo.Participants.BindTo(bindings.Participants); - participantCount.Participants.BindTo(bindings.Participants); participantCount.ParticipantCount.BindTo(bindings.ParticipantCount); participantCount.MaxParticipants.BindTo(bindings.MaxParticipants); - beatmapTypeInfo.Beatmap.BindTo(bindings.CurrentBeatmap); beatmapTypeInfo.Ruleset.BindTo(bindings.CurrentRuleset); beatmapTypeInfo.Type.BindTo(bindings.Type); background.Beatmap.BindTo(bindings.CurrentBeatmap); - bindings.Status.BindValueChanged(displayStatus); - bindings.Participants.BindValueChanged(p => participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u))); bindings.Name.BindValueChanged(n => name.Text = n); - Room.BindValueChanged(updateRoom, true); } private void updateRoom(Room room) { bindings.Room = room; + participants.Room = room; if (room != null) { - participantsFlow.FadeIn(transition_duration); participantCount.FadeIn(transition_duration); beatmapTypeInfo.FadeIn(transition_duration); name.FadeIn(transition_duration); @@ -200,7 +200,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components } else { - participantsFlow.FadeOut(transition_duration); participantCount.FadeOut(transition_duration); beatmapTypeInfo.FadeOut(transition_duration); name.FadeOut(transition_duration); @@ -210,13 +209,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components } } - protected override void UpdateAfterChildren() - { - base.UpdateAfterChildren(); - - participantsScroll.Height = DrawHeight - topFlow.DrawHeight; - } - private void displayStatus(RoomStatus s) { status.Text = s.Message; @@ -226,39 +218,113 @@ namespace osu.Game.Screens.Multi.Lounge.Components status.FadeColour(c, transition_duration); } - private class UserTile : Container, IHasTooltip - { - private readonly User user; - - public string TooltipText => user.Username; - - public UserTile(User user) - { - this.user = user; - Size = new Vector2(70f); - CornerRadius = 5f; - Masking = true; - - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex(@"27252d"), - }, - new UpdateableAvatar - { - RelativeSizeAxes = Axes.Both, - User = user, - }, - }; - } - } - private class RoomStatusNoneSelected : RoomStatus { public override string Message => @"No Room Selected"; public override Color4 GetAppropriateColour(OsuColour colours) => colours.Gray8; } + + private class MatchParticipants : CompositeDrawable + { + private Room room; + private readonly FillFlowContainer fill; + + public Room Room + { + get { return room; } + set + { + if (room == value) + return; + + room = value; + updateParticipants(); + } + } + + public MatchParticipants() + { + Padding = new MarginPadding { Horizontal = 10 }; + + InternalChild = new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = fill = new FillFlowContainer + { + Spacing = new Vector2(10), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + } + }; + } + + [Resolved] + private APIAccess api { get; set; } + + private GetRoomScoresRequest request; + + private void updateParticipants() + { + var roomId = room.RoomID.Value ?? 0; + + request?.Cancel(); + + // nice little progressive fade + int time = 500; + foreach (var c in fill.Children) + { + c.Delay(500 - time).FadeOut(time, Easing.Out); + time = Math.Max(20, time - 20); + c.Expire(); + } + + if (roomId == 0) return; + + request = new GetRoomScoresRequest(roomId); + request.Success += scores => + { + if (roomId != room.RoomID.Value) + return; + + fill.Clear(); + foreach (var s in scores) + fill.Add(new UserTile(s.User)); + + fill.FadeInFromZero(1000, Easing.OutQuint); + }; + + api.Queue(request); + } + + private class UserTile : CompositeDrawable, IHasTooltip + { + private readonly User user; + + public string TooltipText => user.Username; + + public UserTile(User user) + { + this.user = user; + Size = new Vector2(70f); + CornerRadius = 5f; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"27252d"), + }, + new UpdateableAvatar + { + RelativeSizeAxes = Axes.Both, + User = user, + }, + }; + } + } + } } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 5133e96a52..4ad8154090 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components private readonly Bindable selectedRoom = new Bindable(); public IBindable SelectedRoom => selectedRoom; - private readonly IBindableCollection rooms = new BindableCollection(); + private readonly IBindableList rooms = new BindableList(); private readonly FillFlowContainer roomFlow; public IReadOnlyList Rooms => roomFlow; diff --git a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs index 864191105f..5ac0453373 100644 --- a/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs +++ b/osu.Game/Screens/Multi/Match/Components/MatchLeaderboard.cs @@ -16,17 +16,18 @@ namespace osu.Game.Screens.Multi.Match.Components { public Action> ScoresLoaded; - private readonly Room room; - - public MatchLeaderboard(Room room) + public Room Room { - this.room = room; + get => bindings.Room; + set => bindings.Room = value; } + private readonly RoomBindings bindings = new RoomBindings(); + [BackgroundDependencyLoader] private void load() { - room.RoomID.BindValueChanged(id => + bindings.RoomID.BindValueChanged(id => { if (id == null) return; @@ -38,10 +39,10 @@ namespace osu.Game.Screens.Multi.Match.Components protected override APIRequest FetchScores(Action> scoresCallback) { - if (room.RoomID == null) + if (bindings.RoomID.Value == null) return null; - var req = new GetRoomScoresRequest(room.RoomID.Value ?? 0); + var req = new GetRoomScoresRequest(bindings.RoomID.Value ?? 0); req.Success += r => { diff --git a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs index 55a5a2c85e..14cdd90128 100644 --- a/osu.Game/Screens/Multi/Match/MatchSubScreen.cs +++ b/osu.Game/Screens/Multi/Match/MatchSubScreen.cs @@ -71,10 +71,11 @@ namespace osu.Game.Screens.Multi.Match { new Drawable[] { - leaderboard = new MatchLeaderboard(room) + leaderboard = new MatchLeaderboard { Padding = new MarginPadding(10), - RelativeSizeAxes = Axes.Both + RelativeSizeAxes = Axes.Both, + Room = room }, new Container { diff --git a/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs index 54528e5503..44f5f11c93 100644 --- a/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs +++ b/osu.Game/Screens/Multi/Ranking/Pages/RoomLeaderboardPage.cs @@ -103,8 +103,8 @@ namespace osu.Game.Screens.Multi.Ranking.Pages public class ResultsMatchLeaderboard : MatchLeaderboard { public ResultsMatchLeaderboard(Room room) - : base(room) { + Room = room; } protected override bool FadeTop => true; diff --git a/osu.Game/Screens/Multi/RoomBindings.cs b/osu.Game/Screens/Multi/RoomBindings.cs index dc2547268d..30e2918b69 100644 --- a/osu.Game/Screens/Multi/RoomBindings.cs +++ b/osu.Game/Screens/Multi/RoomBindings.cs @@ -39,6 +39,7 @@ namespace osu.Game.Screens.Multi if (room != null) { + RoomID.UnbindFrom(room.RoomID); Name.UnbindFrom(room.Name); Host.UnbindFrom(room.Host); Status.UnbindFrom(room.Status); @@ -56,6 +57,7 @@ namespace osu.Game.Screens.Multi if (room != null) { + RoomID.BindTo(room.RoomID); Name.BindTo(room.Name); Host.BindTo(room.Host); Status.BindTo(room.Status); @@ -82,11 +84,12 @@ namespace osu.Game.Screens.Multi currentRuleset.Value = playlistItem?.Ruleset; } + public readonly Bindable RoomID = new Bindable(); public readonly Bindable Name = new Bindable(); public readonly Bindable Host = new Bindable(); public readonly Bindable Status = new Bindable(); public readonly Bindable Type = new Bindable(); - public readonly BindableCollection Playlist = new BindableCollection(); + public readonly BindableList Playlist = new BindableList(); public readonly Bindable> Participants = new Bindable>(); public readonly Bindable ParticipantCount = new Bindable(); public readonly Bindable MaxParticipants = new Bindable(); diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index fab19c3fd7..1f95401905 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -21,8 +21,8 @@ namespace osu.Game.Screens.Multi { public event Action RoomsUpdated; - private readonly BindableCollection rooms = new BindableCollection(); - public IBindableCollection Rooms => rooms; + private readonly BindableList rooms = new BindableList(); + public IBindableList Rooms => rooms; private Room currentRoom; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 14dc644100..d5c99f5729 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -20,6 +20,7 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Online.API; using osu.Game.Overlays; @@ -179,10 +180,13 @@ namespace osu.Game.Screens.Play RelativeSizeAxes = Axes.Both, Alpha = 0, }, - new LocalSkinOverrideContainer(working.Skin) + new ScalingContainer(ScalingMode.Gameplay) { - RelativeSizeAxes = Axes.Both, - Child = RulesetContainer + Child = new LocalSkinOverrideContainer(working.Skin) + { + RelativeSizeAxes = Axes.Both, + Child = RulesetContainer + } }, new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, ScoreProcessor) { @@ -191,7 +195,10 @@ namespace osu.Game.Screens.Play ProcessCustomClock = false, Breaks = beatmap.Breaks }, - RulesetContainer.Cursor?.CreateProxy() ?? new Container(), + new ScalingContainer(ScalingMode.Gameplay) + { + Child = RulesetContainer.Cursor?.CreateProxy() ?? new Container(), + }, hudOverlay = new HUDOverlay(ScoreProcessor, RulesetContainer, working, offsetClock, adjustableClock) { Clock = Clock, // hud overlay doesn't want to use the audio clock directly diff --git a/osu.Game/Screens/Play/SongProgressBar.cs b/osu.Game/Screens/Play/SongProgressBar.cs index 1f0c4936a5..b06a34e603 100644 --- a/osu.Game/Screens/Play/SongProgressBar.cs +++ b/osu.Game/Screens/Play/SongProgressBar.cs @@ -112,6 +112,6 @@ namespace osu.Game.Screens.Play handleBase.X = xFill; } - protected override void OnUserChange() => OnSeek?.Invoke(Current); + protected override void OnUserChange(double value) => OnSeek?.Invoke(value); } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f65cc0e49d..2f212a2564 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -297,14 +297,14 @@ namespace osu.Game.Screens.Select /// Whether to trigger . public void FinaliseSelection(BeatmapInfo beatmap = null, bool performStartAction = true) { - // avoid attempting to continue before a selection has been obtained. - // this could happen via a user interaction while the carousel is still in a loading state. - if (Carousel.SelectedBeatmap == null) return; - // if we have a pending filter operation, we want to run it now. // it could change selection (ie. if the ruleset has been changed). Carousel.FlushPendingFilterOperations(); + // avoid attempting to continue before a selection has been obtained. + // this could happen via a user interaction while the carousel is still in a loading state. + if (Carousel.SelectedBeatmap == null) return; + if (beatmap != null) Carousel.SelectBeatmap(beatmap); diff --git a/osu.Game/Screens/Tournament/Drawings.cs b/osu.Game/Screens/Tournament/Drawings.cs index 0a428ea289..754f34f00f 100644 --- a/osu.Game/Screens/Tournament/Drawings.cs +++ b/osu.Game/Screens/Tournament/Drawings.cs @@ -327,7 +327,7 @@ namespace osu.Game.Screens.Tournament continue; // ReSharper disable once AccessToModifiedClosure - DrawingsTeam teamToAdd = allTeams.FirstOrDefault(t => t.FullName == line); + DrawingsTeam teamToAdd = allTeams.Find(t => t.FullName == line); if (teamToAdd == null) continue; diff --git a/osu.Game/Skinning/LegacySkin.cs b/osu.Game/Skinning/LegacySkin.cs index 25a9813ad6..23367c58c9 100644 --- a/osu.Game/Skinning/LegacySkin.cs +++ b/osu.Game/Skinning/LegacySkin.cs @@ -102,7 +102,7 @@ namespace osu.Game.Skinning string lastPiece = filename.Split('/').Last(); - var file = source.Files.FirstOrDefault(f => + var file = source.Files.Find(f => string.Equals(hasExtension ? f.Filename : Path.ChangeExtension(f.Filename, null), lastPiece, StringComparison.InvariantCultureIgnoreCase)); return file?.FileInfo.StoragePath; } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index 8f860c5f53..2330c25443 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Textures; -using System.Linq; using osu.Game.Beatmaps; namespace osu.Game.Storyboards.Drawables @@ -71,7 +70,7 @@ namespace osu.Game.Storyboards.Drawables { var framePath = basePath.Replace(".", frame + "."); - var path = beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == framePath)?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.ToLowerInvariant() == framePath)?.FileInfo.StoragePath; if (path == null) continue; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 485f9bf61c..b03285b419 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -6,7 +6,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; -using System.Linq; using osu.Game.Beatmaps; namespace osu.Game.Storyboards.Drawables @@ -66,7 +65,7 @@ namespace osu.Game.Storyboards.Drawables private void load(IBindableBeatmap beatmap, TextureStore textureStore) { var spritePath = Sprite.Path.ToLowerInvariant(); - var path = beatmap.Value.BeatmapSetInfo.Files.FirstOrDefault(f => f.Filename.ToLowerInvariant() == spritePath)?.FileInfo.StoragePath; + var path = beatmap.Value.BeatmapSetInfo.Files.Find(f => f.Filename.ToLowerInvariant() == spritePath)?.FileInfo.StoragePath; if (path == null) return; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 103c7c20d6..8f00e81237 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -18,7 +18,7 @@ - +