diff --git a/osu-framework b/osu-framework index 036b124eb2..db310bfc10 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 036b124eb2387dde29af56e06391c42063ec85e2 +Subproject commit db310bfc10cd1c9ed12c9e19cdc0edfa53117353 diff --git a/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs index aff2903078..c2e33f7f32 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseGamefield.cs @@ -48,7 +48,7 @@ namespace osu.Desktop.VisualTests.Tests HitObjects = objects, BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BaseDifficulty(), + Difficulty = new BeatmapDifficulty(), Metadata = new BeatmapMetadata { Artist = @"Unknown", diff --git a/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs b/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs index 84bb1cfde6..c97ea929f3 100644 --- a/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs +++ b/osu.Desktop.VisualTests/Tests/TestCasePlaySongSelect.cs @@ -8,6 +8,7 @@ using osu.Framework.MathUtils; using osu.Game.Database; using osu.Game.Modes; using osu.Game.Screens.Select; +using osu.Game.Screens.Select.Filter; namespace osu.Desktop.VisualTests.Tests { @@ -39,10 +40,10 @@ namespace osu.Desktop.VisualTests.Tests Add(songSelect = new PlaySongSelect()); - AddButton(@"Sort by Artist", delegate { songSelect.Filter.Sort = FilterControl.SortMode.Artist; }); - AddButton(@"Sort by Title", delegate { songSelect.Filter.Sort = FilterControl.SortMode.Title; }); - AddButton(@"Sort by Author", delegate { songSelect.Filter.Sort = FilterControl.SortMode.Author; }); - AddButton(@"Sort by Difficulty", delegate { songSelect.Filter.Sort = FilterControl.SortMode.Difficulty; }); + AddButton(@"Sort by Artist", delegate { songSelect.FilterControl.Sort = SortMode.Artist; }); + AddButton(@"Sort by Title", delegate { songSelect.FilterControl.Sort = SortMode.Title; }); + AddButton(@"Sort by Author", delegate { songSelect.FilterControl.Sort = SortMode.Author; }); + AddButton(@"Sort by Difficulty", delegate { songSelect.FilterControl.Sort = SortMode.Difficulty; }); } protected override void Dispose(bool isDisposing) @@ -79,7 +80,7 @@ namespace osu.Desktop.VisualTests.Tests Mode = PlayMode.Osu, Path = "normal.osu", Version = "Normal", - BaseDifficulty = new BaseDifficulty + Difficulty = new BeatmapDifficulty { OverallDifficulty = 3.5f, } @@ -90,7 +91,7 @@ namespace osu.Desktop.VisualTests.Tests Mode = PlayMode.Osu, Path = "hard.osu", Version = "Hard", - BaseDifficulty = new BaseDifficulty + Difficulty = new BeatmapDifficulty { OverallDifficulty = 5, } @@ -101,7 +102,7 @@ namespace osu.Desktop.VisualTests.Tests Mode = PlayMode.Osu, Path = "insane.osu", Version = "Insane", - BaseDifficulty = new BaseDifficulty + Difficulty = new BeatmapDifficulty { OverallDifficulty = 7, } diff --git a/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs b/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs index 0d32532f09..41bd24b900 100644 --- a/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs +++ b/osu.Desktop.VisualTests/Tests/TestCasePlayer.cs @@ -65,7 +65,7 @@ namespace osu.Desktop.VisualTests.Tests HitObjects = objects, BeatmapInfo = new BeatmapInfo { - BaseDifficulty = new BaseDifficulty(), + Difficulty = new BeatmapDifficulty(), Metadata = new BeatmapMetadata { Artist = @"Unknown", diff --git a/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs b/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs new file mode 100644 index 0000000000..da807d5e53 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseTabControl.cs @@ -0,0 +1,45 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Screens.Testing; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Select.Filter; + +namespace osu.Desktop.VisualTests.Tests +{ + public class TestCaseTabControl : TestCase + { + public override string Description => @"Filter for song select"; + + public override void Reset() + { + base.Reset(); + + OsuSpriteText text; + OsuTabControl filter; + Add(filter = new OsuTabControl + { + Margin = new MarginPadding(4), + Size = new Vector2(229, 24), + AutoSort = true + }); + Add(text = new OsuSpriteText + { + Text = "None", + Margin = new MarginPadding(4), + Position = new Vector2(275, 5) + }); + + filter.PinItem(GroupMode.All); + filter.PinItem(GroupMode.RecentlyPlayed); + + filter.ItemChanged += (sender, mode) => + { + text.Text = "Currently Selected: " + mode.ToString(); + }; + } + } +} diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index 81ee7185bb..68aed38b34 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -193,6 +193,7 @@ + diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs index 036f12167a..5df2e26bc3 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSlider.cs @@ -28,6 +28,10 @@ namespace osu.Game.Modes.Osu.Objects.Drawables public DrawableSlider(Slider s) : base(s) { + // Since the DrawableSlider itself is just a container without a size we need to + // pass all input through. + AlwaysReceiveInput = true; + SliderBouncer bouncer1; slider = s; @@ -91,10 +95,6 @@ namespace osu.Game.Modes.Osu.Objects.Drawables } } - // Since the DrawableSlider itself is just a container without a size we need to - // pass all input through. - public override bool Contains(Vector2 screenSpacePos) => true; - private int currentRepeat; protected override void Update() diff --git a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs index be3048a9ba..97df378f86 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/DrawableSpinner.cs @@ -24,6 +24,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables public DrawableSpinner(Spinner s) : base(s) { + AlwaysReceiveInput = true; + Origin = Anchor.Centre; Position = s.Position; @@ -69,8 +71,6 @@ namespace osu.Game.Modes.Osu.Objects.Drawables disc.Scale = scaleToCircle; } - public override bool Contains(Vector2 screenSpacePos) => true; - protected override void CheckJudgement(bool userTriggered) { if (Time.Current < HitObject.StartTime) return; diff --git a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs index 34dec34cb4..906a3cd7c3 100644 --- a/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs +++ b/osu.Game.Modes.Osu/Objects/Drawables/Pieces/SpinnerDisc.cs @@ -19,8 +19,6 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces { public class SpinnerDisc : CircularContainer { - public override bool Contains(Vector2 screenSpacePos) => true; - protected Sprite Disc; public SRGBColour DiscColour @@ -101,6 +99,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces public SpinnerDisc() { + AlwaysReceiveInput = true; + RelativeSizeAxes = Axes.Both; Children = new Drawable[] diff --git a/osu.Game.Modes.Osu/Objects/OsuHitObject.cs b/osu.Game.Modes.Osu/Objects/OsuHitObject.cs index a3f2222fbb..cee55a281c 100644 --- a/osu.Game.Modes.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Modes.Osu/Objects/OsuHitObject.cs @@ -69,7 +69,7 @@ namespace osu.Game.Modes.Osu.Objects public virtual void SetDefaultsFromBeatmap(Beatmap beatmap) { - Scale = (1.0f - 0.7f * (beatmap.BeatmapInfo.BaseDifficulty.CircleSize - 5) / 5) / 2; + Scale = (1.0f - 0.7f * (beatmap.BeatmapInfo.Difficulty.CircleSize - 5) / 5) / 2; } } } diff --git a/osu.Game.Modes.Osu/Objects/Slider.cs b/osu.Game.Modes.Osu/Objects/Slider.cs index 83a5a458c7..fdf3658d44 100644 --- a/osu.Game.Modes.Osu/Objects/Slider.cs +++ b/osu.Game.Modes.Osu/Objects/Slider.cs @@ -51,10 +51,10 @@ namespace osu.Game.Modes.Osu.Objects { base.SetDefaultsFromBeatmap(beatmap); - var baseDifficulty = beatmap.BeatmapInfo.BaseDifficulty; + var baseDifficulty = beatmap.BeatmapInfo.Difficulty; ControlPoint overridePoint; - ControlPoint timingPoint = beatmap.TimingPointAt(StartTime, out overridePoint); + ControlPoint timingPoint = beatmap.TimingInfo.TimingPointAt(StartTime, out overridePoint); var velocityAdjustment = overridePoint?.VelocityAdjustment ?? 1; var baseVelocity = 100 * baseDifficulty.SliderMultiplier / velocityAdjustment; diff --git a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs index 61638ef25e..356fa5a6c1 100644 --- a/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/OsuLegacyDecoderTest.cs @@ -94,7 +94,7 @@ namespace osu.Game.Tests.Beatmaps.Formats using (var stream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu")) { var beatmap = decoder.Decode(new StreamReader(stream)); - var difficulty = beatmap.BeatmapInfo.BaseDifficulty; + var difficulty = beatmap.BeatmapInfo.Difficulty; Assert.AreEqual(6.5f, difficulty.DrainRate); Assert.AreEqual(4, difficulty.CircleSize); Assert.AreEqual(8, difficulty.OverallDifficulty); diff --git a/osu.Game/Beatmaps/Beatmap.cs b/osu.Game/Beatmaps/Beatmap.cs index 6dbd605359..fc8bb751f9 100644 --- a/osu.Game/Beatmaps/Beatmap.cs +++ b/osu.Game/Beatmaps/Beatmap.cs @@ -7,7 +7,6 @@ using osu.Game.Database; using osu.Game.Modes; using osu.Game.Modes.Objects; using System.Collections.Generic; -using System.Linq; namespace osu.Game.Beatmaps { @@ -18,7 +17,7 @@ namespace osu.Game.Beatmaps where T : HitObject { public BeatmapInfo BeatmapInfo; - public List ControlPoints; + public TimingInfo TimingInfo = new TimingInfo(); public readonly List ComboColors = new List { new Color4(17, 136, 170, 255), @@ -41,49 +40,23 @@ namespace osu.Game.Beatmaps public Beatmap(Beatmap original = null) { BeatmapInfo = original?.BeatmapInfo ?? BeatmapInfo; - ControlPoints = original?.ControlPoints ?? ControlPoints; + TimingInfo = original?.TimingInfo ?? TimingInfo; ComboColors = original?.ComboColors ?? ComboColors; } - public double BPMMaximum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderBy(c => c.BeatLength).FirstOrDefault() ?? ControlPoint.Default).BeatLength; - public double BPMMinimum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? ControlPoint.Default).BeatLength; - public double BPMMode => BPMAt(ControlPoints.Where(c => c.BeatLength != 0).GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).First().First().Time); - - public double BPMAt(double time) + /// + /// Finds the slider velocity at a time. + /// + /// The time to find the slider velocity at. + /// The slider velocity in positional length units. + public double SliderVelocityAt(double time) { - return 60000 / BeatLengthAt(time); - } + double scoringDistance = 100 * BeatmapInfo.Difficulty.SliderMultiplier; + double beatDistance = TimingInfo.BeatDistanceAt(time); - public double BeatLengthAt(double time) - { - ControlPoint overridePoint; - ControlPoint timingPoint = TimingPointAt(time, out overridePoint); - return timingPoint.BeatLength; - } - - public ControlPoint TimingPointAt(double time, out ControlPoint overridePoint) - { - overridePoint = null; - - ControlPoint timingPoint = null; - foreach (var controlPoint in ControlPoints) - { - // Some beatmaps have the first timingPoint (accidentally) start after the first HitObject(s). - // This null check makes it so that the first ControlPoint that makes a timing change is used as - // the timingPoint for those HitObject(s). - if (controlPoint.Time <= time || timingPoint == null) - { - if (controlPoint.TimingChange) - { - timingPoint = controlPoint; - overridePoint = null; - } - else overridePoint = controlPoint; - } - else break; - } - - return timingPoint ?? ControlPoint.Default; + if (beatDistance > 0) + return scoringDistance / beatDistance * 1000; + return scoringDistance; } } diff --git a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs index ba608fb08d..425c6cc5dc 100644 --- a/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/BeatmapDecoder.cs @@ -5,7 +5,6 @@ using System; using System.Collections.Generic; using System.IO; using osu.Game.Modes.Objects; -using osu.Game.Beatmaps.Timing; using osu.Game.Database; namespace osu.Game.Beatmaps.Formats @@ -43,11 +42,10 @@ namespace osu.Game.Beatmaps.Formats var beatmap = new Beatmap { HitObjects = new List(), - ControlPoints = new List(), BeatmapInfo = new BeatmapInfo { Metadata = new BeatmapMetadata(), - BaseDifficulty = new BaseDifficulty(), + Difficulty = new BeatmapDifficulty(), }, }; ParseFile(stream, beatmap); diff --git a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs index 71d26b2c51..e7ede36b4b 100644 --- a/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/OsuLegacyDecoder.cs @@ -144,7 +144,7 @@ namespace osu.Game.Beatmaps.Formats private void handleDifficulty(Beatmap beatmap, string key, string val) { - var difficulty = beatmap.BeatmapInfo.BaseDifficulty; + var difficulty = beatmap.BeatmapInfo.Difficulty; switch (key) { case @"HPDrainRate": @@ -209,7 +209,7 @@ namespace osu.Game.Beatmaps.Formats } if (cp != null) - beatmap.ControlPoints.Add(cp); + beatmap.TimingInfo.ControlPoints.Add(cp); } private void handleColours(Beatmap beatmap, string key, string val, ref bool hasCustomColours) diff --git a/osu.Game/Beatmaps/Timing/TimingInfo.cs b/osu.Game/Beatmaps/Timing/TimingInfo.cs new file mode 100644 index 0000000000..f245a6b1aa --- /dev/null +++ b/osu.Game/Beatmaps/Timing/TimingInfo.cs @@ -0,0 +1,106 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Beatmaps.Timing +{ + public class TimingInfo + { + public readonly List ControlPoints = new List(); + + public double BPMMaximum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderBy(c => c.BeatLength).FirstOrDefault() ?? ControlPoint.Default).BeatLength; + public double BPMMinimum => 60000 / (ControlPoints?.Where(c => c.BeatLength != 0).OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? ControlPoint.Default).BeatLength; + public double BPMMode => BPMAt(ControlPoints.Where(c => c.BeatLength != 0).GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).First().First().Time); + + public double BPMAt(double time) + { + return 60000 / BeatLengthAt(time); + } + + /// + /// Finds the BPM multiplier at a time. + /// + /// The time to find the BPM multiplier at. + /// The BPM multiplier. + public double BPMMultiplierAt(double time) + { + ControlPoint overridePoint; + ControlPoint timingPoint = TimingPointAt(time, out overridePoint); + + return overridePoint?.VelocityAdjustment ?? timingPoint?.VelocityAdjustment ?? 1; + } + + /// + /// Finds the beat length at a time. + /// + /// The time to find the beat length at. + /// The beat length in milliseconds. + public double BeatLengthAt(double time) + { + ControlPoint overridePoint; + ControlPoint timingPoint = TimingPointAt(time, out overridePoint); + + return timingPoint.BeatLength; + } + + /// + /// Finds the beat velocity at a time. + /// + /// The time to find the velocity at. + /// The velocity. + public double BeatVelocityAt(double time) + { + ControlPoint overridePoint; + ControlPoint timingPoint = TimingPointAt(time, out overridePoint); + + return overridePoint?.VelocityAdjustment ?? timingPoint?.VelocityAdjustment ?? 1; + } + + /// + /// Finds the beat length at a time. + /// + /// The time to find the beat length at. + /// The beat length in positional length units. + public double BeatDistanceAt(double time) + { + ControlPoint overridePoint; + ControlPoint timingPoint = TimingPointAt(time, out overridePoint); + + return (timingPoint?.BeatLength ?? 1) * (overridePoint?.VelocityAdjustment ?? timingPoint?.VelocityAdjustment ?? 1); + } + + /// + /// Finds the timing point at a time. + /// + /// The time to find the timing point at. + /// The timing point containing the velocity change of the returned timing point. + /// The timing point. + public ControlPoint TimingPointAt(double time, out ControlPoint overridePoint) + { + overridePoint = null; + + ControlPoint timingPoint = null; + foreach (var controlPoint in ControlPoints) + { + // Some beatmaps have the first timingPoint (accidentally) start after the first HitObject(s). + // This null check makes it so that the first ControlPoint that makes a timing change is used as + // the timingPoint for those HitObject(s). + if (controlPoint.Time <= time || timingPoint == null) + { + if (controlPoint.TimingChange) + { + timingPoint = controlPoint; + overridePoint = null; + } + else + overridePoint = controlPoint; + } + else break; + } + + return timingPoint ?? ControlPoint.Default; + } + } +} \ No newline at end of file diff --git a/osu.Game/Database/BaseDifficulty.cs b/osu.Game/Database/BaseDifficulty.cs index 83f6e2fdd5..3db8b29644 100644 --- a/osu.Game/Database/BaseDifficulty.cs +++ b/osu.Game/Database/BaseDifficulty.cs @@ -5,7 +5,7 @@ using SQLite.Net.Attributes; namespace osu.Game.Database { - public class BaseDifficulty + public class BeatmapDifficulty { [PrimaryKey, AutoIncrement] public int ID { get; set; } @@ -15,6 +15,23 @@ namespace osu.Game.Database public float ApproachRate { get; set; } public float SliderMultiplier { get; set; } public float SliderTickRate { get; set; } + + /// + /// Maps a difficulty value [0, 10] to a two-piece linear range of values. + /// + /// The difficulty value to be mapped. + /// Minimum of the resulting range which will be achieved by a difficulty value of 0. + /// Midpoint of the resulting range which will be achieved by a difficulty value of 5. + /// Maximum of the resulting range which will be achieved by a difficulty value of 10. + /// Value to which the difficulty value maps in the specified range. + public static double DifficultyRange(double difficulty, double min, double mid, double max) + { + if (difficulty > 5) + return mid + (max - mid) * (difficulty - 5) / 5; + if (difficulty < 5) + return mid - (mid - min) * (5 - difficulty) / 5; + return mid; + } } } diff --git a/osu.Game/Database/BeatmapDatabase.cs b/osu.Game/Database/BeatmapDatabase.cs index 871251b882..b3d48c152f 100644 --- a/osu.Game/Database/BeatmapDatabase.cs +++ b/osu.Game/Database/BeatmapDatabase.cs @@ -64,7 +64,7 @@ namespace osu.Game.Database foreach (var i in b.Beatmaps) { if (i.Metadata != null) connection.Delete(i.Metadata); - if (i.BaseDifficulty != null) connection.Delete(i.BaseDifficulty); + if (i.Difficulty != null) connection.Delete(i.Difficulty); connection.Delete(i); } @@ -90,7 +90,7 @@ namespace osu.Game.Database try { conn.CreateTable(); - conn.CreateTable(); + conn.CreateTable(); conn.CreateTable(); conn.CreateTable(); } @@ -112,7 +112,7 @@ namespace osu.Game.Database } connection.DeleteAll(); - connection.DeleteAll(); + connection.DeleteAll(); connection.DeleteAll(); connection.DeleteAll(); } @@ -329,7 +329,7 @@ namespace osu.Game.Database typeof(BeatmapSetInfo), typeof(BeatmapInfo), typeof(BeatmapMetadata), - typeof(BaseDifficulty), + typeof(BeatmapDifficulty), }; public void Update(T record, bool cascade = true) where T : class diff --git a/osu.Game/Database/BeatmapInfo.cs b/osu.Game/Database/BeatmapInfo.cs index 017f34726b..5f9c0baee8 100644 --- a/osu.Game/Database/BeatmapInfo.cs +++ b/osu.Game/Database/BeatmapInfo.cs @@ -31,11 +31,11 @@ namespace osu.Game.Database [OneToOne(CascadeOperations = CascadeOperation.All)] public BeatmapMetadata Metadata { get; set; } - [ForeignKey(typeof(BaseDifficulty)), NotNull] + [ForeignKey(typeof(BeatmapDifficulty)), NotNull] public int BaseDifficultyID { get; set; } [OneToOne(CascadeOperations = CascadeOperation.All)] - public BaseDifficulty BaseDifficulty { get; set; } + public BeatmapDifficulty Difficulty { get; set; } public string Path { get; set; } @@ -80,7 +80,7 @@ namespace osu.Game.Database { get { - return starDifficulty < 0 ? (BaseDifficulty?.OverallDifficulty ?? 5) : starDifficulty; + return starDifficulty < 0 ? (Difficulty?.OverallDifficulty ?? 5) : starDifficulty; } set { starDifficulty = value; } diff --git a/osu.Game/Database/BeatmapSetInfo.cs b/osu.Game/Database/BeatmapSetInfo.cs index 3e05451bed..12247c3997 100644 --- a/osu.Game/Database/BeatmapSetInfo.cs +++ b/osu.Game/Database/BeatmapSetInfo.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using System.Linq; using SQLite.Net.Attributes; using SQLiteNetExtensions.Attributes; @@ -23,6 +24,8 @@ namespace osu.Game.Database [OneToMany(CascadeOperations = CascadeOperation.All)] public List Beatmaps { get; set; } + public float MaxStarDifficulty => Beatmaps.Max(b => b.StarDifficulty); + public bool DeletePending { get; set; } public string Hash { get; set; } diff --git a/osu.Game/Graphics/Containers/ParallaxContainer.cs b/osu.Game/Graphics/Containers/ParallaxContainer.cs index 88627dbd30..c4cd1777e1 100644 --- a/osu.Game/Graphics/Containers/ParallaxContainer.cs +++ b/osu.Game/Graphics/Containers/ParallaxContainer.cs @@ -18,10 +18,10 @@ namespace osu.Game.Graphics.Containers private Bindable parallaxEnabled; - public override bool Contains(Vector2 screenSpacePos) => true; - public ParallaxContainer() { + AlwaysReceiveInput = true; + RelativeSizeAxes = Axes.Both; AddInternal(content = new Container { diff --git a/osu.Game/Graphics/Cursor/CursorTrail.cs b/osu.Game/Graphics/Cursor/CursorTrail.cs index 09d1193661..ffd96d9622 100644 --- a/osu.Game/Graphics/Cursor/CursorTrail.cs +++ b/osu.Game/Graphics/Cursor/CursorTrail.cs @@ -18,7 +18,6 @@ namespace osu.Game.Graphics.Cursor { internal class CursorTrail : Drawable { - public override bool Contains(Vector2 screenSpacePos) => true; public override bool HandleInput => true; private int currentIndex; @@ -59,6 +58,7 @@ namespace osu.Game.Graphics.Cursor public CursorTrail() { + AlwaysReceiveInput = true; RelativeSizeAxes = Axes.Both; for (int i = 0; i < max_sprites; i++) diff --git a/osu.Game/Graphics/Processing/RatioAdjust.cs b/osu.Game/Graphics/Processing/RatioAdjust.cs index 219d75c675..dd039d5144 100644 --- a/osu.Game/Graphics/Processing/RatioAdjust.cs +++ b/osu.Game/Graphics/Processing/RatioAdjust.cs @@ -10,10 +10,9 @@ namespace osu.Game.Graphics.Processing { internal class RatioAdjust : Container { - public override bool Contains(Vector2 screenSpacePos) => true; - public RatioAdjust() { + AlwaysReceiveInput = true; RelativeSizeAxes = Axes.Both; } diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 590c5bf393..49b5f1e509 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -88,7 +88,7 @@ namespace osu.Game.Graphics.UserInterface private bool didClick; // Used for making sure that the OnMouseDown animation can call instead of OnHoverLost's when clicking - public override bool Contains(Vector2 screenSpacePos) => backgroundContainer.Contains(screenSpacePos); + protected override bool InternalContains(Vector2 screenSpacePos) => backgroundContainer.Contains(screenSpacePos); protected override bool OnClick(Framework.Input.InputState state) { diff --git a/osu.Game/Graphics/UserInterface/OsuDropDownHeader.cs b/osu.Game/Graphics/UserInterface/OsuDropDownHeader.cs index 176c066af5..00dc510338 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropDownHeader.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropDownHeader.cs @@ -21,6 +21,17 @@ namespace osu.Game.Graphics.UserInterface set { label.Text = value; } } + private Color4? accentColour; + public virtual Color4 AccentColour + { + get { return accentColour.GetValueOrDefault(); } + set + { + accentColour = value; + BackgroundColourHover = value; + } + } + public OsuDropDownHeader() { Foreground.Padding = new MarginPadding(4); @@ -30,7 +41,7 @@ namespace osu.Game.Graphics.UserInterface CornerRadius = 4; Height = 40; - Children = new[] + Foreground.Children = new Drawable[] { label = new OsuSpriteText { @@ -51,7 +62,7 @@ namespace osu.Game.Graphics.UserInterface private void load(OsuColour colours) { BackgroundColour = Color4.Black.Opacity(0.5f); - BackgroundColourHover = colours.PinkDarker; + BackgroundColourHover = accentColour ?? colours.PinkDarker; } } } \ No newline at end of file diff --git a/osu.Game/Graphics/UserInterface/OsuDropDownMenu.cs b/osu.Game/Graphics/UserInterface/OsuDropDownMenu.cs index 786636ce1a..5d9e30db8d 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropDownMenu.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropDownMenu.cs @@ -1,39 +1,61 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Graphics.Transforms; -using osu.Framework.Graphics.UserInterface; +using System.Linq; +using osu.Framework.Allocation; using OpenTK; using OpenTK.Graphics; using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Transforms; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterface { - public class OsuDropDownMenu : DropDownMenu + public class OsuDropDownMenu : DropDownMenu { - protected override DropDownHeader CreateHeader() => new OsuDropDownHeader(); + protected override DropDownHeader CreateHeader() => new OsuDropDownHeader { AccentColour = AccentColour }; + + private Color4? accentColour; + public virtual Color4 AccentColour + { + get { return accentColour.GetValueOrDefault(); } + set + { + accentColour = value; + if (Header != null) + ((OsuDropDownHeader)Header).AccentColour = value; + foreach (var item in ItemList.OfType>()) + item.AccentColour = value; + } + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + if (accentColour == null) + AccentColour = colours.PinkDarker; + } public OsuDropDownMenu() { ContentContainer.CornerRadius = 4; ContentBackground.Colour = Color4.Black.Opacity(0.5f); + + DropDownItemsContainer.Padding = new MarginPadding(5); } - protected override void AnimateOpen() - { - ContentContainer.FadeIn(300, EasingTypes.OutQuint); - } + protected override void AnimateOpen() => ContentContainer.FadeIn(300, EasingTypes.OutQuint); - protected override void AnimateClose() - { - ContentContainer.FadeOut(300, EasingTypes.OutQuint); - } + protected override void AnimateClose() => ContentContainer.FadeOut(300, EasingTypes.OutQuint); protected override void UpdateContentHeight() { - ContentContainer.ResizeTo(State == DropDownMenuState.Opened ? new Vector2(1, ContentHeight) : new Vector2(1, 0), 300, EasingTypes.OutQuint); + var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight; + ContentContainer.ResizeTo(new Vector2(1, State == DropDownMenuState.Opened ? actualHeight : 0), 300, EasingTypes.OutQuint); } - protected override DropDownMenuItem CreateDropDownItem(string key, U value) => new OsuDropDownMenuItem(key, value); + protected override DropDownMenuItem CreateDropDownItem(string key, T value) => new OsuDropDownMenuItem(key, value) { AccentColour = AccentColour }; } } \ No newline at end of file diff --git a/osu.Game/Graphics/UserInterface/OsuDropDownMenuItem.cs b/osu.Game/Graphics/UserInterface/OsuDropDownMenuItem.cs index b06422c302..71650dee52 100644 --- a/osu.Game/Graphics/UserInterface/OsuDropDownMenuItem.cs +++ b/osu.Game/Graphics/UserInterface/OsuDropDownMenuItem.cs @@ -18,6 +18,9 @@ namespace osu.Game.Graphics.UserInterface { Foreground.Padding = new MarginPadding(2); + Masking = true; + CornerRadius = 6; + Children = new[] { new FillFlowContainer @@ -27,12 +30,15 @@ namespace osu.Game.Graphics.UserInterface AutoSizeAxes = Axes.Y, Children = new Drawable[] { - new TextAwesome + chevron = new TextAwesome { + AlwaysPresent = true, Icon = FontAwesome.fa_chevron_right, + UseFullGlyphHeight = false, Colour = Color4.Black, - TextSize = 12, - Margin = new MarginPadding { Right = 3 }, + Alpha = 0.5f, + TextSize = 8, + Margin = new MarginPadding { Left = 3, Right = 3 }, Origin = Anchor.CentreLeft, Anchor = Anchor.CentreLeft, }, @@ -46,11 +52,33 @@ namespace osu.Game.Graphics.UserInterface }; } + private Color4? accentColour; + + private TextAwesome chevron; + + protected override void FormatForeground(bool hover = false) + { + base.FormatForeground(hover); + chevron.Alpha = hover ? 1 : 0; + } + + public Color4 AccentColour + { + get { return accentColour.GetValueOrDefault(); } + set + { + accentColour = value; + BackgroundColourHover = BackgroundColourSelected = value; + FormatBackground(); + FormatForeground(); + } + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { - BackgroundColour = Color4.Black.Opacity(0.5f); - BackgroundColourHover = colours.PinkDarker; + BackgroundColour = Color4.Transparent; + BackgroundColourHover = accentColour ?? colours.PinkDarker; BackgroundColourSelected = Color4.Black.Opacity(0.5f); } } diff --git a/osu.Game/Graphics/UserInterface/OsuTabControl.cs b/osu.Game/Graphics/UserInterface/OsuTabControl.cs new file mode 100644 index 0000000000..d5699eddaf --- /dev/null +++ b/osu.Game/Graphics/UserInterface/OsuTabControl.cs @@ -0,0 +1,139 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Linq; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuTabControl : TabControl + { + protected override DropDownMenu CreateDropDownMenu() => new OsuTabDropDownMenu(); + + protected override TabItem CreateTabItem(T value) => new OsuTabItem { Value = value }; + + protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || DropDown.Contains(screenSpacePos); + + public OsuTabControl() + { + if (!typeof(T).IsEnum) + throw new InvalidOperationException("OsuTabControl only supports enums as the generic type argument"); + + foreach (var val in (T[])Enum.GetValues(typeof(T))) + AddItem(val); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + if (accentColour == null) + AccentColour = colours.Blue; + } + + private Color4? accentColour; + public Color4 AccentColour + { + get { return accentColour.GetValueOrDefault(); } + set + { + accentColour = value; + var dropDown = DropDown as OsuTabDropDownMenu; + if (dropDown != null) + dropDown.AccentColour = value; + foreach (var item in TabContainer.Children.OfType>()) + item.AccentColour = value; + } + } + + public class OsuTabDropDownMenu : OsuDropDownMenu + { + protected override DropDownHeader CreateHeader() => new OsuTabDropDownHeader + { + AccentColour = AccentColour, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }; + + protected override DropDownMenuItem CreateDropDownItem(string key, T1 value) + { + var item = base.CreateDropDownItem(key, value); + item.ForegroundColourHover = Color4.Black; + return item; + } + + public OsuTabDropDownMenu() + { + ContentContainer.Anchor = Anchor.TopRight; + ContentContainer.Origin = Anchor.TopRight; + + RelativeSizeAxes = Axes.X; + + ContentBackground.Colour = Color4.Black.Opacity(0.7f); + MaxDropDownHeight = 400; + } + + public class OsuTabDropDownHeader : OsuDropDownHeader + { + public override Color4 AccentColour + { + get { return base.AccentColour; } + set + { + base.AccentColour = value; + Foreground.Colour = value; + } + } + + protected override bool OnHover(InputState state) + { + Foreground.Colour = BackgroundColour; + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + Foreground.Colour = BackgroundColourHover; + base.OnHoverLost(state); + } + + public OsuTabDropDownHeader() + { + RelativeSizeAxes = Axes.None; + AutoSizeAxes = Axes.X; + + BackgroundColour = Color4.Black.Opacity(0.5f); + + Background.Height = 0.5f; + Background.CornerRadius = 5; + Background.Masking = true; + + Foreground.RelativeSizeAxes = Axes.None; + Foreground.AutoSizeAxes = Axes.X; + Foreground.RelativeSizeAxes = Axes.Y; + Foreground.Margin = new MarginPadding(5); + + Foreground.Children = new Drawable[] + { + new TextAwesome + { + Icon = FontAwesome.fa_ellipsis_h, + TextSize = 14, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + } + }; + + Padding = new MarginPadding { Left = 5, Right = 5 }; + } + } + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuTabItem.cs b/osu.Game/Graphics/UserInterface/OsuTabItem.cs new file mode 100644 index 0000000000..acfca1b67b --- /dev/null +++ b/osu.Game/Graphics/UserInterface/OsuTabItem.cs @@ -0,0 +1,121 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transforms; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Graphics.UserInterface +{ + public class OsuTabItem : TabItem + { + private SpriteText text; + private Box box; + + private Color4? accentColour; + public Color4 AccentColour + { + get { return accentColour.GetValueOrDefault(); } + set + { + accentColour = value; + if (!Active) + text.Colour = value; + } + } + + public new T Value + { + get { return base.Value; } + set + { + base.Value = value; + text.Text = (value as Enum)?.GetDescription(); + } + } + + public override bool Active + { + get { return base.Active; } + set + { + if (Active == value) return; + + if (value) + fadeActive(); + else + fadeInactive(); + base.Active = value; + } + } + + private const float transition_length = 500; + + private void fadeActive() + { + box.FadeIn(transition_length, EasingTypes.OutQuint); + text.FadeColour(Color4.White, transition_length, EasingTypes.OutQuint); + } + + private void fadeInactive() + { + box.FadeOut(transition_length, EasingTypes.OutQuint); + text.FadeColour(AccentColour, transition_length, EasingTypes.OutQuint); + } + + protected override bool OnHover(InputState state) + { + if (!Active) + fadeActive(); + return true; + } + + protected override void OnHoverLost(InputState state) + { + if (!Active) + fadeInactive(); + } + + public OsuTabItem() + { + AutoSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + + Children = new Drawable[] + { + text = new OsuSpriteText + { + Margin = new MarginPadding(5), + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + TextSize = 14, + Font = @"Exo2.0-Bold", // Font should only turn bold when active? + }, + box = new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + Alpha = 0, + Colour = Color4.White, + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + if (accentColour == null) + AccentColour = colours.Blue; + } + } +} diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs index 8221ef952f..0d4e72f92c 100644 --- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs +++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs @@ -158,7 +158,7 @@ namespace osu.Game.Graphics.UserInterface } } - public override bool Contains(Vector2 screenSpacePos) => IconLayer.Contains(screenSpacePos) || TextLayer.Contains(screenSpacePos); + protected override bool InternalContains(Vector2 screenSpacePos) => IconLayer.Contains(screenSpacePos) || TextLayer.Contains(screenSpacePos); protected override bool OnHover(InputState state) { diff --git a/osu.Game/Modes/UI/Playfield.cs b/osu.Game/Modes/UI/Playfield.cs index 288b4bf352..bff461f649 100644 --- a/osu.Game/Modes/UI/Playfield.cs +++ b/osu.Game/Modes/UI/Playfield.cs @@ -18,7 +18,6 @@ namespace osu.Game.Modes.UI /// The HitObjects contained in this Playfield. /// public HitObjectContainer> HitObjects; - public override bool Contains(Vector2 screenSpacePos) => true; internal Container ScaledContent; @@ -31,6 +30,8 @@ namespace osu.Game.Modes.UI /// Whether we want our internal coordinate system to be scaled to a specified width. protected Playfield(float? customWidth = null) { + AlwaysReceiveInput = true; + AddInternal(ScaledContent = new ScaledContainer { CustomWidth = customWidth, @@ -77,12 +78,18 @@ namespace osu.Game.Modes.UI //dividing by the customwidth will effectively scale our content to the required container size. protected override Vector2 DrawScale => CustomWidth.HasValue ? new Vector2(DrawSize.X / CustomWidth.Value) : base.DrawScale; - public override bool Contains(Vector2 screenSpacePos) => true; + public ScaledContainer() + { + AlwaysReceiveInput = true; + } } public class HitObjectContainer : Container where U : Drawable { - public override bool Contains(Vector2 screenSpacePos) => true; + public HitObjectContainer() + { + AlwaysReceiveInput = true; + } } } } diff --git a/osu.Game/Overlays/Toolbar/Toolbar.cs b/osu.Game/Overlays/Toolbar/Toolbar.cs index 79698a4f3e..657334d1bb 100644 --- a/osu.Game/Overlays/Toolbar/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar/Toolbar.cs @@ -35,10 +35,10 @@ namespace osu.Game.Overlays.Toolbar private const float alpha_hovering = 0.8f; private const float alpha_normal = 0.6f; - public override bool Contains(Vector2 screenSpacePos) => true; - public Toolbar() { + AlwaysReceiveInput = true; + Children = new Drawable[] { new ToolbarBackground(), @@ -63,8 +63,9 @@ namespace osu.Game.Overlays.Toolbar } } }, - new PassThroughFlowContainer + new FillFlowContainer { + AlwaysReceiveInput = true, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, Direction = FillDirection.Horizontal, @@ -144,11 +145,5 @@ namespace osu.Game.Overlays.Toolbar MoveToY(-DrawSize.Y, transition_time, EasingTypes.OutQuint); FadeOut(transition_time); } - - private class PassThroughFlowContainer : FillFlowContainer - { - //needed to get input to the login overlay. - public override bool Contains(Vector2 screenSpacePos) => true; - } } } diff --git a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs b/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs index 9de0f290a5..7f28764b17 100644 --- a/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs +++ b/osu.Game/Overlays/Toolbar/ToolbarUserArea.cs @@ -15,10 +15,10 @@ namespace osu.Game.Overlays.Toolbar public override RectangleF BoundingBox => button.BoundingBox; - public override bool Contains(Vector2 screenSpacePos) => true; - public ToolbarUserArea() { + AlwaysReceiveInput = true; + RelativeSizeAxes = Axes.Y; AutoSizeAxes = Axes.X; diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 7af03d2f11..52872c858b 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -35,10 +35,7 @@ namespace osu.Game.Screens.Menu private Key triggerKey; private SampleChannel sampleClick; - public override bool Contains(Vector2 screenSpacePos) - { - return box.Contains(screenSpacePos); - } + protected override bool InternalContains(Vector2 screenSpacePos) => box.Contains(screenSpacePos); public Button(string text, string internalName, FontAwesome symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown) { diff --git a/osu.Game/Screens/Menu/OsuLogo.cs b/osu.Game/Screens/Menu/OsuLogo.cs index 01622fe096..259e98c483 100644 --- a/osu.Game/Screens/Menu/OsuLogo.cs +++ b/osu.Game/Screens/Menu/OsuLogo.cs @@ -50,10 +50,7 @@ namespace osu.Game.Screens.Menu } } - public override bool Contains(Vector2 screenSpacePos) - { - return logoContainer.Contains(screenSpacePos); - } + protected override bool InternalContains(Vector2 screenSpacePos) => logoContainer.Contains(screenSpacePos); public bool Ripple { diff --git a/osu.Game/Screens/Play/KeyCounterCollection.cs b/osu.Game/Screens/Play/KeyCounterCollection.cs index aa2bf47227..bb6ab34cbb 100644 --- a/osu.Game/Screens/Play/KeyCounterCollection.cs +++ b/osu.Game/Screens/Play/KeyCounterCollection.cs @@ -4,7 +4,6 @@ using System.Linq; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using OpenTK; using OpenTK.Graphics; using osu.Framework.Input; @@ -14,6 +13,8 @@ namespace osu.Game.Screens.Play { public KeyCounterCollection() { + AlwaysReceiveInput = true; + Direction = FillDirection.Horizontal; AutoSizeAxes = Axes.Both; } @@ -33,8 +34,6 @@ namespace osu.Game.Screens.Play counter.ResetCount(); } - public override bool Contains(Vector2 screenSpacePos) => true; - //further: change default values here and in KeyCounter if needed, instead of passing them in every constructor private bool isCounting; public bool IsCounting @@ -111,12 +110,11 @@ namespace osu.Game.Screens.Play public Receptor(KeyCounterCollection target) { + AlwaysReceiveInput = true; RelativeSizeAxes = Axes.Both; this.target = target; } - public override bool Contains(Vector2 screenSpacePos) => true; - public override bool HandleInput => true; protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => target.Children.Any(c => c.TriggerKeyDown(state, args)); diff --git a/osu.Game/Screens/Play/KeyCounterMouse.cs b/osu.Game/Screens/Play/KeyCounterMouse.cs index 425bcffee5..744cb905d9 100644 --- a/osu.Game/Screens/Play/KeyCounterMouse.cs +++ b/osu.Game/Screens/Play/KeyCounterMouse.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Input; -using OpenTK; using OpenTK.Input; namespace osu.Game.Screens.Play @@ -13,6 +12,7 @@ namespace osu.Game.Screens.Play public KeyCounterMouse(MouseButton button) : base(getStringRepresentation(button)) { + AlwaysReceiveInput = true; Button = button; } @@ -29,8 +29,6 @@ namespace osu.Game.Screens.Play } } - public override bool Contains(Vector2 screenSpacePos) => true; - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { if (args.Button == Button) IsLit = true; diff --git a/osu.Game/Screens/Play/PauseOverlay.cs b/osu.Game/Screens/Play/PauseOverlay.cs index 0f3303fc29..b862adcd53 100644 --- a/osu.Game/Screens/Play/PauseOverlay.cs +++ b/osu.Game/Screens/Play/PauseOverlay.cs @@ -70,7 +70,6 @@ namespace osu.Game.Screens.Play private FillFlowContainer retryCounterContainer; - public override bool Contains(Vector2 screenSpacePos) => true; public override bool HandleInput => State == Visibility.Visible; protected override void PopIn() => FadeIn(transition_duration, EasingTypes.In); @@ -217,6 +216,7 @@ namespace osu.Game.Screens.Play public PauseOverlay() { + AlwaysReceiveInput = true; RelativeSizeAxes = Axes.Both; } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 261d279600..ec8cbb1438 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -239,18 +239,18 @@ namespace osu.Game.Screens.Play private void onCompletion() { + // Force a final check to see if the player has failed + // Some game modes (e.g. taiko) fail at the end of the map + if (scoreProcessor.CheckFailed()) + { + // If failed, onFail will be invoked which will push a new screen. + // Let's not push the completion screen in this case + return; + } + Delay(1000); Schedule(delegate { - // Force a final check to see if the player has failed - // Some game modes (e.g. taiko) fail at the end of the map - if (scoreProcessor.CheckFailed()) - { - // If failed, onFail will be called which will push a new screen. - // Let's not push the completion screen in this case - return; - } - ValidForResume = false; Push(new Results { diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index f82c35598a..5285d26264 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -217,12 +217,12 @@ namespace osu.Game.Screens.Select private string getBPMRange(Beatmap beatmap) { - double bpmMax = beatmap.BPMMaximum; - double bpmMin = beatmap.BPMMinimum; + double bpmMax = beatmap.TimingInfo.BPMMaximum; + double bpmMin = beatmap.TimingInfo.BPMMinimum; if (Precision.AlmostEquals(bpmMin, bpmMax)) return Math.Round(bpmMin) + "bpm"; - return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.BPMMode) + "bpm)"; + return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.TimingInfo.BPMMode) + "bpm)"; } public class InfoLabel : Container diff --git a/osu.Game/Screens/Select/CarouselContainer.cs b/osu.Game/Screens/Select/CarouselContainer.cs index 6a5bb2dc94..092e4461e0 100644 --- a/osu.Game/Screens/Select/CarouselContainer.cs +++ b/osu.Game/Screens/Select/CarouselContainer.cs @@ -15,6 +15,7 @@ using OpenTK.Input; using System.Collections; using osu.Framework.MathUtils; using System.Diagnostics; +using osu.Game.Screens.Select.Filter; namespace osu.Game.Screens.Select { @@ -157,46 +158,26 @@ namespace osu.Game.Screens.Select ScrollTo(selectedY, animated); } - public void Sort(FilterControl.SortMode mode) + public void Sort(SortMode mode) { List sortedGroups = new List(groups); switch (mode) { - case FilterControl.SortMode.Artist: + case SortMode.Artist: sortedGroups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Artist, y.BeatmapSet.Metadata.Artist, StringComparison.InvariantCultureIgnoreCase)); break; - case FilterControl.SortMode.Title: + case SortMode.Title: sortedGroups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Title, y.BeatmapSet.Metadata.Title, StringComparison.InvariantCultureIgnoreCase)); break; - case FilterControl.SortMode.Author: + case SortMode.Author: sortedGroups.Sort((x, y) => string.Compare(x.BeatmapSet.Metadata.Author, y.BeatmapSet.Metadata.Author, StringComparison.InvariantCultureIgnoreCase)); break; - case FilterControl.SortMode.Difficulty: - sortedGroups.Sort((x, y) => - { - float xAverage = 0, yAverage = 0; - int counter = 0; - foreach (BeatmapInfo set in x.BeatmapSet.Beatmaps) - { - xAverage += set.StarDifficulty; - counter++; - } - xAverage /= counter; - counter = 0; - foreach (BeatmapInfo set in y.BeatmapSet.Beatmaps) - { - yAverage += set.StarDifficulty; - counter++; - } - yAverage /= counter; - if (xAverage > yAverage) - return 1; - else - return -1; - }); + case SortMode.Difficulty: + sortedGroups.Sort((x, y) => x.BeatmapSet.MaxStarDifficulty.CompareTo(y.BeatmapSet.MaxStarDifficulty)); break; default: - throw new NotImplementedException(); + Sort(SortMode.Artist); // Temporary + break; } scrollableContent.Clear(false); diff --git a/osu.Game/Screens/Select/Filter/GroupMode.cs b/osu.Game/Screens/Select/Filter/GroupMode.cs new file mode 100644 index 0000000000..b9e0938fcc --- /dev/null +++ b/osu.Game/Screens/Select/Filter/GroupMode.cs @@ -0,0 +1,41 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Screens.Select.Filter +{ + public enum GroupMode + { + [Description("All")] + All, + [Description("Artist")] + Artist, + [Description("Author")] + Author, + [Description("BPM")] + BPM, + [Description("Collections")] + Collections, + [Description("Date Added")] + DateAdded, + [Description("Difficulty")] + Difficulty, + [Description("Favorites")] + Favorites, + [Description("Length")] + Length, + [Description("My Maps")] + MyMaps, + [Description("No Grouping")] + NoGrouping, + [Description("Rank Achieved")] + RankAchieved, + [Description("Ranked Status")] + RankedStatus, + [Description("Recently Played")] + RecentlyPlayed, + [Description("Title")] + Title + } +} diff --git a/osu.Game/Screens/Select/Filter/SortMode.cs b/osu.Game/Screens/Select/Filter/SortMode.cs new file mode 100644 index 0000000000..4beecb730e --- /dev/null +++ b/osu.Game/Screens/Select/Filter/SortMode.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel; + +namespace osu.Game.Screens.Select.Filter +{ + public enum SortMode + { + [Description("Artist")] + Artist, + [Description("Author")] + Author, + [Description("BPM")] + BPM, + [Description("Date Added")] + DateAdded, + [Description("Difficulty")] + Difficulty, + [Description("Length")] + Length, + [Description("Rank Achieved")] + RankAchieved, + [Description("Title")] + Title + } +} diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index e20c170e3f..2a25928dc7 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -9,9 +9,12 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; +using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Select.Filter; +using Container = osu.Framework.Graphics.Containers.Container; +using osu.Framework.Input; namespace osu.Game.Screens.Select { @@ -20,10 +23,17 @@ namespace osu.Game.Screens.Select public Action FilterChanged; public string Search => searchTextBox.Text; + + private OsuTabControl sortTabs; + + private TabControl groupTabs; + private SortMode sort = SortMode.Title; - public SortMode Sort { + public SortMode Sort + { get { return sort; } - set { + set + { if (sort != value) { sort = value; @@ -32,10 +42,26 @@ namespace osu.Game.Screens.Select } } + private GroupMode group = GroupMode.All; + public GroupMode Group + { + get { return group; } + set + { + if (group != value) + { + group = value; + FilterChanged?.Invoke(); + } + } + } + public Action Exit; private SearchTextBox searchTextBox; + protected override bool InternalContains(Vector2 screenSpacePos) => base.InternalContains(screenSpacePos) || groupTabs.Contains(screenSpacePos) || sortTabs.Contains(screenSpacePos); + public FilterControl() { Children = new Drawable[] @@ -46,18 +72,18 @@ namespace osu.Game.Screens.Select Alpha = 0.8f, RelativeSizeAxes = Axes.Both, }, - new FillFlowContainer + new Container { Padding = new MarginPadding(20), - AutoSizeAxes = Axes.Y, - RelativeSizeAxes = Axes.X, + AlwaysReceiveInput = true, + RelativeSizeAxes = Axes.Both, + Width = 0.5f, Anchor = Anchor.TopRight, Origin = Anchor.TopRight, - Width = 0.4f, // TODO: InnerWidth property or something - Direction = FillDirection.Vertical, Children = new Drawable[] { - searchTextBox = new SearchTextBox { + searchTextBox = new SearchTextBox + { RelativeSizeAxes = Axes.X, OnChange = (sender, newText) => { @@ -66,10 +92,59 @@ namespace osu.Game.Screens.Select }, Exit = () => Exit?.Invoke(), }, - new GroupSortTabs() + new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + Colour = OsuColour.Gray(80), + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + }, + new FillFlowContainer + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Direction = FillDirection.Horizontal, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + AlwaysReceiveInput = true, + Children = new Drawable[] + { + groupTabs = new OsuTabControl + { + RelativeSizeAxes = Axes.X, + Height = 24, + Width = 0.5f, + AutoSort = true + }, + //spriteText = new OsuSpriteText + //{ + // Font = @"Exo2.0-Bold", + // Text = "Sort results by", + // TextSize = 14, + // Margin = new MarginPadding + // { + // Top = 5, + // Bottom = 5 + // }, + //}, + sortTabs = new OsuTabControl() + { + RelativeSizeAxes = Axes.X, + Width = 0.5f, + Height = 24, + AutoSort = true, + } + } + }, } } }; + + groupTabs.PinItem(GroupMode.All); + groupTabs.PinItem(GroupMode.RecentlyPlayed); + groupTabs.ItemChanged += (sender, value) => Group = value; + sortTabs.ItemChanged += (sender, value) => Sort = value; } public void Deactivate() @@ -83,208 +158,18 @@ namespace osu.Game.Screens.Select searchTextBox.HoldFocus = true; } - private class TabItem : ClickableContainer + [BackgroundDependencyLoader] + private void load(OsuColour colours) { - public string Text - { - get { return text.Text; } - set { text.Text = value; } - } - - private void fadeActive() - { - box.FadeIn(300); - text.FadeColour(Color4.White, 300); - } - - private void fadeInactive() - { - box.FadeOut(300); - text.FadeColour(fadeColour, 300); - } - - private bool active; - public bool Active - { - get { return active; } - set - { - active = value; - if (active) - fadeActive(); - else - fadeInactive(); - } - } - - private SpriteText text; - private Box box; - private Color4 fadeColour; - - protected override bool OnHover(InputState state) - { - if (!active) - fadeActive(); - return true; - } - - protected override void OnHoverLost(InputState state) - { - if (!active) - fadeInactive(); - } - - public TabItem() - { - AutoSizeAxes = Axes.Both; - Children = new Drawable[] - { - text = new OsuSpriteText - { - Margin = new MarginPadding(5), - TextSize = 14, - Font = @"Exo2.0-Bold", - }, - box = new Box - { - RelativeSizeAxes = Axes.X, - Height = 1, - Alpha = 0, - Colour = Color4.White, - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - text.Colour = colours.Blue; - fadeColour = colours.Blue; - } + sortTabs.AccentColour = colours.GreenLight; } - private class GroupSortTabs : Container - { - private TextAwesome groupsEllipsis, sortEllipsis; - private SpriteText sortLabel; - - public GroupSortTabs() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.X, - Height = 1, - Colour = OsuColour.Gray(80), - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Children = new Drawable[] - { - new TabItem - { - Text = "All", - Active = true, - }, - new TabItem - { - Text = "Recently Played", - Active = false, - }, - new TabItem - { - Text = "Collections", - Active = false, - }, - groupsEllipsis = new TextAwesome - { - Icon = FontAwesome.fa_ellipsis_h, - Origin = Anchor.TopLeft, - TextSize = 14, - Margin = new MarginPadding { Top = 5, Bottom = 5 }, - } - } - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Children = new Drawable[] - { - sortLabel = new OsuSpriteText - { - Font = @"Exo2.0-Bold", - Text = "Sort results by", - TextSize = 14, - Margin = new MarginPadding { Top = 5, Bottom = 5 }, - }, - new TabItem - { - Text = "Artist", - Active = true, - }, - sortEllipsis = new TextAwesome - { - Icon = FontAwesome.fa_ellipsis_h, - Origin = Anchor.TopLeft, - TextSize = 14, - Margin = new MarginPadding { Top = 5, Bottom = 5 }, - } - } - }, - }; - } + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - groupsEllipsis.Colour = colours.Blue; - sortLabel.Colour = colours.GreenLight; - sortEllipsis.Colour = colours.GreenLight; - } - } - - public enum SortMode - { - Artist, - BPM, - Author, - DateAdded, - Difficulty, - Length, - RankAchieved, - Title - } + protected override bool OnMouseMove(InputState state) => true; - public enum GroupMode - { - NoGrouping, - Artist, - BPM, - Author, - DateAdded, - Difficulty, - Length, - RankAchieved, - Title, - Collections, - Favorites, - MyMaps, - RankedStatus, - RecentlyPlayed - } + protected override bool OnClick(InputState state) => true; + + protected override bool OnDragStart(InputState state) => true; } } \ No newline at end of file diff --git a/osu.Game/Screens/Select/Footer.cs b/osu.Game/Screens/Select/Footer.cs index fae1cb5d4d..0e369c78b9 100644 --- a/osu.Game/Screens/Select/Footer.cs +++ b/osu.Game/Screens/Select/Footer.cs @@ -26,8 +26,6 @@ namespace osu.Game.Screens.Select private const float padding = 80; - public override bool Contains(Vector2 screenSpacePos) => true; - public Action OnBack; public Action OnStart; @@ -69,6 +67,8 @@ namespace osu.Game.Screens.Select public Footer() { + AlwaysReceiveInput = true; + const float bottom_tool_height = 50; RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs index cfe8eea07f..a0bc702aac 100644 --- a/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs +++ b/osu.Game/Screens/Select/Options/BeatmapOptionsButton.cs @@ -83,7 +83,7 @@ namespace osu.Game.Screens.Select.Options return false; } - public override bool Contains(Vector2 screenSpacePos) => box.Contains(screenSpacePos); + protected override bool InternalContains(Vector2 screenSpacePos) => box.Contains(screenSpacePos); public BeatmapOptionsButton() { diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index ca8b353c57..a0f20242c2 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -56,31 +56,16 @@ namespace osu.Game.Screens.Select protected virtual bool ShowFooter => true; /// - /// Can be null if == false + /// Can be null if is false. /// protected readonly BeatmapOptionsOverlay BeatmapOptions; /// - /// Can be null if == false + /// Can be null if is false. /// protected readonly Footer Footer; - private FilterControl filter; - public FilterControl Filter - { - get - { - return filter; - } - private set - { - if (filter != value) - { - filter = value; - filterChanged(); - } - } - } + public readonly FilterControl FilterControl; protected SongSelect() { @@ -109,7 +94,7 @@ namespace osu.Game.Screens.Select Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, }); - Add(filter = new FilterControl + Add(FilterControl = new FilterControl { RelativeSizeAxes = Axes.X, Height = filter_height, @@ -198,9 +183,9 @@ namespace osu.Game.Screens.Select filterTask = Scheduler.AddDelayed(() => { filterTask = null; - var search = filter.Search; + var search = FilterControl.Search; BeatmapGroup newSelection = null; - carousel.Sort(filter.Sort); + carousel.Sort(FilterControl.Sort); foreach (var beatmapGroup in carousel) { var set = beatmapGroup.BeatmapSet; @@ -257,7 +242,7 @@ namespace osu.Game.Screens.Select beatmapInfoWedge.State = Visibility.Visible; - filter.Activate(); + FilterControl.Activate(); } protected override void OnResuming(Screen last) @@ -270,7 +255,7 @@ namespace osu.Game.Screens.Select Content.ScaleTo(1, 250, EasingTypes.OutSine); - filter.Activate(); + FilterControl.Activate(); } protected override void OnSuspending(Screen next) @@ -279,7 +264,7 @@ namespace osu.Game.Screens.Select Content.FadeOut(250); - filter.Deactivate(); + FilterControl.Deactivate(); base.OnSuspending(next); } @@ -289,7 +274,7 @@ namespace osu.Game.Screens.Select Content.FadeOut(100); - filter.Deactivate(); + FilterControl.Deactivate(); return base.OnExiting(next); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ac9dbaf05b..80d5c906e0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -75,6 +75,7 @@ + @@ -196,6 +197,8 @@ + + @@ -347,6 +350,8 @@ + +