diff --git a/osu-framework b/osu-framework index 0f3db5da09..e5f0cf73c1 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 0f3db5da09d0e7c4d2ef3057030e018f34ba536e +Subproject commit e5f0cf73c1e0bbcbd04194bf175d73af47fc850a diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index c6aac3bb71..4c540fa8cf 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.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.Globalization; using System.IO; using OpenTK.Graphics; @@ -31,6 +32,8 @@ namespace osu.Game.Beatmaps.Formats private ConvertHitObjectParser parser; + private readonly Dictionary variables = new Dictionary(); + private LegacySampleBank defaultSampleBank; private int defaultSampleVolume = 100; @@ -56,36 +59,39 @@ namespace osu.Game.Beatmaps.Formats TimingPoints, Colours, HitObjects, + Variables, } - private void handleGeneral(Beatmap beatmap, string key, string val) + private void handleGeneral(Beatmap beatmap, string line) { + var pair = splitKeyVal(line, ':'); + var metadata = beatmap.BeatmapInfo.Metadata; - switch (key) + switch (pair.Key) { case @"AudioFilename": - metadata.AudioFile = val; + metadata.AudioFile = pair.Value; break; case @"AudioLeadIn": - beatmap.BeatmapInfo.AudioLeadIn = int.Parse(val); + beatmap.BeatmapInfo.AudioLeadIn = int.Parse(pair.Value); break; case @"PreviewTime": - metadata.PreviewTime = int.Parse(val); + metadata.PreviewTime = int.Parse(pair.Value); break; case @"Countdown": - beatmap.BeatmapInfo.Countdown = int.Parse(val) == 1; + beatmap.BeatmapInfo.Countdown = int.Parse(pair.Value) == 1; break; case @"SampleSet": - defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), val); + defaultSampleBank = (LegacySampleBank)Enum.Parse(typeof(LegacySampleBank), pair.Value); break; case @"SampleVolume": - defaultSampleVolume = int.Parse(val); + defaultSampleVolume = int.Parse(pair.Value); break; case @"StackLeniency": - beatmap.BeatmapInfo.StackLeniency = float.Parse(val, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.StackLeniency = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"Mode": - beatmap.BeatmapInfo.RulesetID = int.Parse(val); + beatmap.BeatmapInfo.RulesetID = int.Parse(pair.Value); switch (beatmap.BeatmapInfo.RulesetID) { @@ -104,107 +110,135 @@ namespace osu.Game.Beatmaps.Formats } break; case @"LetterboxInBreaks": - beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(val) == 1; + beatmap.BeatmapInfo.LetterboxInBreaks = int.Parse(pair.Value) == 1; break; case @"SpecialStyle": - beatmap.BeatmapInfo.SpecialStyle = int.Parse(val) == 1; + beatmap.BeatmapInfo.SpecialStyle = int.Parse(pair.Value) == 1; break; case @"WidescreenStoryboard": - beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(val) == 1; + beatmap.BeatmapInfo.WidescreenStoryboard = int.Parse(pair.Value) == 1; break; } } - private void handleEditor(Beatmap beatmap, string key, string val) + private void handleEditor(Beatmap beatmap, string line) { - switch (key) + var pair = splitKeyVal(line, ':'); + + switch (pair.Key) { case @"Bookmarks": - beatmap.BeatmapInfo.StoredBookmarks = val; + beatmap.BeatmapInfo.StoredBookmarks = pair.Value; break; case @"DistanceSpacing": - beatmap.BeatmapInfo.DistanceSpacing = double.Parse(val, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.DistanceSpacing = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"BeatDivisor": - beatmap.BeatmapInfo.BeatDivisor = int.Parse(val); + beatmap.BeatmapInfo.BeatDivisor = int.Parse(pair.Value); break; case @"GridSize": - beatmap.BeatmapInfo.GridSize = int.Parse(val); + beatmap.BeatmapInfo.GridSize = int.Parse(pair.Value); break; case @"TimelineZoom": - beatmap.BeatmapInfo.TimelineZoom = double.Parse(val, NumberFormatInfo.InvariantInfo); + beatmap.BeatmapInfo.TimelineZoom = double.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; } } - private void handleMetadata(Beatmap beatmap, string key, string val) + private void handleMetadata(Beatmap beatmap, string line) { + var pair = splitKeyVal(line, ':'); + var metadata = beatmap.BeatmapInfo.Metadata; - switch (key) + switch (pair.Key) { case @"Title": - metadata.Title = val; + metadata.Title = pair.Value; break; case @"TitleUnicode": - metadata.TitleUnicode = val; + metadata.TitleUnicode = pair.Value; break; case @"Artist": - metadata.Artist = val; + metadata.Artist = pair.Value; break; case @"ArtistUnicode": - metadata.ArtistUnicode = val; + metadata.ArtistUnicode = pair.Value; break; case @"Creator": - metadata.Author = val; + metadata.Author = pair.Value; break; case @"Version": - beatmap.BeatmapInfo.Version = val; + beatmap.BeatmapInfo.Version = pair.Value; break; case @"Source": - beatmap.BeatmapInfo.Metadata.Source = val; + beatmap.BeatmapInfo.Metadata.Source = pair.Value; break; case @"Tags": - beatmap.BeatmapInfo.Metadata.Tags = val; + beatmap.BeatmapInfo.Metadata.Tags = pair.Value; break; case @"BeatmapID": - beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(val); + beatmap.BeatmapInfo.OnlineBeatmapID = int.Parse(pair.Value); break; case @"BeatmapSetID": - beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(val); - metadata.OnlineBeatmapSetID = int.Parse(val); + beatmap.BeatmapInfo.OnlineBeatmapSetID = int.Parse(pair.Value); + metadata.OnlineBeatmapSetID = int.Parse(pair.Value); break; } } - private void handleDifficulty(Beatmap beatmap, string key, string val) + private void handleDifficulty(Beatmap beatmap, string line) { + var pair = splitKeyVal(line, ':'); + var difficulty = beatmap.BeatmapInfo.Difficulty; - switch (key) + switch (pair.Key) { case @"HPDrainRate": - difficulty.DrainRate = float.Parse(val, NumberFormatInfo.InvariantInfo); + difficulty.DrainRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"CircleSize": - difficulty.CircleSize = float.Parse(val, NumberFormatInfo.InvariantInfo); + difficulty.CircleSize = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"OverallDifficulty": - difficulty.OverallDifficulty = float.Parse(val, NumberFormatInfo.InvariantInfo); + difficulty.OverallDifficulty = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"ApproachRate": - difficulty.ApproachRate = float.Parse(val, NumberFormatInfo.InvariantInfo); + difficulty.ApproachRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"SliderMultiplier": - difficulty.SliderMultiplier = float.Parse(val, NumberFormatInfo.InvariantInfo); + difficulty.SliderMultiplier = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; case @"SliderTickRate": - difficulty.SliderTickRate = float.Parse(val, NumberFormatInfo.InvariantInfo); + difficulty.SliderTickRate = float.Parse(pair.Value, NumberFormatInfo.InvariantInfo); break; } } - private void handleEvents(Beatmap beatmap, string val) + /// + /// Decodes any beatmap variables present in a line into their real values. + /// + /// The line which may contains variables. + private void decodeVariables(ref string line) { - string[] split = val.Split(','); + while (line.IndexOf('$') >= 0) + { + string[] split = line.Split(','); + for (int i = 0; i < split.Length; i++) + { + var item = split[i]; + if (item.StartsWith("$") && variables.ContainsKey(item)) + split[i] = variables[item]; + } + + line = string.Join(",", split); + } + } + + private void handleEvents(Beatmap beatmap, string line) + { + decodeVariables(ref line); + + string[] split = line.Split(','); EventType type; if (!Enum.TryParse(split[0], out type)) @@ -236,9 +270,9 @@ namespace osu.Game.Beatmaps.Formats } } - private void handleTimingPoints(Beatmap beatmap, string val) + private void handleTimingPoints(Beatmap beatmap, string line) { - string[] split = val.Split(','); + string[] split = line.Split(','); double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo); double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo); @@ -321,12 +355,14 @@ namespace osu.Game.Beatmaps.Formats } } - private void handleColours(Beatmap beatmap, string key, string val, ref bool hasCustomColours) + private void handleColours(Beatmap beatmap, string line, ref bool hasCustomColours) { - string[] split = val.Split(','); + var pair = splitKeyVal(line, ':'); + + string[] split = pair.Value.Split(','); if (split.Length != 3) - throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {val}"); + throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B): {pair.Value}"); byte r, g, b; if (!byte.TryParse(split[0], out r) || !byte.TryParse(split[1], out g) || !byte.TryParse(split[2], out b)) @@ -339,7 +375,7 @@ namespace osu.Game.Beatmaps.Formats } // Note: the combo index specified in the beatmap is discarded - if (key.StartsWith(@"Combo")) + if (pair.Key.StartsWith(@"Combo")) { beatmap.ComboColors.Add(new Color4 { @@ -351,6 +387,12 @@ namespace osu.Game.Beatmaps.Formats } } + private void handleVariables(string line) + { + var pair = splitKeyVal(line, '='); + variables[pair.Key] = pair.Value; + } + protected override Beatmap ParseFile(StreamReader stream) { return new LegacyBeatmap(base.ParseFile(stream)); @@ -390,42 +432,39 @@ namespace osu.Game.Beatmaps.Formats continue; } - string val = line, key = null; - if (section != Section.Events && section != Section.TimingPoints && section != Section.HitObjects) - { - key = val.Remove(val.IndexOf(':')).Trim(); - val = val.Substring(val.IndexOf(':') + 1).Trim(); - } switch (section) { case Section.General: - handleGeneral(beatmap, key, val); + handleGeneral(beatmap, line); break; case Section.Editor: - handleEditor(beatmap, key, val); + handleEditor(beatmap, line); break; case Section.Metadata: - handleMetadata(beatmap, key, val); + handleMetadata(beatmap, line); break; case Section.Difficulty: - handleDifficulty(beatmap, key, val); + handleDifficulty(beatmap, line); break; case Section.Events: - handleEvents(beatmap, val); + handleEvents(beatmap, line); break; case Section.TimingPoints: - handleTimingPoints(beatmap, val); + handleTimingPoints(beatmap, line); break; case Section.Colours: - handleColours(beatmap, key, val, ref hasCustomColours); + handleColours(beatmap, line, ref hasCustomColours); break; case Section.HitObjects: - var obj = parser.Parse(val); + var obj = parser.Parse(line); if (obj != null) beatmap.HitObjects.Add(obj); break; + case Section.Variables: + handleVariables(line); + break; } } @@ -433,6 +472,15 @@ namespace osu.Game.Beatmaps.Formats hitObject.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty); } + private KeyValuePair splitKeyVal(string line, char separator) + { + return new KeyValuePair + ( + line.Remove(line.IndexOf(separator)).Trim(), + line.Substring(line.IndexOf(separator) + 1).Trim() + ); + } + internal enum LegacySampleBank { None = 0, diff --git a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs index fe1d255bba..42fff0f258 100644 --- a/osu.Game/Graphics/UserInterface/FocusedTextBox.cs +++ b/osu.Game/Graphics/UserInterface/FocusedTextBox.cs @@ -37,11 +37,10 @@ namespace osu.Game.Graphics.UserInterface this.inputManager = inputManager; } - protected override bool OnFocus(InputState state) + protected override void OnFocus(InputState state) { - var result = base.OnFocus(state); + base.OnFocus(state); BorderThickness = 0; - return result; } protected override void OnFocusLost(InputState state) @@ -56,6 +55,6 @@ namespace osu.Game.Graphics.UserInterface base.OnFocusLost(state); } - public override bool RequestingFocus => HoldFocus; + public override bool RequestsFocus => HoldFocus; } } diff --git a/osu.Game/Graphics/UserInterface/OsuDropdown.cs b/osu.Game/Graphics/UserInterface/OsuDropdown.cs index 14483f3bfb..6dadd63ac4 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropdown.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropdown.cs @@ -72,7 +72,7 @@ namespace osu.Game.Graphics.UserInterface Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, - new OsuSpriteText { + Label = new OsuSpriteText { Text = text, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, @@ -85,6 +85,7 @@ namespace osu.Game.Graphics.UserInterface private Color4? accentColour; protected readonly TextAwesome Chevron; + protected readonly OsuSpriteText Label; protected override void FormatForeground(bool hover = false) { @@ -170,4 +171,4 @@ namespace osu.Game.Graphics.UserInterface } } } -} \ No newline at end of file +} diff --git a/osu.Game/Graphics/UserInterface/OsuTextBox.cs b/osu.Game/Graphics/UserInterface/OsuTextBox.cs index 97c38f6b85..3512b4cdb1 100644 --- a/osu.Game/Graphics/UserInterface/OsuTextBox.cs +++ b/osu.Game/Graphics/UserInterface/OsuTextBox.cs @@ -45,11 +45,10 @@ namespace osu.Game.Graphics.UserInterface BorderColour = colour.Yellow; } - protected override bool OnFocus(InputState state) + protected override void OnFocus(InputState state) { BorderThickness = 3; - - return base.OnFocus(state); + base.OnFocus(state); } protected override void OnFocusLost(InputState state) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 5b2c01151c..a9970e5e95 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -167,11 +167,15 @@ namespace osu.Game.Overlays } } - protected override bool OnFocus(InputState state) + public override bool AcceptsFocus => true; + + protected override bool OnClick(InputState state) => true; + + protected override void OnFocus(InputState state) { //this is necessary as inputTextBox is masked away and therefore can't get focus :( InputManager.ChangeFocus(inputTextBox); - return false; + base.OnFocus(state); } protected override void PopIn() diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 916c774ecc..281d27b7c1 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -131,7 +131,7 @@ namespace osu.Game.Overlays if (BeatmapSets == null) return; panels.Children = BeatmapSets.Select(b => displayStyle == PanelDisplayStyle.Grid ? (DirectPanel)new DirectGridPanel(b) { Width = 400 } : new DirectListPanel(b)); } - + public class ResultCounts { public readonly int Artists; diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 9b72cfce42..0618f96cac 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -135,7 +135,7 @@ namespace osu.Game.Overlays.Music private bool matching = true; - public bool MatchingCurrentFilter + public bool MatchingFilter { set { diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index ffe59a9d93..eeb072fb00 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -22,7 +22,7 @@ namespace osu.Game.Overlays.Music } } - public BeatmapSetInfo FirstVisibleSet => items.Children.FirstOrDefault(i => i.MatchingCurrentFilter)?.BeatmapSetInfo; + public BeatmapSetInfo FirstVisibleSet => items.Children.FirstOrDefault(i => i.MatchingFilter)?.BeatmapSetInfo; private void itemSelected(BeatmapSetInfo b) { @@ -75,7 +75,7 @@ namespace osu.Game.Overlays.Music private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren { public string[] FilterTerms => new string[] { }; - public bool MatchingCurrentFilter + public bool MatchingFilter { set { diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index 00ca50927e..11a964d179 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -166,10 +166,14 @@ namespace osu.Game.Overlays.Settings.Sections.General if (form != null) inputManager.ChangeFocus(form); } - protected override bool OnFocus(InputState state) + public override bool AcceptsFocus => true; + + protected override bool OnClick(InputState state) => true; + + protected override void OnFocus(InputState state) { if (form != null) inputManager.ChangeFocus(form); - return base.OnFocus(state); + base.OnFocus(state); } private class LoginForm : FillFlowContainer @@ -235,10 +239,13 @@ namespace osu.Game.Overlays.Settings.Sections.General }; } - protected override bool OnFocus(InputState state) + public override bool AcceptsFocus => true; + + protected override bool OnClick(InputState state) => true; + + protected override void OnFocus(InputState state) { Schedule(() => { inputManager.ChangeFocus(string.IsNullOrEmpty(username.Text) ? username : password); }); - return base.OnFocus(state); } } @@ -338,8 +345,8 @@ namespace osu.Game.Overlays.Settings.Sections.General { public UserDropdownMenuItem(string text, UserAction current) : base(text, current) { - Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = UserDropdownHeader.LABEL_LEFT_MARGIN, Right = 5 }; - Chevron.Margin = new MarginPadding { Left = 2, Right = 3 }; + Foreground.Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 5 }; + Label.Margin = new MarginPadding { Left = UserDropdownHeader.LABEL_LEFT_MARGIN - 11 }; CornerRadius = 5; } } diff --git a/osu.Game/Overlays/Settings/SettingsItem.cs b/osu.Game/Overlays/Settings/SettingsItem.cs index e592ca9e37..7cddefb755 100644 --- a/osu.Game/Overlays/Settings/SettingsItem.cs +++ b/osu.Game/Overlays/Settings/SettingsItem.cs @@ -55,7 +55,7 @@ namespace osu.Game.Overlays.Settings public string[] FilterTerms => new[] { LabelText }; - public bool MatchingCurrentFilter + public bool MatchingFilter { set { diff --git a/osu.Game/Overlays/Settings/SettingsSection.cs b/osu.Game/Overlays/Settings/SettingsSection.cs index 8b95c72412..e65b7f19d9 100644 --- a/osu.Game/Overlays/Settings/SettingsSection.cs +++ b/osu.Game/Overlays/Settings/SettingsSection.cs @@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Settings public IEnumerable FilterableChildren => Children.OfType(); public string[] FilterTerms => new[] { Header }; - public bool MatchingCurrentFilter + public bool MatchingFilter { set { diff --git a/osu.Game/Overlays/Settings/SettingsSubsection.cs b/osu.Game/Overlays/Settings/SettingsSubsection.cs index 30abbc3805..44328ae867 100644 --- a/osu.Game/Overlays/Settings/SettingsSubsection.cs +++ b/osu.Game/Overlays/Settings/SettingsSubsection.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Settings public IEnumerable FilterableChildren => Children.OfType(); public string[] FilterTerms => new[] { Header }; - public bool MatchingCurrentFilter + public bool MatchingFilter { set { diff --git a/osu.Game/Overlays/SettingsOverlay.cs b/osu.Game/Overlays/SettingsOverlay.cs index 474631fd1e..87f6d836af 100644 --- a/osu.Game/Overlays/SettingsOverlay.cs +++ b/osu.Game/Overlays/SettingsOverlay.cs @@ -138,10 +138,14 @@ namespace osu.Game.Overlays InputManager.ChangeFocus(null); } - protected override bool OnFocus(InputState state) + public override bool AcceptsFocus => true; + + protected override bool OnClick(InputState state) => true; + + protected override void OnFocus(InputState state) { InputManager.ChangeFocus(searchTextBox); - return false; + base.OnFocus(state); } private class SettingsSectionsContainer : SectionsContainer @@ -159,7 +163,7 @@ namespace osu.Game.Overlays public SettingsSectionsContainer() { - ScrollContainer.ScrollDraggerVisible = false; + ScrollContainer.ScrollbarVisible = false; Add(headerBackground = new Box { Colour = Color4.Black, diff --git a/osu.Game/Screens/OsuScreen.cs b/osu.Game/Screens/OsuScreen.cs index 6a0e37ca6f..de9c698f2a 100644 --- a/osu.Game/Screens/OsuScreen.cs +++ b/osu.Game/Screens/OsuScreen.cs @@ -131,7 +131,15 @@ namespace osu.Game.Screens Background.Exit(); } - return base.OnExiting(next); + if (base.OnExiting(next)) + return true; + + // while this is not necessary as we are constructing our own bindable, there are cases where + // the GC doesn't run as fast as expected and this is triggered post-exit. + // added to resolve https://github.com/ppy/osu/issues/829 + beatmap.ValueChanged -= OnBeatmapChanged; + + return false; } } } diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index a7aa752d65..7d97581a29 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -74,7 +74,7 @@ namespace osu.Game.Screens.Select.Leaderboards scrollContainer = new ScrollContainer { RelativeSizeAxes = Axes.Both, - ScrollDraggerVisible = false, + ScrollbarVisible = false, Children = new Drawable[] { scrollFlow = new FillFlowContainer