From 937dbb7bf690c22fff07cade2d4cadd6c8c9128a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2019 16:06:16 +0900 Subject: [PATCH 01/77] Initial layout for timing screen --- .../Visual/Editor/TestSceneTimingScreen.cs | 21 ++++ osu.Game/Screens/Edit/Timing/TimingScreen.cs | 97 ++++++++++++++++++- 2 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs new file mode 100644 index 0000000000..be84ae2cf9 --- /dev/null +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit.Timing; + +namespace osu.Game.Tests.Visual.Editor +{ + [TestFixture] + public class TestSceneTimingScreen : EditorClockTestScene + { + [BackgroundDependencyLoader] + private void load() + { + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + Child = new TimingScreen(); + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 9ded4207e5..b723ffac9a 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,13 +1,104 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; + namespace osu.Game.Screens.Edit.Timing { - public class TimingScreen : EditorScreen + public class TimingScreen : EditorScreenWithTimeline { - public TimingScreen() + protected override Drawable CreateMainContent() => new GridContainer { - Child = new ScreenWhiteBox.UnderConstructionMessage("Timing mode"); + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 200), + }, + Content = new[] + { + new Drawable[] + { + new ControlPointList(), + new ControlPointSettings(), + }, + } + }; + + public class ControlPointList : CompositeDrawable + { + [Resolved] + protected IBindable Beatmap { get; private set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Gray0, + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new ControlPointRow(), + new ControlPointRow(), + new ControlPointRow(), + new ControlPointRow(), + new ControlPointRow(), + new ControlPointRow(), + } + }, + } + }; + } + + private class ControlPointRow : CompositeDrawable + { + public ControlPointRow() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new OsuSpriteText { Text = "sample row" }, + }; + } + } + } + + public class ControlPointSettings : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new Box + { + Colour = colours.Gray3, + RelativeSizeAxes = Axes.Both, + }; + } } } } From de13320a2debb7c48d46691ea5a34a0b09d722a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Oct 2019 18:46:05 +0900 Subject: [PATCH 02/77] Add initial table display --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 187 ++++++++++++++++--- 1 file changed, 160 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index b723ffac9a..be7058ae08 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,12 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Globalization; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -15,6 +21,9 @@ namespace osu.Game.Screens.Edit.Timing { public class TimingScreen : EditorScreenWithTimeline { + [Cached] + private readonly Bindable controlPoint = new Bindable(); + protected override Drawable CreateMainContent() => new GridContainer { RelativeSizeAxes = Axes.Both, @@ -53,37 +62,13 @@ namespace osu.Game.Screens.Edit.Timing new OsuScrollContainer { RelativeSizeAxes = Axes.Both, - Child = new FillFlowContainer + Child = new ControlPointTable { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new ControlPointRow(), - new ControlPointRow(), - new ControlPointRow(), - new ControlPointRow(), - new ControlPointRow(), - new ControlPointRow(), - } - }, + ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints + } } }; } - - private class ControlPointRow : CompositeDrawable - { - public ControlPointRow() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - InternalChildren = new Drawable[] - { - new OsuSpriteText { Text = "sample row" }, - }; - } - } } public class ControlPointSettings : CompositeDrawable @@ -101,4 +86,152 @@ namespace osu.Game.Screens.Edit.Timing } } } + + public class ControlPointTable : TableContainer + { + private const float horizontal_inset = 20; + private const float row_height = 25; + private const int text_size = 14; + + private readonly FillFlowContainer backgroundFlow; + + [Resolved] + private Bindable controlPoint { get; set; } + + public ControlPointTable() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding { Horizontal = horizontal_inset }; + RowSize = new Dimension(GridSizeMode.Absolute, row_height); + + AddInternal(backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1f, + Padding = new MarginPadding { Horizontal = -horizontal_inset }, + Margin = new MarginPadding { Top = row_height } + }); + } + + public IReadOnlyList ControlPoints + { + set + { + Content = null; + backgroundFlow.Clear(); + + if (value?.Any() != true) + return; + + for (int i = 0; i < value.Count; i++) + { + var cp = value[i]; + backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = cp }); + } + + Columns = createHeaders(); + Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + } + } + + private TableColumn[] createHeaders() + { + var columns = new List + { + new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("offset", Anchor.Centre), + new TableColumn("BPM", Anchor.Centre), + new TableColumn("Meter", Anchor.Centre), + new TableColumn("Sample Set", Anchor.Centre), + new TableColumn("Volume", Anchor.Centre), + }; + + return columns.ToArray(); + } + + private Drawable[] createContent(int index, ControlPoint controlPoint) + { + return new Drawable[] + { + new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = $"{controlPoint.Time}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = $"{(controlPoint as TimingControlPoint)?.BeatLength.ToString(CultureInfo.InvariantCulture) ?? "-"}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = $"{(controlPoint as TimingControlPoint)?.TimeSignature.ToString() ?? "-"}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + }; + } + + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); + + private class HeaderText : OsuSpriteText + { + public HeaderText(string text) + { + Text = text.ToUpper(); + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); + } + } + + public class RowBackground : OsuClickableContainer + { + private const int fade_duration = 100; + + private readonly Box hoveredBackground; + + public RowBackground() + { + RelativeSizeAxes = Axes.X; + Height = 25; + + AlwaysPresent = true; + + CornerRadius = 3; + Masking = true; + + Children = new Drawable[] + { + hoveredBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoveredBackground.Colour = colours.Blue; + } + + protected override bool OnHover(HoverEvent e) + { + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } + } } From 71d45d41d1f85125dfae1171e3b5a2f954f4053d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2019 17:46:39 +0900 Subject: [PATCH 03/77] Add basic visualisation of different control point types --- .../ControlPoints/ControlPointInfo.cs | 6 + osu.Game/Screens/Edit/Timing/TimingScreen.cs | 133 +++++++++++++----- 2 files changed, 105 insertions(+), 34 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 855084ad02..68eb0ec6d1 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -36,6 +36,12 @@ namespace osu.Game.Beatmaps.ControlPoints [JsonProperty] public SortedList EffectPoints { get; private set; } = new SortedList(Comparer.Default); + public IReadOnlyList AllControlPoints => + TimingPoints + .Concat((IEnumerable)DifficultyPoints) + .Concat(SamplePoints) + .Concat(EffectPoints).ToArray(); + /// /// Finds the difficulty control point that is active at . /// diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index be7058ae08..97ec03e6dd 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -2,13 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Globalization; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Beatmaps; @@ -16,6 +16,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -64,7 +65,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.Both, Child = new ControlPointTable { - ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints + ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.AllControlPoints } } }; @@ -115,7 +116,7 @@ namespace osu.Game.Screens.Edit.Timing }); } - public IReadOnlyList ControlPoints + public IEnumerable ControlPoints { set { @@ -125,14 +126,15 @@ namespace osu.Game.Screens.Edit.Timing if (value?.Any() != true) return; - for (int i = 0; i < value.Count; i++) + var grouped = value.GroupBy(cp => cp.Time, cp => cp); + + foreach (var group in grouped) { - var cp = value[i]; - backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = cp }); + backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = group.First() }); } Columns = createHeaders(); - Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + Content = grouped.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); } } @@ -141,41 +143,104 @@ namespace osu.Game.Screens.Edit.Timing var columns = new List { new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("offset", Anchor.Centre), - new TableColumn("BPM", Anchor.Centre), - new TableColumn("Meter", Anchor.Centre), - new TableColumn("Sample Set", Anchor.Centre), - new TableColumn("Volume", Anchor.Centre), + new TableColumn("time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Attributes", Anchor.Centre), }; return columns.ToArray(); } - private Drawable[] createContent(int index, ControlPoint controlPoint) + private Drawable[] createContent(int index, IGrouping controlPoints) => new Drawable[] { - return new Drawable[] + new OsuSpriteText { - new OsuSpriteText + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Margin = new MarginPadding(10) + }, + new OsuSpriteText + { + Text = $"{controlPoints.Key:n0}ms", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null), + Padding = new MarginPadding(10), + Spacing = new Vector2(10) + }, + }; + + private Drawable createAttribute(ControlPoint controlPoint) + { + if (controlPoint.AutoGenerated) + return null; + + switch (controlPoint) + { + case TimingControlPoint timing: + return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + + case DifficultyControlPoint difficulty: + + return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); + + case EffectControlPoint effect: + return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + + case SampleControlPoint sample: + return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + } + + return null; + } + + private class RowAttribute : CompositeDrawable, IHasTooltip + { + private readonly string header; + private readonly string content; + + public RowAttribute(string header, string content) + { + this.header = header; + this.content = content; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.X; + + Height = 20; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] { - Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - new OsuSpriteText - { - Text = $"{controlPoint.Time}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - new OsuSpriteText - { - Text = $"{(controlPoint as TimingControlPoint)?.BeatLength.ToString(CultureInfo.InvariantCulture) ?? "-"}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - new OsuSpriteText - { - Text = $"{(controlPoint as TimingControlPoint)?.TimeSignature.ToString() ?? "-"}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - }; + new Box + { + Colour = colours.Yellow, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Padding = new MarginPadding(2), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), + Text = header, + Colour = colours.Gray3 + }, + }; + } + + public string TooltipText => content; } protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); From ffec960b77960b855f9e8b63fc2a9b4e90814d2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2019 17:59:54 +0900 Subject: [PATCH 04/77] Split out classes --- .../Screens/Edit/Timing/ControlPointTable.cs | 186 +++++++++++++++ osu.Game/Screens/Edit/Timing/RowAttribute.cs | 59 +++++ osu.Game/Screens/Edit/Timing/TimingScreen.cs | 219 ------------------ 3 files changed, 245 insertions(+), 219 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/ControlPointTable.cs create mode 100644 osu.Game/Screens/Edit/Timing/RowAttribute.cs diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs new file mode 100644 index 0000000000..1ff88fb5b4 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -0,0 +1,186 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Screens.Edit.Timing +{ + public class ControlPointTable : TableContainer + { + private const float horizontal_inset = 20; + private const float row_height = 25; + private const int text_size = 14; + + private readonly FillFlowContainer backgroundFlow; + + [Resolved] + private Bindable controlPoint { get; set; } + + public ControlPointTable() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding { Horizontal = horizontal_inset }; + RowSize = new Dimension(GridSizeMode.Absolute, row_height); + + AddInternal(backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1f, + Padding = new MarginPadding { Horizontal = -horizontal_inset }, + Margin = new MarginPadding { Top = row_height } + }); + } + + public IEnumerable ControlPoints + { + set + { + Content = null; + backgroundFlow.Clear(); + + if (value?.Any() != true) + return; + + var grouped = value.GroupBy(cp => cp.Time, cp => cp); + + foreach (var group in grouped) + { + backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = group.First() }); + } + + Columns = createHeaders(); + Content = grouped.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + } + } + + private TableColumn[] createHeaders() + { + var columns = new List + { + new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Attributes", Anchor.Centre), + }; + + return columns.ToArray(); + } + + private Drawable[] createContent(int index, IGrouping controlPoints) => new Drawable[] + { + new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Margin = new MarginPadding(10) + }, + new OsuSpriteText + { + Text = $"{controlPoints.Key:n0}ms", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null), + Padding = new MarginPadding(10), + Spacing = new Vector2(10) + }, + }; + + private Drawable createAttribute(ControlPoint controlPoint) + { + if (controlPoint.AutoGenerated) + return null; + + switch (controlPoint) + { + case TimingControlPoint timing: + return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + + case DifficultyControlPoint difficulty: + + return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); + + case EffectControlPoint effect: + return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + + case SampleControlPoint sample: + return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + } + + return null; + } + + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); + + private class HeaderText : OsuSpriteText + { + public HeaderText(string text) + { + Text = text.ToUpper(); + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); + } + } + + public class RowBackground : OsuClickableContainer + { + private const int fade_duration = 100; + + private readonly Box hoveredBackground; + + public RowBackground() + { + RelativeSizeAxes = Axes.X; + Height = 25; + + AlwaysPresent = true; + + CornerRadius = 3; + Masking = true; + + Children = new Drawable[] + { + hoveredBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoveredBackground.Colour = colours.Blue; + } + + protected override bool OnHover(HoverEvent e) + { + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs new file mode 100644 index 0000000000..8716382142 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + public class RowAttribute : CompositeDrawable, IHasTooltip + { + private readonly string header; + private readonly string content; + + public RowAttribute(string header, string content) + { + this.header = header; + this.content = content; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.X; + + Height = 20; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Yellow, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Padding = new MarginPadding(2), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), + Text = header, + Colour = colours.Gray3 + }, + }; + } + + public string TooltipText => content; + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 97ec03e6dd..3dda57becc 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,22 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -87,216 +80,4 @@ namespace osu.Game.Screens.Edit.Timing } } } - - public class ControlPointTable : TableContainer - { - private const float horizontal_inset = 20; - private const float row_height = 25; - private const int text_size = 14; - - private readonly FillFlowContainer backgroundFlow; - - [Resolved] - private Bindable controlPoint { get; set; } - - public ControlPointTable() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Padding = new MarginPadding { Horizontal = horizontal_inset }; - RowSize = new Dimension(GridSizeMode.Absolute, row_height); - - AddInternal(backgroundFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Depth = 1f, - Padding = new MarginPadding { Horizontal = -horizontal_inset }, - Margin = new MarginPadding { Top = row_height } - }); - } - - public IEnumerable ControlPoints - { - set - { - Content = null; - backgroundFlow.Clear(); - - if (value?.Any() != true) - return; - - var grouped = value.GroupBy(cp => cp.Time, cp => cp); - - foreach (var group in grouped) - { - backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = group.First() }); - } - - Columns = createHeaders(); - Content = grouped.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); - } - } - - private TableColumn[] createHeaders() - { - var columns = new List - { - new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("Attributes", Anchor.Centre), - }; - - return columns.ToArray(); - } - - private Drawable[] createContent(int index, IGrouping controlPoints) => new Drawable[] - { - new OsuSpriteText - { - Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), - Margin = new MarginPadding(10) - }, - new OsuSpriteText - { - Text = $"{controlPoints.Key:n0}ms", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null), - Padding = new MarginPadding(10), - Spacing = new Vector2(10) - }, - }; - - private Drawable createAttribute(ControlPoint controlPoint) - { - if (controlPoint.AutoGenerated) - return null; - - switch (controlPoint) - { - case TimingControlPoint timing: - return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); - - case DifficultyControlPoint difficulty: - - return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); - - case EffectControlPoint effect: - return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); - - case SampleControlPoint sample: - return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); - } - - return null; - } - - private class RowAttribute : CompositeDrawable, IHasTooltip - { - private readonly string header; - private readonly string content; - - public RowAttribute(string header, string content) - { - this.header = header; - this.content = content; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AutoSizeAxes = Axes.X; - - Height = 20; - - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - - Masking = true; - CornerRadius = 5; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = colours.Yellow, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Padding = new MarginPadding(2), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), - Text = header, - Colour = colours.Gray3 - }, - }; - } - - public string TooltipText => content; - } - - protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); - - private class HeaderText : OsuSpriteText - { - public HeaderText(string text) - { - Text = text.ToUpper(); - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); - } - } - - public class RowBackground : OsuClickableContainer - { - private const int fade_duration = 100; - - private readonly Box hoveredBackground; - - public RowBackground() - { - RelativeSizeAxes = Axes.X; - Height = 25; - - AlwaysPresent = true; - - CornerRadius = 3; - Masking = true; - - Children = new Drawable[] - { - hoveredBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoveredBackground.Colour = colours.Blue; - } - - protected override bool OnHover(HoverEvent e) - { - hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); - base.OnHoverLost(e); - } - } - } } From 7dc65ec9645e71bd790f5e157bf757ebfd6748c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 20 Oct 2019 23:32:49 +0900 Subject: [PATCH 05/77] Add missing required types --- osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs index be84ae2cf9..54dcdc8da5 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Rulesets.Osu; @@ -11,6 +13,12 @@ namespace osu.Game.Tests.Visual.Editor [TestFixture] public class TestSceneTimingScreen : EditorClockTestScene { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ControlPointTable), + typeof(RowAttribute) + }; + [BackgroundDependencyLoader] private void load() { From 0fbba9a5e5c875f63a1e4afc7181aa4ee41895cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 20 Oct 2019 23:42:13 +0900 Subject: [PATCH 06/77] Split out more classes --- .../Visual/Editor/TestSceneTimingScreen.cs | 1 + .../Edit/Timing/ControlPointSettings.cs | 26 +++++++++++++++++++ osu.Game/Screens/Edit/Timing/TimingScreen.cs | 15 ----------- 3 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/ControlPointSettings.cs diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs index 54dcdc8da5..3e227169e0 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -16,6 +16,7 @@ namespace osu.Game.Tests.Visual.Editor public override IReadOnlyList RequiredTypes => new[] { typeof(ControlPointTable), + typeof(ControlPointSettings), typeof(RowAttribute) }; diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs new file mode 100644 index 0000000000..2feced9738 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Edit.Timing +{ + public class ControlPointSettings : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new Box + { + Colour = colours.Gray3, + RelativeSizeAxes = Axes.Both, + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 3dda57becc..9cbba6b155 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -64,20 +64,5 @@ namespace osu.Game.Screens.Edit.Timing }; } } - - public class ControlPointSettings : CompositeDrawable - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - RelativeSizeAxes = Axes.Both; - - InternalChild = new Box - { - Colour = colours.Gray3, - RelativeSizeAxes = Axes.Both, - }; - } - } } } From 80bf68c1081ed4dc90f1cc5c99c7810e8e34e597 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Oct 2019 00:06:38 +0900 Subject: [PATCH 07/77] Add control sections and hook up bindable control groups --- .../Edit/Timing/ControlPointSettings.cs | 130 +++++++++++++++++- .../Screens/Edit/Timing/ControlPointTable.cs | 4 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 3 +- 3 files changed, 131 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 2feced9738..dc4fb2a338 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -1,11 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -16,11 +25,126 @@ namespace osu.Game.Screens.Edit.Timing { RelativeSizeAxes = Axes.Both; - InternalChild = new Box + InternalChildren = new Drawable[] { - Colour = colours.Gray3, - RelativeSizeAxes = Axes.Both, + new Box + { + Colour = colours.Gray3, + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(30), + Children = createSections() + }, + } }; } + + private IReadOnlyList createSections() => new Drawable[] + { + new TimingSection(), + new DifficultySection(), + new SampleSection(), + new EffectSection(), + }; + + private class TimingSection : Section + { + } + + private class DifficultySection : Section + { + } + + private class SampleSection : Section + { + } + + private class EffectSection : Section + { + } + + private class Section : Container + where T : ControlPoint + { + private const float header_height = 20; + + protected override Container Content { get; } + + [Resolved] + private Bindable> selectedPoints { get; set; } + + protected Section() + { + RelativeSizeAxes = Axes.X; + AutoSizeDuration = 200; + AutoSizeEasing = Easing.OutQuint; + AutoSizeAxes = Axes.Y; + + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = Color4.Gray, + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = header_height, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) + }, + } + }, + Content = new Container() + { + Y = header_height, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + Colour = Color4.DarkGray, + RelativeSizeAxes = Axes.X, + Height = 100, + }, + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedPoints.BindValueChanged(points => + { + var matching = points.NewValue?.OfType().Where(p => !p.AutoGenerated).FirstOrDefault(); + + if (matching != null) + { + Content.BypassAutoSizeAxes = Axes.None; + } + else + { + Content.BypassAutoSizeAxes = Axes.Y; + } + }, true); + } + } } } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 1ff88fb5b4..5032e71fe8 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Timing private readonly FillFlowContainer backgroundFlow; [Resolved] - private Bindable controlPoint { get; set; } + private Bindable> selectedPoints { get; set; } public ControlPointTable() { @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Edit.Timing foreach (var group in grouped) { - backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = group.First() }); + backgroundFlow.Add(new RowBackground { Action = () => selectedPoints.Value = group }); } Columns = createHeaders(); diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 9cbba6b155..5da5d3f785 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -16,7 +17,7 @@ namespace osu.Game.Screens.Edit.Timing public class TimingScreen : EditorScreenWithTimeline { [Cached] - private readonly Bindable controlPoint = new Bindable(); + private Bindable> selectedPoints = new Bindable>(); protected override Drawable CreateMainContent() => new GridContainer { From 81e8b678d342c8a9f9f646974a9659790573cdea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2019 20:17:19 +0900 Subject: [PATCH 08/77] Update editor time when a new timing point is selected --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 5da5d3f785..ed045aca86 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -19,6 +21,9 @@ namespace osu.Game.Screens.Edit.Timing [Cached] private Bindable> selectedPoints = new Bindable>(); + [Resolved] + private IAdjustableClock clock { get; set; } + protected override Drawable CreateMainContent() => new GridContainer { RelativeSizeAxes = Axes.Both, @@ -37,6 +42,13 @@ namespace osu.Game.Screens.Edit.Timing } }; + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedPoints.BindValueChanged(selected => { clock.Seek(selected.NewValue.First().Time); }); + } + public class ControlPointList : CompositeDrawable { [Resolved] From 4883844c4c390aa515fef26d4b730c8992d6fe57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2019 20:57:56 +0900 Subject: [PATCH 09/77] Add basic information display for all types of control points --- .../Edit/Timing/ControlPointSettings.cs | 136 +++++++++++++++--- 1 file changed, 114 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index dc4fb2a338..1ab22679d9 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -14,7 +14,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -40,7 +39,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(30), + Spacing = new Vector2(2), Children = createSections() }, } @@ -57,31 +56,123 @@ namespace osu.Game.Screens.Edit.Timing private class TimingSection : Section { + private OsuSpriteText bpm; + private OsuSpriteText timeSignature; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bpm = new OsuSpriteText(), + timeSignature = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; + timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; + }); + } } private class DifficultySection : Section { + private OsuSpriteText multiplier; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + multiplier = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); + } } private class SampleSection : Section { + private OsuSpriteText bank; + private OsuSpriteText volume; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bank = new OsuSpriteText(), + volume = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bank.Text = $"Bank: {point.NewValue?.SampleBank}"; + volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; + }); + } } private class EffectSection : Section { + private OsuSpriteText kiai; + private OsuSpriteText omitBarLine; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + kiai = new OsuSpriteText(), + omitBarLine = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; + omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; + }); + } } - private class Section : Container + private class Section : CompositeDrawable where T : ControlPoint { - private const float header_height = 20; + private OsuCheckbox checkbox; + private Container content; - protected override Container Content { get; } + protected FillFlowContainer Flow { get; private set; } + + protected Bindable ControlPoint { get; } = new Bindable(); + + private const float header_height = 20; [Resolved] private Bindable> selectedPoints { get; set; } - protected Section() + [BackgroundDependencyLoader] + private void load(OsuColour colours) { RelativeSizeAxes = Axes.X; AutoSizeDuration = 200; @@ -94,7 +185,7 @@ namespace osu.Game.Screens.Edit.Timing { new Box { - Colour = Color4.Gray, + Colour = colours.Gray1, RelativeSizeAxes = Axes.Both, }, new Container @@ -103,13 +194,13 @@ namespace osu.Game.Screens.Edit.Timing Height = header_height, Children = new Drawable[] { - new OsuSpriteText + checkbox = new OsuCheckbox { - Text = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) - }, + LabelText = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) + } } }, - Content = new Container() + content = new Container { Y = header_height, RelativeSizeAxes = Axes.X, @@ -118,9 +209,15 @@ namespace osu.Game.Screens.Edit.Timing { new Box { - Colour = Color4.DarkGray, + Colour = colours.Gray2, + RelativeSizeAxes = Axes.Both, + }, + Flow = new FillFlowContainer + { + Padding = new MarginPadding(10), RelativeSizeAxes = Axes.X, - Height = 100, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, }, } } @@ -133,17 +230,12 @@ namespace osu.Game.Screens.Edit.Timing selectedPoints.BindValueChanged(points => { - var matching = points.NewValue?.OfType().Where(p => !p.AutoGenerated).FirstOrDefault(); + ControlPoint.Value = points.NewValue?.OfType().Where(p => !p.AutoGenerated).FirstOrDefault(); - if (matching != null) - { - Content.BypassAutoSizeAxes = Axes.None; - } - else - { - Content.BypassAutoSizeAxes = Axes.Y; - } + checkbox.Current.Value = ControlPoint.Value != null; }, true); + + checkbox.Current.BindValueChanged(selected => { content.BypassAutoSizeAxes = selected.NewValue ? Axes.None : Axes.Y; }, true); } } } From 5e22eed131ca60093f816fa8a50a06a4536f8b85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2019 21:50:21 +0900 Subject: [PATCH 10/77] Add add/remove buttons --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 53 +++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index ed045aca86..b6bd9ae61d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -13,6 +14,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -51,9 +54,14 @@ namespace osu.Game.Screens.Edit.Timing public class ControlPointList : CompositeDrawable { + private OsuButton deleteButton; + [Resolved] protected IBindable Beatmap { get; private set; } + [Resolved] + private Bindable> selectedPoints { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -73,9 +81,52 @@ namespace osu.Game.Screens.Edit.Timing { ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.AllControlPoints } - } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding(10), + Spacing = new Vector2(5), + Children = new Drawable[] + { + deleteButton = new OsuButton + { + Text = "-", + Size = new Vector2(30, 30), + Action = delete, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + new OsuButton + { + Text = "+", + Action = addNew, + Size = new Vector2(30, 30), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } + }, }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedPoints.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); + } + + private void delete() + { + } + + private void addNew() + { + } } } } From c06f142433d547abe9d96bf082df5c36e8c8400b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Oct 2019 11:22:55 +0900 Subject: [PATCH 11/77] Fix some spacing and references --- osu.Game/Screens/Edit/Timing/ControlPointSettings.cs | 2 -- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 1ab22679d9..0974745294 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -39,7 +38,6 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(2), Children = createSections() }, } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 5032e71fe8..777d34e75b 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit.Timing var columns = new List { new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Attributes", Anchor.Centre), }; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index b6bd9ae61d..bd0a75f0b0 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; From e38b7cb169767b0d637cfcb166ed919161aa3087 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Oct 2019 17:00:56 +0900 Subject: [PATCH 12/77] Replace local Equatable implementations with abstract EquivalentTo --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 12 +++++++++--- .../Beatmaps/ControlPoints/DifficultyControlPoint.cs | 8 +++----- .../Beatmaps/ControlPoints/EffectControlPoint.cs | 10 ++++------ .../Beatmaps/ControlPoints/SampleControlPoint.cs | 9 ++++----- .../Beatmaps/ControlPoints/TimingControlPoint.cs | 9 ++++----- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 8 ++++---- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index abe7e5e803..0081fab46a 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -5,7 +5,7 @@ using System; namespace osu.Game.Beatmaps.ControlPoints { - public class ControlPoint : IComparable, IEquatable + public abstract class ControlPoint : IComparable, IEquatable { /// /// The time at which the control point takes effect. @@ -19,7 +19,13 @@ namespace osu.Game.Beatmaps.ControlPoints public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); - public bool Equals(ControlPoint other) - => Time.Equals(other?.Time); + /// + /// Whether this control point is equivalent to another, ignoring time. + /// + /// Another control point to compare with. + /// Whether equivalent. + public abstract bool EquivalentTo(ControlPoint other); + + public bool Equals(ControlPoint other) => Time.Equals(other?.Time) && EquivalentTo(other); } } diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index a3e3121575..42651fd0ca 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osuTK; namespace osu.Game.Beatmaps.ControlPoints { - public class DifficultyControlPoint : ControlPoint, IEquatable + public class DifficultyControlPoint : ControlPoint { /// /// The speed multiplier at this control point. @@ -19,8 +18,7 @@ namespace osu.Game.Beatmaps.ControlPoints private double speedMultiplier = 1; - public bool Equals(DifficultyControlPoint other) - => base.Equals(other) - && SpeedMultiplier.Equals(other?.SpeedMultiplier); + public override bool EquivalentTo(ControlPoint other) => + other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(speedMultiplier); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 354d86dc13..928f2a51ad 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -1,11 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; - namespace osu.Game.Beatmaps.ControlPoints { - public class EffectControlPoint : ControlPoint, IEquatable + public class EffectControlPoint : ControlPoint { /// /// Whether this control point enables Kiai mode. @@ -17,8 +15,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// public bool OmitFirstBarLine; - public bool Equals(EffectControlPoint other) - => base.Equals(other) - && KiaiMode == other?.KiaiMode && OmitFirstBarLine == other.OmitFirstBarLine; + public override bool EquivalentTo(ControlPoint other) => + other is EffectControlPoint otherTyped && + KiaiMode == otherTyped.KiaiMode && OmitFirstBarLine == otherTyped.OmitFirstBarLine; } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 7bc7a9056d..35eefebca4 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Audio; namespace osu.Game.Beatmaps.ControlPoints { - public class SampleControlPoint : ControlPoint, IEquatable + public class SampleControlPoint : ControlPoint { public const string DEFAULT_BANK = "normal"; @@ -45,8 +44,8 @@ namespace osu.Game.Beatmaps.ControlPoints return newSampleInfo; } - public bool Equals(SampleControlPoint other) - => base.Equals(other) - && string.Equals(SampleBank, other?.SampleBank) && SampleVolume == other?.SampleVolume; + public override bool EquivalentTo(ControlPoint other) => + other is SampleControlPoint otherTyped && + string.Equals(SampleBank, otherTyped?.SampleBank) && SampleVolume == otherTyped?.SampleVolume; } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index ccb8a92b3a..03b188929b 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -1,13 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osuTK; using osu.Game.Beatmaps.Timing; namespace osu.Game.Beatmaps.ControlPoints { - public class TimingControlPoint : ControlPoint, IEquatable + public class TimingControlPoint : ControlPoint { /// /// The time signature at this control point. @@ -27,8 +26,8 @@ namespace osu.Game.Beatmaps.ControlPoints private double beatLength = DEFAULT_BEAT_LENGTH; - public bool Equals(TimingControlPoint other) - => base.Equals(other) - && TimeSignature == other?.TimeSignature && beatLength.Equals(other.beatLength); + public override bool EquivalentTo(ControlPoint other) => + other is TimingControlPoint otherTyped + && TimeSignature == otherTyped?.TimeSignature && beatLength.Equals(otherTyped.beatLength); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 83d20da458..26f7209be6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -189,7 +189,7 @@ namespace osu.Game.Beatmaps.Formats Foreground = 3 } - internal class LegacySampleControlPoint : SampleControlPoint, IEquatable + internal class LegacySampleControlPoint : SampleControlPoint { public int CustomSampleBank; @@ -203,9 +203,9 @@ namespace osu.Game.Beatmaps.Formats return baseInfo; } - public bool Equals(LegacySampleControlPoint other) - => base.Equals(other) - && CustomSampleBank == other?.CustomSampleBank; + public override bool EquivalentTo(ControlPoint other) => + base.EquivalentTo(other) + && CustomSampleBank == ((LegacySampleControlPoint)other).CustomSampleBank; } } } From 94ffe03e6e71701e723db6196532dde41fd1c254 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Oct 2019 16:19:06 +0900 Subject: [PATCH 13/77] Group timing points --- .../TestSceneDrawableHitObjects.cs | 2 +- .../TestSceneOsuDistanceSnapGrid.cs | 13 +- .../TestSceneSlider.cs | 2 +- .../TestSceneSliderInput.cs | 6 +- .../TestSceneTaikoPlayfield.cs | 6 +- .../Audio/DrumSampleMapping.cs | 7 +- .../Editor/TestSceneDistanceSnapGrid.cs | 10 +- .../Editor/TestSceneEditorSeekSnapping.cs | 20 ++- .../TestSceneDrawableScrollingRuleset.cs | 34 ++--- .../TestSceneBeatSyncedContainer.cs | 4 +- .../Beatmaps/ControlPoints/ControlPoint.cs | 6 +- .../ControlPoints/ControlPointGroup.cs | 48 +++++++ .../ControlPoints/ControlPointInfo.cs | 130 ++++++++++++++++-- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 92 ++----------- .../Containers/BeatSyncedContainer.cs | 2 - osu.Game/Screens/Edit/EditorClock.cs | 5 +- 16 files changed, 233 insertions(+), 154 deletions(-) create mode 100644 osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 7a9b61c60c..0369b6db4e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Tests private void load() { var controlPointInfo = new ControlPointInfo(); - controlPointInfo.TimingPoints.Add(new TimingControlPoint()); + controlPointInfo.Add(0, new TimingControlPoint()); WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index 6b8daa531f..b66123e628 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -53,9 +53,8 @@ namespace osu.Game.Rulesets.Osu.Tests Clear(); editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; - editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); beatDivisor.Value = 1; }); @@ -80,8 +79,8 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep($"set beat length = {beatLength}", () => { - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beatLength }); }); createGrid(); @@ -95,8 +94,8 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep($"set speed multiplier = {multiplier}", () => { - editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); - editorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = multiplier }); }); createGrid(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 4893ebfdd4..a955911bd5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -308,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Tests private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier) { var cpi = new ControlPointInfo(); - cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); + cpi.Add(0, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 2eb783233a..5f75cbabec 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -313,10 +313,6 @@ namespace osu.Game.Rulesets.Osu.Tests }, 25), } }, - ControlPointInfo = - { - DifficultyPoints = { new DifficultyControlPoint { SpeedMultiplier = 0.1f } } - }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, @@ -324,6 +320,8 @@ namespace osu.Game.Rulesets.Osu.Tests }, }); + Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); p.OnLoadComplete += _ => diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index eaa8ca7ebb..8522a42739 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Tests AddStep("Reset height", () => changePlayfieldSize(6)); var controlPointInfo = new ControlPointInfo(); - controlPointInfo.TimingPoints.Add(new TimingControlPoint()); + controlPointInfo.Add(0, new TimingControlPoint()); WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap { @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Taiko.Tests HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; var cpi = new ControlPointInfo(); - cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai }); + cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); Hit hit = new Hit(); hit.ApplyDefaults(cpi, new BeatmapDifficulty()); @@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Taiko.Tests HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; var cpi = new ControlPointInfo(); - cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai }); + cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); Hit hit = new Hit(); hit.ApplyDefaults(cpi, new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs index ad2596931d..aaf113f216 100644 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs @@ -19,12 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Audio { this.controlPoints = controlPoints; - IEnumerable samplePoints; - if (controlPoints.SamplePoints.Count == 0) - // Get the default sample point - samplePoints = new[] { controlPoints.SamplePointAt(double.MinValue) }; - else - samplePoints = controlPoints.SamplePoints; + IEnumerable samplePoints = controlPoints.SamplePoints.Count == 0 ? new[] { controlPoints.SamplePointAt(double.MinValue) } : controlPoints.SamplePoints; foreach (var s in samplePoints) { diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index a9e5930478..07646fdb78 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Editor public TestSceneDistanceSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); createGrid(); } @@ -42,8 +42,8 @@ namespace osu.Game.Tests.Visual.Editor { Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); BeatDivisor.Value = 1; }); @@ -81,8 +81,8 @@ namespace osu.Game.Tests.Visual.Editor { AddStep($"set beat length = {beatLength}", () => { - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beatLength }); }); createGrid(); diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs index b997d6aaeb..3118e0cabe 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs @@ -28,18 +28,7 @@ namespace osu.Game.Tests.Visual.Editor { var testBeatmap = new Beatmap { - ControlPointInfo = new ControlPointInfo - { - TimingPoints = - { - new TimingControlPoint { Time = 0, BeatLength = 200 }, - new TimingControlPoint { Time = 100, BeatLength = 400 }, - new TimingControlPoint { Time = 175, BeatLength = 800 }, - new TimingControlPoint { Time = 350, BeatLength = 200 }, - new TimingControlPoint { Time = 450, BeatLength = 100 }, - new TimingControlPoint { Time = 500, BeatLength = 307.69230769230802 } - } - }, + ControlPointInfo = new ControlPointInfo(), HitObjects = { new HitCircle { StartTime = 0 }, @@ -47,6 +36,13 @@ namespace osu.Game.Tests.Visual.Editor } }; + testBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 200 }); + testBeatmap.ControlPointInfo.Add(100, new TimingControlPoint { BeatLength = 400 }); + testBeatmap.ControlPointInfo.Add(175, new TimingControlPoint { BeatLength = 800 }); + testBeatmap.ControlPointInfo.Add(350, new TimingControlPoint { BeatLength = 200 }); + testBeatmap.ControlPointInfo.Add(450, new TimingControlPoint { BeatLength = 100 }); + testBeatmap.ControlPointInfo.Add(500, new TimingControlPoint { BeatLength = 307.69230769230802 }); + Beatmap.Value = CreateWorkingBeatmap(testBeatmap); Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index dcab964d6d..684e79b3f5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -47,7 +47,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRelativeBeatLengthScaleSingleTimingPoint() { - var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range / 2 }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 }); createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -61,10 +62,10 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant() { - var beatmap = createBeatmap( - new TimingControlPoint { BeatLength = time_range / 2 }, - new TimingControlPoint { Time = 12000, BeatLength = time_range }, - new TimingControlPoint { Time = 100000, BeatLength = time_range }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 }); + beatmap.ControlPointInfo.Add(12000, new TimingControlPoint { BeatLength = time_range }); + beatmap.ControlPointInfo.Add(100000, new TimingControlPoint { BeatLength = time_range }); createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -75,9 +76,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRelativeBeatLengthScaleFromSecondTimingPoint() { - var beatmap = createBeatmap( - new TimingControlPoint { BeatLength = time_range }, - new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); + beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 }); createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -97,9 +98,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNonRelativeScale() { - var beatmap = createBeatmap( - new TimingControlPoint { BeatLength = time_range }, - new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); + beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 }); createTest(beatmap); @@ -119,7 +120,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSliderMultiplierDoesNotAffectRelativeBeatLength() { - var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2; createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -132,7 +134,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSliderMultiplierAffectsNonRelativeBeatLength() { - var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2; createTest(beatmap); @@ -154,14 +157,11 @@ namespace osu.Game.Tests.Visual.Gameplay /// Creates an , containing 10 hitobjects and user-provided timing points. /// The hitobjects are spaced milliseconds apart. /// - /// The timing points to add to the beatmap. /// The . - private IBeatmap createBeatmap(params TimingControlPoint[] timingControlPoints) + private IBeatmap createBeatmap() { var beatmap = new Beatmap { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } }; - beatmap.ControlPointInfo.TimingPoints.AddRange(timingControlPoints); - for (int i = 0; i < 10; i++) beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index d84ffa0d93..b6df559686 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Internal; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; @@ -10,7 +11,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Lists; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private SortedList timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; + private IReadOnlyList timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 0081fab46a..6288c1460f 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -10,13 +10,17 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time at which the control point takes effect. /// - public double Time; + public double Time => controlPointGroup?.Time ?? 0; /// /// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap. /// internal bool AutoGenerated; + private ControlPointGroup controlPointGroup; + + public void AttachGroup(ControlPointGroup pointGroup) => this.controlPointGroup = pointGroup; + public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); /// diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs new file mode 100644 index 0000000000..c4b990675e --- /dev/null +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class ControlPointGroup : IComparable + { + public event Action ItemAdded; + public event Action ItemRemoved; + + /// + /// The time at which the control point takes effect. + /// + public double Time { get; } + + public IReadOnlyList ControlPoints => controlPoints; + + private readonly List controlPoints = new List(); + + public ControlPointGroup(double time) + { + Time = time; + } + + public int CompareTo(ControlPointGroup other) => Time.CompareTo(other.Time); + + public void Add(ControlPoint point) + { + point.AttachGroup(this); + + foreach (var existing in controlPoints.Where(p => p.GetType() == point.GetType()).ToArray()) + Remove(existing); + + controlPoints.Add(point); + ItemAdded?.Invoke(point); + } + + public void Remove(ControlPoint point) + { + controlPoints.Remove(point); + ItemRemoved?.Invoke(point); + } + } +} diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 68eb0ec6d1..2175eccaa2 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -12,35 +12,50 @@ namespace osu.Game.Beatmaps.ControlPoints [Serializable] public class ControlPointInfo { + /// + /// Control point groups. + /// + [JsonProperty] + public IReadOnlyList Groups => groups; + + private readonly SortedList groups = new SortedList(Comparer.Default); + /// /// All timing points. /// [JsonProperty] - public SortedList TimingPoints { get; private set; } = new SortedList(Comparer.Default); + public IReadOnlyList TimingPoints => timingPoints; + + private readonly SortedList timingPoints = new SortedList(Comparer.Default); /// /// All difficulty points. /// [JsonProperty] - public SortedList DifficultyPoints { get; private set; } = new SortedList(Comparer.Default); + public IReadOnlyList DifficultyPoints => difficultyPoints; + + private readonly SortedList difficultyPoints = new SortedList(Comparer.Default); /// /// All sound points. /// [JsonProperty] - public SortedList SamplePoints { get; private set; } = new SortedList(Comparer.Default); + public IReadOnlyList SamplePoints => samplePoints; + + private readonly SortedList samplePoints = new SortedList(Comparer.Default); /// /// All effect points. /// [JsonProperty] - public SortedList EffectPoints { get; private set; } = new SortedList(Comparer.Default); + public IReadOnlyList EffectPoints => effectPoints; - public IReadOnlyList AllControlPoints => - TimingPoints - .Concat((IEnumerable)DifficultyPoints) - .Concat(SamplePoints) - .Concat(EffectPoints).ToArray(); + private readonly SortedList effectPoints = new SortedList(Comparer.Default); + + /// + /// All control points, of all types. + /// + public IEnumerable AllControlPoints => Groups.SelectMany(g => g.ControlPoints); /// /// Finds the difficulty control point that is active at . @@ -70,6 +85,28 @@ namespace osu.Game.Beatmaps.ControlPoints /// The timing control point. public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null); + /// + /// Finds the closest of the same type as that is active at . + /// + /// The time to find the timing control point at. + /// A reference point to infer type. + /// The timing control point. + public ControlPoint SimilarPointAt(double time, ControlPoint referencePoint) + { + switch (referencePoint) + { + case TimingControlPoint _: return TimingPointAt(time); + + case EffectControlPoint _: return EffectPointAt(time); + + case SampleControlPoint _: return SamplePointAt(time); + + case DifficultyControlPoint _: return DifficultyPointAt(time); + } + + return null; + } + /// /// Finds the maximum BPM represented by any timing control point. /// @@ -98,7 +135,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the control point at. /// The control point to use when is before any control points. If null, a new control point will be constructed. /// The active control point at . - private T binarySearch(SortedList list, double time, T prePoint = null) + private T binarySearch(IReadOnlyList list, double time, T prePoint = null) where T : ControlPoint, new() { if (list == null) @@ -131,5 +168,78 @@ namespace osu.Game.Beatmaps.ControlPoints // l will be the first control point with Time > time, but we want the one before it return list[l - 1]; } + + public void Add(double time, ControlPoint newPoint, bool force = false) + { + if (!force && SimilarPointAt(time, newPoint)?.EquivalentTo(newPoint) == true) + return; + + GroupAt(time, true).Add(newPoint); + } + + public ControlPointGroup GroupAt(double time, bool createIfNotExisting) + { + var existing = Groups.FirstOrDefault(g => g.Time == time); + + if (existing != null) + return existing; + + if (createIfNotExisting) + { + var newGroup = new ControlPointGroup(time); + newGroup.ItemAdded += groupItemAdded; + newGroup.ItemRemoved += groupItemRemoved; + groups.Add(newGroup); + return newGroup; + } + + return null; + } + + private void groupItemRemoved(ControlPoint obj) + { + switch (obj) + { + case TimingControlPoint typed: + timingPoints.Remove(typed); + break; + + case EffectControlPoint typed: + effectPoints.Remove(typed); + break; + + case SampleControlPoint typed: + samplePoints.Remove(typed); + break; + + case DifficultyControlPoint typed: + difficultyPoints.Remove(typed); + break; + } + } + + private void groupItemAdded(ControlPoint obj) + { + switch (obj) + { + case TimingControlPoint typed: + timingPoints.Add(typed); + break; + + case EffectControlPoint typed: + effectPoints.Add(typed); + break; + + case SampleControlPoint typed: + samplePoints.Add(typed); + break; + + case DifficultyControlPoint typed: + difficultyPoints.Add(typed); + break; + } + } + + public void Clear() => groups.Clear(); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 786b7611b5..61f70a8c27 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -369,31 +369,29 @@ namespace osu.Game.Beatmaps.Formats if (timingChange) { var controlPoint = CreateTimingControlPoint(); - controlPoint.Time = time; controlPoint.BeatLength = beatLength; controlPoint.TimeSignature = timeSignature; - handleTimingControlPoint(controlPoint); + beatmap.ControlPointInfo.Add(time, controlPoint); + } + else + { + beatmap.ControlPointInfo.Add(time, new DifficultyControlPoint + { + SpeedMultiplier = speedMultiplier, + AutoGenerated = timingChange + }); } - handleDifficultyControlPoint(new DifficultyControlPoint + beatmap.ControlPointInfo.Add(time, new EffectControlPoint { - Time = time, - SpeedMultiplier = speedMultiplier, - AutoGenerated = timingChange - }); - - handleEffectControlPoint(new EffectControlPoint - { - Time = time, KiaiMode = kiaiMode, OmitFirstBarLine = omitFirstBarSignature, AutoGenerated = timingChange }); - handleSampleControlPoint(new LegacySampleControlPoint + beatmap.ControlPointInfo.Add(time, new LegacySampleControlPoint { - Time = time, SampleBank = stringSampleSet, SampleVolume = sampleVolume, CustomSampleBank = customSampleBank, @@ -401,74 +399,6 @@ namespace osu.Game.Beatmaps.Formats }); } - private void handleTimingControlPoint(TimingControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.TimingPointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.TimingPoints.Remove(existing); - } - - beatmap.ControlPointInfo.TimingPoints.Add(newPoint); - } - - private void handleDifficultyControlPoint(DifficultyControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.DifficultyPoints.Remove(existing); - } - - beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint); - } - - private void handleEffectControlPoint(EffectControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.EffectPoints.Remove(existing); - } - - beatmap.ControlPointInfo.EffectPoints.Add(newPoint); - } - - private void handleSampleControlPoint(SampleControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.SamplePoints.Remove(existing); - } - - beatmap.ControlPointInfo.SamplePoints.Add(newPoint); - } - private void handleHitObject(string line) { // If the ruleset wasn't specified, assume the osu!standard ruleset. diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 370d044ba4..2832a70518 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -105,12 +105,10 @@ namespace osu.Game.Graphics.Containers { BeatLength = default_beat_length, AutoGenerated = true, - Time = 0 }; defaultEffect = new EffectControlPoint { - Time = 0, AutoGenerated = true, KiaiMode = false, OmitFirstBarLine = false diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 24fb561f04..1cfb123f25 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using Microsoft.EntityFrameworkCore.Internal; using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -57,7 +58,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.Find(t => t.Time > timingPoint.Time); + var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); if (position > nextTimingPoint?.Time) position = nextTimingPoint.Time; @@ -124,7 +125,7 @@ namespace osu.Game.Screens.Edit if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First()) seekTime = timingPoint.Time; - var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time); + var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); if (seekTime > nextTimingPoint?.Time) seekTime = nextTimingPoint.Time; From da6769f0fcd9966a24624ec97451a16dd01fc187 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Oct 2019 17:00:23 +0900 Subject: [PATCH 14/77] Remove necessity of AutoGenerated flag --- .../Formats/LegacyBeatmapDecoderTest.cs | 10 +-- .../Beatmaps/ControlPoints/ControlPoint.cs | 7 +-- .../ControlPoints/ControlPointInfo.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 63 ++++++++++++++----- .../Containers/BeatSyncedContainer.cs | 2 - .../Edit/Timing/ControlPointSettings.cs | 2 +- .../Screens/Edit/Timing/ControlPointTable.cs | 3 - 7 files changed, 56 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index de516d3142..c50250159e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); - Assert.AreEqual(42, controlPoints.DifficultyPoints.Count); - Assert.AreEqual(42, controlPoints.SamplePoints.Count); - Assert.AreEqual(42, controlPoints.EffectPoints.Count); + Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); + Assert.AreEqual(34, controlPoints.SamplePoints.Count); + Assert.AreEqual(8, controlPoints.EffectPoints.Count); var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(48428); - Assert.AreEqual(48428, difficultyPoint.Time); + Assert.AreEqual(0, difficultyPoint.Time); Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(116999); @@ -224,7 +224,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsFalse(effectPoint.OmitFirstBarLine); effectPoint = controlPoints.EffectPointAt(119637); - Assert.AreEqual(119637, effectPoint.Time); + Assert.AreEqual(95901, effectPoint.Time); Assert.IsFalse(effectPoint.KiaiMode); Assert.IsFalse(effectPoint.OmitFirstBarLine); } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 6288c1460f..0861e00d8d 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -12,11 +12,6 @@ namespace osu.Game.Beatmaps.ControlPoints /// public double Time => controlPointGroup?.Time ?? 0; - /// - /// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap. - /// - internal bool AutoGenerated; - private ControlPointGroup controlPointGroup; public void AttachGroup(ControlPointGroup pointGroup) => this.controlPointGroup = pointGroup; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 2175eccaa2..7e9c8844f0 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -177,7 +177,7 @@ namespace osu.Game.Beatmaps.ControlPoints GroupAt(time, true).Add(newPoint); } - public ControlPointGroup GroupAt(double time, bool createIfNotExisting) + public ControlPointGroup GroupAt(double time, bool createIfNotExisting = false) { var existing = Groups.FirstOrDefault(g => g.Time == time); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 61f70a8c27..5589aecc19 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using osu.Framework.IO.File; @@ -50,6 +51,8 @@ namespace osu.Game.Beatmaps.Formats base.ParseStreamInto(stream, beatmap); + flushPendingPoints(); + // Objects may be out of order *only* if a user has manually edited an .osu file. // Unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828). // OrderBy is used to guarantee that the parsing order of hitobjects with equal start times is maintained (stably-sorted) @@ -369,34 +372,64 @@ namespace osu.Game.Beatmaps.Formats if (timingChange) { var controlPoint = CreateTimingControlPoint(); + controlPoint.BeatLength = beatLength; controlPoint.TimeSignature = timeSignature; - beatmap.ControlPointInfo.Add(time, controlPoint); - } - else - { - beatmap.ControlPointInfo.Add(time, new DifficultyControlPoint - { - SpeedMultiplier = speedMultiplier, - AutoGenerated = timingChange - }); + addControlPoint(time, controlPoint, true); } - beatmap.ControlPointInfo.Add(time, new EffectControlPoint + addControlPoint(time, new DifficultyControlPoint + { + SpeedMultiplier = speedMultiplier, + }, timingChange); + + addControlPoint(time, new EffectControlPoint { KiaiMode = kiaiMode, OmitFirstBarLine = omitFirstBarSignature, - AutoGenerated = timingChange - }); + }, timingChange); - beatmap.ControlPointInfo.Add(time, new LegacySampleControlPoint + addControlPoint(time, new LegacySampleControlPoint { SampleBank = stringSampleSet, SampleVolume = sampleVolume, CustomSampleBank = customSampleBank, - AutoGenerated = timingChange - }); + }, timingChange); + + // To handle the scenario where a non-timing line shares the same time value as a subsequent timing line but + // appears earlier in the file, we buffer non-timing control points and rewrite them *after* control points from the timing line + // with the same time value (allowing them to overwrite as necessary). + // + // The expected outcome is that we prefer the non-timing line's adjustments over the timing line's adjustments when time is equal. + if (timingChange) + flushPendingPoints(); + } + + private readonly List pendingControlPoints = new List(); + private double pendingControlPointsTime; + + private void addControlPoint(double time, ControlPoint point, bool timingChange) + { + if (timingChange) + { + beatmap.ControlPointInfo.Add(time, point); + return; + } + + if (time != pendingControlPointsTime) + flushPendingPoints(); + + pendingControlPoints.Add(point); + pendingControlPointsTime = time; + } + + private void flushPendingPoints() + { + foreach (var p in pendingControlPoints) + beatmap.ControlPointInfo.Add(pendingControlPointsTime, p); + + pendingControlPoints.Clear(); } private void handleHitObject(string line) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 2832a70518..2e76ab964f 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -104,12 +104,10 @@ namespace osu.Game.Graphics.Containers defaultTiming = new TimingControlPoint { BeatLength = default_beat_length, - AutoGenerated = true, }; defaultEffect = new EffectControlPoint { - AutoGenerated = true, KiaiMode = false, OmitFirstBarLine = false }; diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 0974745294..ce72d7c191 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -228,7 +228,7 @@ namespace osu.Game.Screens.Edit.Timing selectedPoints.BindValueChanged(points => { - ControlPoint.Value = points.NewValue?.OfType().Where(p => !p.AutoGenerated).FirstOrDefault(); + ControlPoint.Value = points.NewValue?.OfType().FirstOrDefault(); checkbox.Current.Value = ControlPoint.Value != null; }, true); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 777d34e75b..597200e54c 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -105,9 +105,6 @@ namespace osu.Game.Screens.Edit.Timing private Drawable createAttribute(ControlPoint controlPoint) { - if (controlPoint.AutoGenerated) - return null; - switch (controlPoint) { case TimingControlPoint timing: From b8efc59cdcd2be6d4ca8cbe9466221c61b70672c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Oct 2019 20:13:22 +0900 Subject: [PATCH 15/77] Update UI components to use new grouping --- .../Edit/Timing/ControlPointSettings.cs | 4 ++-- .../Screens/Edit/Timing/ControlPointTable.cs | 18 ++++++++---------- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 10 ++++------ 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index ce72d7c191..470750b860 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Edit.Timing private const float header_height = 20; [Resolved] - private Bindable> selectedPoints { get; set; } + private Bindable selectedPoints { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -228,7 +228,7 @@ namespace osu.Game.Screens.Edit.Timing selectedPoints.BindValueChanged(points => { - ControlPoint.Value = points.NewValue?.OfType().FirstOrDefault(); + ControlPoint.Value = points.NewValue?.ControlPoints.OfType().FirstOrDefault(); checkbox.Current.Value = ControlPoint.Value != null; }, true); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 597200e54c..aca74d4a20 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Timing private readonly FillFlowContainer backgroundFlow; [Resolved] - private Bindable> selectedPoints { get; set; } + private Bindable selectedGroup { get; set; } public ControlPointTable() { @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Timing }); } - public IEnumerable ControlPoints + public IEnumerable ControlGroups { set { @@ -56,15 +56,13 @@ namespace osu.Game.Screens.Edit.Timing if (value?.Any() != true) return; - var grouped = value.GroupBy(cp => cp.Time, cp => cp); - - foreach (var group in grouped) + foreach (var group in value) { - backgroundFlow.Add(new RowBackground { Action = () => selectedPoints.Value = group }); + backgroundFlow.Add(new RowBackground { Action = () => selectedGroup.Value = group }); } Columns = createHeaders(); - Content = grouped.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + Content = value.Select((g, i) => createContent(i, g)).ToArray().ToRectangular(); } } @@ -80,7 +78,7 @@ namespace osu.Game.Screens.Edit.Timing return columns.ToArray(); } - private Drawable[] createContent(int index, IGrouping controlPoints) => new Drawable[] + private Drawable[] createContent(int index, ControlPointGroup group) => new Drawable[] { new OsuSpriteText { @@ -90,14 +88,14 @@ namespace osu.Game.Screens.Edit.Timing }, new OsuSpriteText { - Text = $"{controlPoints.Key:n0}ms", + Text = $"{group.Time:n0}ms", Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, new FillFlowContainer { RelativeSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null), + ChildrenEnumerable = group.ControlPoints.Select(createAttribute).Where(c => c != null), Padding = new MarginPadding(10), Spacing = new Vector2(10) }, diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index bd0a75f0b0..a9fd038829 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -21,7 +19,7 @@ namespace osu.Game.Screens.Edit.Timing public class TimingScreen : EditorScreenWithTimeline { [Cached] - private Bindable> selectedPoints = new Bindable>(); + private Bindable selectedPoints = new Bindable(); [Resolved] private IAdjustableClock clock { get; set; } @@ -48,7 +46,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedPoints.BindValueChanged(selected => { clock.Seek(selected.NewValue.First().Time); }); + selectedPoints.BindValueChanged(selected => { clock.Seek(selected.NewValue.Time); }); } public class ControlPointList : CompositeDrawable @@ -59,7 +57,7 @@ namespace osu.Game.Screens.Edit.Timing protected IBindable Beatmap { get; private set; } [Resolved] - private Bindable> selectedPoints { get; set; } + private Bindable selectedPoints { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -78,7 +76,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.Both, Child = new ControlPointTable { - ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.AllControlPoints + ControlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups } }, new FillFlowContainer From 8ccff0e9cf6e42f21a23b324bc12cc6bea4105fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 26 Oct 2019 11:20:07 +0900 Subject: [PATCH 16/77] temp --- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 6 +++--- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index c50250159e..7317771bac 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); - Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); + Assert.AreEqual(6, controlPoints.DifficultyPoints.Count); Assert.AreEqual(34, controlPoints.SamplePoints.Count); - Assert.AreEqual(8, controlPoints.EffectPoints.Count); + Assert.AreEqual(9, controlPoints.EffectPoints.Count); var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(48428); - Assert.AreEqual(0, difficultyPoint.Time); + Assert.AreEqual(956, difficultyPoint.Time); Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(116999); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 24e3c3d8ca..f8c6d3aa3b 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -95,13 +95,13 @@ namespace osu.Game.Beatmaps.ControlPoints { switch (referencePoint) { - case TimingControlPoint _: return TimingPointAt(time); + case TimingControlPoint _: return binarySearch(TimingPoints, time); - case EffectControlPoint _: return EffectPointAt(time); + case EffectControlPoint _: return binarySearch(EffectPoints, time); - case SampleControlPoint _: return SamplePointAt(time); + case SampleControlPoint _: return binarySearch(SamplePoints, time); - case DifficultyControlPoint _: return DifficultyPointAt(time); + case DifficultyControlPoint _: return binarySearch(DifficultyPoints, time); } return null; From 93b003eb5adf1fdb2c31a6e6af2809e2fc9e315a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Oct 2019 13:31:23 +0900 Subject: [PATCH 17/77] Add selected row state --- .../Screens/Edit/Timing/ControlPointTable.cs | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index aca74d4a20..df1f6daf4e 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -58,7 +59,7 @@ namespace osu.Game.Screens.Edit.Timing foreach (var group in value) { - backgroundFlow.Add(new RowBackground { Action = () => selectedGroup.Value = group }); + backgroundFlow.Add(new RowBackground(group)); } Columns = createHeaders(); @@ -135,12 +136,17 @@ namespace osu.Game.Screens.Edit.Timing public class RowBackground : OsuClickableContainer { + private readonly ControlPointGroup controlGroup; private const int fade_duration = 100; private readonly Box hoveredBackground; - public RowBackground() + [Resolved] + private Bindable selectedGroup { get; set; } + + public RowBackground(ControlPointGroup controlGroup) { + this.controlGroup = controlGroup; RelativeSizeAxes = Axes.X; Height = 25; @@ -157,25 +163,63 @@ namespace osu.Game.Screens.Edit.Timing Alpha = 0, }, }; + + Action = () => selectedGroup.Value = controlGroup; } + private Color4 colourHover; + private Color4 colourSelected; + [BackgroundDependencyLoader] private void load(OsuColour colours) { - hoveredBackground.Colour = colours.Blue; + hoveredBackground.Colour = colourHover = colours.BlueDarker; + colourSelected = colours.YellowDarker; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedGroup.BindValueChanged(group => { Selected = controlGroup == group.NewValue; }); + } + + private bool selected; + + protected bool Selected + { + get => selected; + set + { + if (value == selected) + return; + + selected = value; + updateState(); + } } protected override bool OnHover(HoverEvent e) { - hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + updateState(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + updateState(); base.OnHoverLost(e); } + + private void updateState() + { + hoveredBackground.FadeColour(selected ? colourSelected : colourHover, 450, Easing.OutQuint); + + if (selected || IsHovered) + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + else + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + } } } } From de69665a4652f2f0595bb612c3c53570348522df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Oct 2019 14:17:59 +0900 Subject: [PATCH 18/77] Reduce horizontal spacing of attributes --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index df1f6daf4e..8d5774cd09 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Edit.Timing Direction = FillDirection.Horizontal, ChildrenEnumerable = group.ControlPoints.Select(createAttribute).Where(c => c != null), Padding = new MarginPadding(10), - Spacing = new Vector2(10) + Spacing = new Vector2(2) }, }; From 0fba272e78dc1501847d8a97c8a38a69e126b695 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Oct 2019 15:19:36 +0900 Subject: [PATCH 19/77] Add the ability to add new ControlPoint types to existing groups --- .../Visual/Editor/TestSceneTimingScreen.cs | 5 + .../ControlPoints/ControlPointGroup.cs | 6 +- .../Edit/Timing/ControlPointSettings.cs | 190 ------------------ .../Screens/Edit/Timing/ControlPointTable.cs | 64 ++++-- .../Screens/Edit/Timing/DifficultySection.cs | 37 ++++ osu.Game/Screens/Edit/Timing/EffectSection.cs | 44 ++++ osu.Game/Screens/Edit/Timing/SampleSection.cs | 44 ++++ osu.Game/Screens/Edit/Timing/Section.cs | 126 ++++++++++++ osu.Game/Screens/Edit/Timing/TimingSection.cs | 44 ++++ 9 files changed, 346 insertions(+), 214 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/DifficultySection.cs create mode 100644 osu.Game/Screens/Edit/Timing/EffectSection.cs create mode 100644 osu.Game/Screens/Edit/Timing/SampleSection.cs create mode 100644 osu.Game/Screens/Edit/Timing/Section.cs create mode 100644 osu.Game/Screens/Edit/Timing/TimingSection.cs diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs index 3e227169e0..121853d8d0 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -17,6 +17,11 @@ namespace osu.Game.Tests.Visual.Editor { typeof(ControlPointTable), typeof(ControlPointSettings), + typeof(Section<>), + typeof(TimingSection), + typeof(EffectSection), + typeof(SampleSection), + typeof(DifficultySection), typeof(RowAttribute) }; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs index d57baf25be..cb73ce884e 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; namespace osu.Game.Beatmaps.ControlPoints { @@ -17,9 +17,9 @@ namespace osu.Game.Beatmaps.ControlPoints /// public double Time { get; } - public IReadOnlyList ControlPoints => controlPoints; + public IBindableList ControlPoints => controlPoints; - private readonly List controlPoints = new List(); + private readonly BindableList controlPoints = new BindableList(); public ControlPointGroup(double time) { diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 470750b860..e1182d9fa4 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -2,17 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Edit.Timing { @@ -51,190 +46,5 @@ namespace osu.Game.Screens.Edit.Timing new SampleSection(), new EffectSection(), }; - - private class TimingSection : Section - { - private OsuSpriteText bpm; - private OsuSpriteText timeSignature; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - bpm = new OsuSpriteText(), - timeSignature = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; - timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; - }); - } - } - - private class DifficultySection : Section - { - private OsuSpriteText multiplier; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - multiplier = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); - } - } - - private class SampleSection : Section - { - private OsuSpriteText bank; - private OsuSpriteText volume; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - bank = new OsuSpriteText(), - volume = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bank.Text = $"Bank: {point.NewValue?.SampleBank}"; - volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; - }); - } - } - - private class EffectSection : Section - { - private OsuSpriteText kiai; - private OsuSpriteText omitBarLine; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - kiai = new OsuSpriteText(), - omitBarLine = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; - omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; - }); - } - } - - private class Section : CompositeDrawable - where T : ControlPoint - { - private OsuCheckbox checkbox; - private Container content; - - protected FillFlowContainer Flow { get; private set; } - - protected Bindable ControlPoint { get; } = new Bindable(); - - private const float header_height = 20; - - [Resolved] - private Bindable selectedPoints { get; set; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - RelativeSizeAxes = Axes.X; - AutoSizeDuration = 200; - AutoSizeEasing = Easing.OutQuint; - AutoSizeAxes = Axes.Y; - - Masking = true; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = colours.Gray1, - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.X, - Height = header_height, - Children = new Drawable[] - { - checkbox = new OsuCheckbox - { - LabelText = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) - } - } - }, - content = new Container - { - Y = header_height, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - Colour = colours.Gray2, - RelativeSizeAxes = Axes.Both, - }, - Flow = new FillFlowContainer - { - Padding = new MarginPadding(10), - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - }, - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - selectedPoints.BindValueChanged(points => - { - ControlPoint.Value = points.NewValue?.ControlPoints.OfType().FirstOrDefault(); - - checkbox.Current.Value = ControlPoint.Value != null; - }, true); - - checkbox.Current.BindValueChanged(selected => { content.BypassAutoSizeAxes = selected.NewValue ? Axes.None : Axes.Y; }, true); - } - } } } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 8d5774cd09..9c11443199 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -92,35 +92,57 @@ namespace osu.Game.Screens.Edit.Timing Text = $"{group.Time:n0}ms", Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - ChildrenEnumerable = group.ControlPoints.Select(createAttribute).Where(c => c != null), - Padding = new MarginPadding(10), - Spacing = new Vector2(2) - }, + new ControlGroupAttributes(group), }; - private Drawable createAttribute(ControlPoint controlPoint) + private class ControlGroupAttributes : CompositeDrawable { - switch (controlPoint) + private readonly IBindableList controlPoints; + + private readonly FillFlowContainer fill; + + public ControlGroupAttributes(ControlPointGroup group) { - case TimingControlPoint timing: - return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + InternalChild = fill = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(10), + Spacing = new Vector2(2) + }; - case DifficultyControlPoint difficulty: + controlPoints = group.ControlPoints.GetBoundCopy(); + controlPoints.ItemsAdded += _ => createChildren(); + controlPoints.ItemsRemoved += _ => createChildren(); - return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); - - case EffectControlPoint effect: - return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); - - case SampleControlPoint sample: - return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + createChildren(); } - return null; + private void createChildren() + { + fill.ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null); + } + + private Drawable createAttribute(ControlPoint controlPoint) + { + switch (controlPoint) + { + case TimingControlPoint timing: + return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + + case DifficultyControlPoint difficulty: + + return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); + + case EffectControlPoint effect: + return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + + case SampleControlPoint sample: + return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + } + + return null; + } } protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs new file mode 100644 index 0000000000..150c11f5ee --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -0,0 +1,37 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class DifficultySection : Section + { + private OsuSpriteText multiplier; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + multiplier = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); + } + + protected override DifficultyControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.DifficultyPointAt(SelectedGroup.Value.Time); + + return new DifficultyControlPoint + { + SpeedMultiplier = reference.SpeedMultiplier, + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs new file mode 100644 index 0000000000..ff8817147a --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -0,0 +1,44 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class EffectSection : Section + { + private OsuSpriteText kiai; + private OsuSpriteText omitBarLine; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + kiai = new OsuSpriteText(), + omitBarLine = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; + omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; + }); + } + + protected override EffectControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(SelectedGroup.Value.Time); + + return new EffectControlPoint + { + KiaiMode = reference.KiaiMode, + OmitFirstBarLine = reference.OmitFirstBarLine + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs new file mode 100644 index 0000000000..0d6bc74057 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -0,0 +1,44 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class SampleSection : Section + { + private OsuSpriteText bank; + private OsuSpriteText volume; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bank = new OsuSpriteText(), + volume = new OsuSpriteText(), + }); + } + + protected override SampleControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time); + + return new SampleControlPoint + { + SampleBank = reference.SampleBank, + SampleVolume = reference.SampleVolume, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bank.Text = $"Bank: {point.NewValue?.SampleBank}"; + volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; + }); + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs new file mode 100644 index 0000000000..c6140ff497 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -0,0 +1,126 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Timing +{ + internal abstract class Section : CompositeDrawable + where T : ControlPoint + { + private OsuCheckbox checkbox; + private Container content; + + protected FillFlowContainer Flow { get; private set; } + + protected Bindable ControlPoint { get; } = new Bindable(); + + private const float header_height = 20; + + [Resolved] + protected IBindable Beatmap { get; private set; } + + [Resolved] + protected Bindable SelectedGroup { get; private set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.X; + AutoSizeDuration = 200; + AutoSizeEasing = Easing.OutQuint; + AutoSizeAxes = Axes.Y; + + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Gray1, + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = header_height, + Children = new Drawable[] + { + checkbox = new OsuCheckbox + { + LabelText = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) + } + } + }, + content = new Container + { + Y = header_height, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray2, + RelativeSizeAxes = Axes.Both, + }, + Flow = new FillFlowContainer + { + Padding = new MarginPadding(10), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + checkbox.Current.BindValueChanged(selected => + { + if (selected.NewValue) + { + if (SelectedGroup.Value == null) + { + checkbox.Current.Value = false; + return; + } + + if (ControlPoint.Value == null) + SelectedGroup.Value.Add(ControlPoint.Value = CreatePoint()); + } + else + { + if (ControlPoint.Value != null) + { + SelectedGroup.Value.Remove(ControlPoint.Value); + ControlPoint.Value = null; + } + } + + content.BypassAutoSizeAxes = selected.NewValue ? Axes.None : Axes.Y; + }, true); + + SelectedGroup.BindValueChanged(points => + { + ControlPoint.Value = points.NewValue?.ControlPoints.OfType().FirstOrDefault(); + checkbox.Current.Value = ControlPoint.Value != null; + }, true); + } + + protected abstract T CreatePoint(); + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs new file mode 100644 index 0000000000..dcbb6f8bbf --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -0,0 +1,44 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class TimingSection : Section + { + private OsuSpriteText bpm; + private OsuSpriteText timeSignature; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bpm = new OsuSpriteText(), + timeSignature = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; + timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; + }); + } + + protected override TimingControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(SelectedGroup.Value.Time); + + return new TimingControlPoint + { + BeatLength = reference.BeatLength, + TimeSignature = reference.TimeSignature + }; + } + } +} From 73369ae6130680101aeaf6216307b13e9b76f564 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Oct 2019 16:13:24 +0900 Subject: [PATCH 20/77] Add the ability to add/remove groups --- .../ControlPoints/ControlPointInfo.cs | 19 +++++++++++-- .../Screens/Edit/Timing/ControlPointTable.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 27 ++++++++++++++----- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index e165adedea..4f83e14eaf 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Framework.Lists; namespace osu.Game.Beatmaps.ControlPoints @@ -16,9 +17,9 @@ namespace osu.Game.Beatmaps.ControlPoints /// Control point groups. /// [JsonProperty] - public IReadOnlyList Groups => groups; + public IBindableList Groups => groups; - private readonly SortedList groups = new SortedList(Comparer.Default); + private readonly BindableList groups = new BindableList(); /// /// All timing points. @@ -294,5 +295,19 @@ namespace osu.Game.Beatmaps.ControlPoints samplePoints.Clear(); effectPoints.Clear(); } + + public ControlPointGroup CreateGroup(double time) + { + var newGroup = new ControlPointGroup(time); + + int i = groups.BinarySearch(newGroup); + if (i < 0) i = ~i; + + groups.Insert(i, newGroup); + + return newGroup; + } + + public void RemoveGroup(ControlPointGroup group) => groups.Remove(group); } } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 9c11443199..729e631d87 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -203,7 +203,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(group => { Selected = controlGroup == group.NewValue; }); + selectedGroup.BindValueChanged(group => { Selected = controlGroup == group.NewValue; }, true); } private bool selected; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index a9fd038829..2b1c8f8497 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -52,12 +52,18 @@ namespace osu.Game.Screens.Edit.Timing public class ControlPointList : CompositeDrawable { private OsuButton deleteButton; + private ControlPointTable table; + + private IBindableList controlGroups; + + [Resolved] + protected IFrameBasedClock EditorClock { get; private set; } [Resolved] protected IBindable Beatmap { get; private set; } [Resolved] - private Bindable selectedPoints { get; set; } + private Bindable selectedGroup { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -74,10 +80,7 @@ namespace osu.Game.Screens.Edit.Timing new OsuScrollContainer { RelativeSizeAxes = Axes.Both, - Child = new ControlPointTable - { - ControlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups - } + Child = table = new ControlPointTable(), }, new FillFlowContainer { @@ -114,15 +117,27 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedPoints.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); + selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); + + controlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlGroups.ItemsAdded += _ => createContent(); + controlGroups.ItemsRemoved += _ => createContent(); + createContent(); } + private void createContent() => table.ControlGroups = controlGroups; + private void delete() { + if (selectedGroup.Value == null) + return; + + Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value); } private void addNew() { + selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.CreateGroup(EditorClock.CurrentTime); } } } From 81b5d7b79fd668463a7d1c616a66da5a77ec4dee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Oct 2019 16:30:05 +0900 Subject: [PATCH 21/77] Select another group after deleting selected --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 2b1c8f8497..2e9164c60f 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -57,7 +58,7 @@ namespace osu.Game.Screens.Edit.Timing private IBindableList controlGroups; [Resolved] - protected IFrameBasedClock EditorClock { get; private set; } + private IFrameBasedClock clock { get; set; } [Resolved] protected IBindable Beatmap { get; private set; } @@ -133,11 +134,13 @@ namespace osu.Game.Screens.Edit.Timing return; Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value); + + selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.Groups.FirstOrDefault(g => g.Time >= clock.CurrentTime); } private void addNew() { - selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.CreateGroup(EditorClock.CurrentTime); + selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.CreateGroup(clock.CurrentTime); } } } From 0179586f7811a1d71209d61cb676a0dd856cb7d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Oct 2019 16:31:23 +0900 Subject: [PATCH 22/77] Disallow inserting a group if one already exists with the current time value --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 4f83e14eaf..6418ef984b 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -301,9 +301,11 @@ namespace osu.Game.Beatmaps.ControlPoints var newGroup = new ControlPointGroup(time); int i = groups.BinarySearch(newGroup); - if (i < 0) i = ~i; - groups.Insert(i, newGroup); + if (i > 0) + return groups[i]; + + groups.Insert(~i, newGroup); return newGroup; } From 29e20bc8d2796d8ac5cb3f8a53dc684ec67263d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 10:45:11 +0900 Subject: [PATCH 23/77] Add xmldoc and combine GroupAt / CreateGroup --- .../ControlPoints/ControlPointInfo.cs | 174 ++++++++---------- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 2 +- 2 files changed, 79 insertions(+), 97 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 6418ef984b..c3e2b469ae 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps.ControlPoints public class ControlPointInfo { /// - /// Control point groups. + /// All control points grouped by time. /// [JsonProperty] public IBindableList Groups => groups; @@ -86,28 +86,6 @@ namespace osu.Game.Beatmaps.ControlPoints /// The timing control point. public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null); - /// - /// Finds the closest of the same type as that is active at . - /// - /// The time to find the timing control point at. - /// A reference point to infer type. - /// The timing control point. - public ControlPoint SimilarPointAt(double time, ControlPoint referencePoint) - { - switch (referencePoint) - { - case TimingControlPoint _: return binarySearch(TimingPoints, time); - - case EffectControlPoint _: return binarySearch(EffectPoints, time); - - case SampleControlPoint _: return binarySearch(SamplePoints, time); - - case DifficultyControlPoint _: return binarySearch(DifficultyPoints, time); - } - - return null; - } - /// /// Finds the maximum BPM represented by any timing control point. /// @@ -129,6 +107,62 @@ namespace osu.Game.Beatmaps.ControlPoints public double BPMMode => 60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + /// + /// Remove all s and return to a pristine state. + /// + public void Clear() + { + groups.Clear(); + timingPoints.Clear(); + difficultyPoints.Clear(); + samplePoints.Clear(); + effectPoints.Clear(); + } + + /// + /// Add a new . Note that the provided control point may not be added if the correct state is already present at the provided time. + /// + /// The time at which the control point should be added. + /// The control point to add. + /// Whether the control point was added. + public bool Add(double time, ControlPoint controlPoint) + { + if (checkAlreadyExisting(time, controlPoint)) + return false; + + GroupAt(time, true).Add(controlPoint); + return true; + } + + public ControlPointGroup GroupAt(double time, bool addIfNotExisting = false) + { + var newGroup = new ControlPointGroup(time); + + int i = groups.BinarySearch(newGroup); + + if (i >= 0) + return groups[i]; + + if (addIfNotExisting) + { + newGroup.ItemAdded += groupItemAdded; + newGroup.ItemRemoved += groupItemRemoved; + + groups.Insert(~i, newGroup); + return newGroup; + } + + return null; + } + + public void RemoveGroup(ControlPointGroup group) + { + group.ItemAdded -= groupItemAdded; + group.ItemRemoved -= groupItemRemoved; + + groups.Remove(group); + } + /// /// Binary searches one of the control point lists to find the active control point at . /// Includes logic for returning a specific point when no matching point is found. @@ -183,33 +217,6 @@ namespace osu.Game.Beatmaps.ControlPoints return list[l - 1]; } - public void Add(double time, ControlPoint newPoint, bool force = false) - { - if (!force && checkAlreadyExisting(time, newPoint)) - return; - - GroupAt(time, true).Add(newPoint); - } - - public ControlPointGroup GroupAt(double time, bool createIfNotExisting = false) - { - var existing = Groups.FirstOrDefault(g => g.Time == time); - - if (existing != null) - return existing; - - if (createIfNotExisting) - { - var newGroup = new ControlPointGroup(time); - newGroup.ItemAdded += groupItemAdded; - newGroup.ItemRemoved += groupItemRemoved; - groups.Add(newGroup); - return newGroup; - } - - return null; - } - /// /// Check whether should be added. /// @@ -243,31 +250,9 @@ namespace osu.Game.Beatmaps.ControlPoints return existing?.EquivalentTo(newPoint) == true; } - private void groupItemRemoved(ControlPoint obj) + private void groupItemAdded(ControlPoint controlPoint) { - switch (obj) - { - case TimingControlPoint typed: - timingPoints.Remove(typed); - break; - - case EffectControlPoint typed: - effectPoints.Remove(typed); - break; - - case SampleControlPoint typed: - samplePoints.Remove(typed); - break; - - case DifficultyControlPoint typed: - difficultyPoints.Remove(typed); - break; - } - } - - private void groupItemAdded(ControlPoint obj) - { - switch (obj) + switch (controlPoint) { case TimingControlPoint typed: timingPoints.Add(typed); @@ -287,29 +272,26 @@ namespace osu.Game.Beatmaps.ControlPoints } } - public void Clear() + private void groupItemRemoved(ControlPoint controlPoint) { - groups.Clear(); - timingPoints.Clear(); - difficultyPoints.Clear(); - samplePoints.Clear(); - effectPoints.Clear(); + switch (controlPoint) + { + case TimingControlPoint typed: + timingPoints.Remove(typed); + break; + + case EffectControlPoint typed: + effectPoints.Remove(typed); + break; + + case SampleControlPoint typed: + samplePoints.Remove(typed); + break; + + case DifficultyControlPoint typed: + difficultyPoints.Remove(typed); + break; + } } - - public ControlPointGroup CreateGroup(double time) - { - var newGroup = new ControlPointGroup(time); - - int i = groups.BinarySearch(newGroup); - - if (i > 0) - return groups[i]; - - groups.Insert(~i, newGroup); - - return newGroup; - } - - public void RemoveGroup(ControlPointGroup group) => groups.Remove(group); } } diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 2e9164c60f..6ae01e056f 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -140,7 +140,7 @@ namespace osu.Game.Screens.Edit.Timing private void addNew() { - selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.CreateGroup(clock.CurrentTime); + selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true); } } } From cd4b7c04e9a18668680016c9b45ee1d4c3f1976d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 11:34:41 +0900 Subject: [PATCH 24/77] Add test coverage --- .../NonVisual/ControlPointInfoTest.cs | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/ControlPointInfoTest.cs diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs new file mode 100644 index 0000000000..a51b90851c --- /dev/null +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -0,0 +1,227 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class ControlPointInfoTest + { + [Test] + public void TestAdd() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); + cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); + + Assert.That(cpi.Groups.Count, Is.EqualTo(2)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2)); + } + + [Test] + public void TestAddRedundantTiming() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); // is *not* redundant, special exception for first timing point. + cpi.Add(1000, new TimingControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddRedundantDifficulty() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new DifficultyControlPoint()); // is redundant + cpi.Add(1000, new DifficultyControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); + + cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddRedundantSample() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new SampleControlPoint()); // is redundant + cpi.Add(1000, new SampleControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); + + cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddRedundantEffect() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new EffectControlPoint()); // is redundant + cpi.Add(1000, new EffectControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); + + cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.EffectPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + var group2 = cpi.GroupAt(1000, true); + + Assert.That(group, Is.EqualTo(group2)); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + } + + [Test] + public void TestGroupAtLookupOnly() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(5000, true); + Assert.That(group, Is.Not.Null); + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.GroupAt(1000), Is.Null); + Assert.That(cpi.GroupAt(5000), Is.Not.Null); + } + + [Test] + public void TestAddRemoveGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + cpi.RemoveGroup(group); + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + } + + [Test] + public void TestAddControlPointToGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + // usually redundant, but adding to group forces it to be added + group.Add(new DifficultyControlPoint()); + + Assert.That(group.ControlPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); + } + + [Test] + public void TestAddDuplicateControlPointToGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + group.Add(new DifficultyControlPoint()); + group.Add(new DifficultyControlPoint { SpeedMultiplier = 2 }); + + Assert.That(group.ControlPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.First().SpeedMultiplier, Is.EqualTo(2)); + } + + [Test] + public void TestRemoveControlPointFromGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + var difficultyPoint = new DifficultyControlPoint(); + + group.Add(difficultyPoint); + group.Remove(difficultyPoint); + + Assert.That(group.ControlPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0)); + } + + [Test] + public void TestOrdering() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); + cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); + cpi.Add(10000, new TimingControlPoint { BeatLength = 200 }); + cpi.Add(5000, new TimingControlPoint { BeatLength = 100 }); + cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 }); + cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 }); + cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 }); + cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true }); + + Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(8)); + + Assert.That(cpi.Groups, Is.Ordered.Ascending.By(nameof(ControlPointGroup.Time))); + + Assert.That(cpi.AllControlPoints, Is.Ordered.Ascending.By(nameof(ControlPoint.Time))); + Assert.That(cpi.TimingPoints, Is.Ordered.Ascending.By(nameof(ControlPoint.Time))); + } + + [Test] + public void TestClear() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); + cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); + cpi.Add(10000, new TimingControlPoint { BeatLength = 200 }); + cpi.Add(5000, new TimingControlPoint { BeatLength = 100 }); + cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 }); + cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 }); + cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 }); + cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true }); + + cpi.Clear(); + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0)); + } + } +} From 43ad4a3a3c978b4305a48576ec3769f91d7f929e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 12:31:38 +0900 Subject: [PATCH 25/77] Tidy up string output --- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 5 +++++ osu.Game/Screens/Edit/Timing/DifficultySection.cs | 2 +- osu.Game/Screens/Edit/Timing/EffectSection.cs | 4 ++-- osu.Game/Screens/Edit/Timing/SampleSection.cs | 4 ++-- osu.Game/Screens/Edit/Timing/TimingSection.cs | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index f8c84c79dd..e3451892bf 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -24,6 +24,11 @@ namespace osu.Game.Beatmaps.ControlPoints set => beatLength = MathHelper.Clamp(value, 6, 60000); } + /// + /// The BPM at this control point. + /// + public double BPM => 60000 / BeatLength; + private double beatLength = DEFAULT_BEAT_LENGTH; public override bool EquivalentTo(ControlPoint other) => diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index 150c11f5ee..df61300451 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); + ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier::0.##}x"; }); } protected override DifficultyControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index ff8817147a..57bee44177 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -25,8 +25,8 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; - omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; + kiai.Text = $"Kiai: {(point.NewValue?.KiaiMode == true ? "on" : "off")}"; + omitBarLine.Text = $"Skip Bar Line: {(point.NewValue?.OmitFirstBarLine == true ? "on" : "off")}"; }); } diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index 0d6bc74057..d623ad08ae 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -37,8 +37,8 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { bank.Text = $"Bank: {point.NewValue?.SampleBank}"; - volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; + volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; }); } } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index dcbb6f8bbf..4f2cf82a65 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; + bpm.Text = $"BPM: {point.NewValue?.BPM:0.##}"; timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; }); } From ee5591d7d502244c61ca8cb2ee515eccc9403086 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 12:42:17 +0900 Subject: [PATCH 26/77] Add missing license headers --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 3 +++ osu.Game/Screens/Edit/Timing/EffectSection.cs | 3 +++ osu.Game/Screens/Edit/Timing/SampleSection.cs | 3 +++ osu.Game/Screens/Edit/Timing/TimingSection.cs | 3 +++ 4 files changed, 12 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index df61300451..bd363c3158 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index 57bee44177..bafdfd0b0d 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index d623ad08ae..0477ad4e78 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 4f2cf82a65..8609da4c4d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; From 9acdcc912924ecc83df4bddd80ff59347cdfef94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 14:44:45 +0900 Subject: [PATCH 27/77] Make all control point attributes bindable Properties are left intact for compatibility reasons. --- .../ControlPoints/DifficultyControlPoint.cs | 19 +++++++---- .../ControlPoints/EffectControlPoint.cs | 26 +++++++++++++-- .../ControlPoints/SampleControlPoint.cs | 27 ++++++++++++++-- .../ControlPoints/TimingControlPoint.cs | 32 ++++++++++++++----- ...egacyDifficultyCalculatorBeatmapDecoder.cs | 6 +++- 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 42651fd0ca..7726eb0245 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,24 +1,31 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; +using osu.Framework.Bindables; namespace osu.Game.Beatmaps.ControlPoints { public class DifficultyControlPoint : ControlPoint { + /// + /// The speed multiplier at this control point. + /// + public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1) + { + MinValue = 0.1, + MaxValue = 10 + }; + /// /// The speed multiplier at this control point. /// public double SpeedMultiplier { - get => speedMultiplier; - set => speedMultiplier = MathHelper.Clamp(value, 0.1, 10); + get => SpeedMultiplierBindable.Value; + set => SpeedMultiplierBindable.Value = value; } - private double speedMultiplier = 1; - public override bool EquivalentTo(ControlPoint other) => - other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(speedMultiplier); + other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(SpeedMultiplier); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 928f2a51ad..369b93ff3d 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -1,19 +1,39 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; + namespace osu.Game.Beatmaps.ControlPoints { public class EffectControlPoint : ControlPoint { /// - /// Whether this control point enables Kiai mode. + /// Whether the first bar line of this control point is ignored. /// - public bool KiaiMode; + public readonly BindableBool OmitFirstBarLineBindable = new BindableBool(); /// /// Whether the first bar line of this control point is ignored. /// - public bool OmitFirstBarLine; + public bool OmitFirstBarLine + { + get => OmitFirstBarLineBindable.Value; + set => OmitFirstBarLineBindable.Value = value; + } + + /// + /// Whether this control point enables Kiai mode. + /// + public readonly BindableBool KiaiModeBindable = new BindableBool(); + + /// + /// Whether this control point enables Kiai mode. + /// + public bool KiaiMode + { + get => KiaiModeBindable.Value; + set => KiaiModeBindable.Value = value; + } public override bool EquivalentTo(ControlPoint other) => other is EffectControlPoint otherTyped && diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 07f5aa6c90..37607c716e 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Game.Audio; namespace osu.Game.Beatmaps.ControlPoints @@ -12,12 +13,34 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The default sample bank at this control point. /// - public string SampleBank = DEFAULT_BANK; + public readonly Bindable SampleBankBindable = new Bindable(DEFAULT_BANK); + + /// + /// The speed multiplier at this control point. + /// + public string SampleBank + { + get => SampleBankBindable.Value; + set => SampleBankBindable.Value = value; + } + + /// + /// The default sample bank at this control point. + /// + public readonly BindableInt SampleVolumeBindable = new BindableInt(100) + { + MinValue = 0, + MaxValue = 100 + }; /// /// The default sample volume at this control point. /// - public int SampleVolume = 100; + public int SampleVolume + { + get => SampleVolumeBindable.Value; + set => SampleVolumeBindable.Value = value; + } /// /// Create a SampleInfo based on the sample settings in this control point. diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index f8c84c79dd..d25d08c5bd 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; +using osu.Framework.Bindables; using osu.Game.Beatmaps.Timing; namespace osu.Game.Beatmaps.ControlPoints @@ -11,23 +11,39 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time signature at this control point. /// - public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple; + public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignatures.SimpleQuadruple); + + /// + /// The time signature at this control point. + /// + public TimeSignatures TimeSignature + { + get => TimeSignatureBindable.Value; + set => TimeSignatureBindable.Value = value; + } public const double DEFAULT_BEAT_LENGTH = 1000; /// /// The beat length at this control point. /// - public virtual double BeatLength + public readonly BindableDouble BeatLengthBindable = new BindableDouble(DEFAULT_BEAT_LENGTH) { - get => beatLength; - set => beatLength = MathHelper.Clamp(value, 6, 60000); - } + MinValue = 6, + MaxValue = 60000 + }; - private double beatLength = DEFAULT_BEAT_LENGTH; + /// + /// The beat length at this control point. + /// + public double BeatLength + { + get => BeatLengthBindable.Value; + set => BeatLengthBindable.Value = value; + } public override bool EquivalentTo(ControlPoint other) => other is TimingControlPoint otherTyped - && TimeSignature == otherTyped.TimeSignature && beatLength.Equals(otherTyped.beatLength); + && TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs index 238187bf8f..a16d391b99 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs @@ -32,7 +32,11 @@ namespace osu.Game.Beatmaps.Formats private class LegacyDifficultyCalculatorControlPoint : TimingControlPoint { - public override double BeatLength { get; set; } = DEFAULT_BEAT_LENGTH; + public LegacyDifficultyCalculatorControlPoint() + { + BeatLengthBindable.MinValue = double.MinValue; + BeatLengthBindable.MaxValue = double.MaxValue; + } } } } From d33b31f0c5100850be2ae873ca46ab4fdb71fffd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 15:33:08 +0900 Subject: [PATCH 28/77] Expose Current bindable in LabelledComponents Adds a `LabelledDrawable` class for usages where bindables are not present. --- .../TestSceneLabelledComponent.cs | 14 +- .../UserInterface/TestSceneLabelledTextBox.cs | 3 +- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- .../UserInterfaceV2/LabelledComponent.cs | 124 ++-------------- .../UserInterfaceV2/LabelledDrawable.cs | 132 ++++++++++++++++++ .../UserInterfaceV2/LabelledSwitchButton.cs | 2 +- .../UserInterfaceV2/LabelledTextBox.cs | 2 +- 7 files changed, 151 insertions(+), 128 deletions(-) create mode 100644 osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs index 700adad9cb..8179f92ffc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs @@ -11,7 +11,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneLabelledComponent : OsuTestScene + public class TestSceneLabelledDrawable : OsuTestScene { [TestCase(false)] [TestCase(true)] @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent component; + LabelledDrawable component; Child = new Container { @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Width = 500, AutoSizeAxes = Axes.Y, - Child = component = padded ? (LabelledComponent)new PaddedLabelledComponent() : new NonPaddedLabelledComponent(), + Child = component = padded ? (LabelledDrawable)new PaddedLabelledDrawable() : new NonPaddedLabelledDrawable(), }; component.Label = "a sample component"; @@ -41,9 +41,9 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private class PaddedLabelledComponent : LabelledComponent + private class PaddedLabelledDrawable : LabelledDrawable { - public PaddedLabelledComponent() + public PaddedLabelledDrawable() : base(true) { } @@ -57,9 +57,9 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private class NonPaddedLabelledComponent : LabelledComponent + private class NonPaddedLabelledDrawable : LabelledDrawable { - public NonPaddedLabelledComponent() + public NonPaddedLabelledDrawable() : base(false) { } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs index 53a2bfabbc..8208b55952 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs @@ -7,7 +7,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Tests.Visual.UserInterface @@ -28,7 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent component; + LabelledTextBox component; Child = new Container { diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 091a837745..a67daa2756 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tournament.Screens }; } - private class ActionableInfo : LabelledComponent + private class ActionableInfo : LabelledDrawable { private OsuButton button; diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs index 2e659825b7..1819b36667 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs @@ -1,132 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; -using osuTK; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 { - public abstract class LabelledComponent : CompositeDrawable - where T : Drawable + public abstract class LabelledComponent : LabelledDrawable, IHasCurrentValue + where T : Drawable, IHasCurrentValue { - protected const float CONTENT_PADDING_VERTICAL = 10; - protected const float CONTENT_PADDING_HORIZONTAL = 15; - protected const float CORNER_RADIUS = 15; - - /// - /// The component that is being displayed. - /// - protected readonly T Component; - - private readonly OsuTextFlowContainer labelText; - private readonly OsuTextFlowContainer descriptionText; - - /// - /// Creates a new . - /// - /// Whether the component should be padded or should be expanded to the bounds of this . protected LabelledComponent(bool padded) + : base(padded) { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - CornerRadius = CORNER_RADIUS; - Masking = true; - - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("1c2125"), - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = padded - ? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL } - : new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL }, - Spacing = new Vector2(0, 12), - Children = new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] - { - new Drawable[] - { - labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold)) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 20 } - }, - new Container - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = Component = CreateComponent().With(d => - { - d.Anchor = Anchor.CentreRight; - d.Origin = Anchor.CentreRight; - }) - } - }, - }, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, - ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }, - descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL }, - Alpha = 0, - } - } - } - }; } - [BackgroundDependencyLoader] - private void load(OsuColour osuColour) + public Bindable Current { - descriptionText.Colour = osuColour.Yellow; + get => Component.Current; + set => Component.Current = value; } - - public string Label - { - set => labelText.Text = value; - } - - public string Description - { - set - { - descriptionText.Text = value; - - if (!string.IsNullOrEmpty(value)) - descriptionText.Show(); - else - descriptionText.Hide(); - } - } - - /// - /// Creates the component that should be displayed. - /// - /// The component. - protected abstract T CreateComponent(); } } diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs new file mode 100644 index 0000000000..f44bd72aee --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -0,0 +1,132 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public abstract class LabelledDrawable : CompositeDrawable + where T : Drawable + { + protected const float CONTENT_PADDING_VERTICAL = 10; + protected const float CONTENT_PADDING_HORIZONTAL = 15; + protected const float CORNER_RADIUS = 15; + + /// + /// The component that is being displayed. + /// + protected readonly T Component; + + private readonly OsuTextFlowContainer labelText; + private readonly OsuTextFlowContainer descriptionText; + + /// + /// Creates a new . + /// + /// Whether the component should be padded or should be expanded to the bounds of this . + protected LabelledDrawable(bool padded) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + CornerRadius = CORNER_RADIUS; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("1c2125"), + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = padded + ? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL } + : new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL }, + Spacing = new Vector2(0, 12), + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + new Drawable[] + { + labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 20 } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = Component = CreateComponent().With(d => + { + d.Anchor = Anchor.CentreRight; + d.Origin = Anchor.CentreRight; + }) + } + }, + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }, + descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL }, + Alpha = 0, + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour osuColour) + { + descriptionText.Colour = osuColour.Yellow; + } + + public string Label + { + set => labelText.Text = value; + } + + public string Description + { + set + { + descriptionText.Text = value; + + if (!string.IsNullOrEmpty(value)) + descriptionText.Show(); + else + descriptionText.Hide(); + } + } + + /// + /// Creates the component that should be displayed. + /// + /// The component. + protected abstract T CreateComponent(); + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs index c973f1d13e..c374d80830 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs @@ -3,7 +3,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { - public class LabelledSwitchButton : LabelledComponent + public class LabelledSwitchButton : LabelledComponent { public LabelledSwitchButton() : base(true) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 50d2a14482..2cbe095d0b 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 { - public class LabelledTextBox : LabelledComponent + public class LabelledTextBox : LabelledComponent { public event TextBox.OnCommitHandler OnCommit; From 0a11cbf656aef28930ad01e1306ed907958d1832 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 16:20:33 +0900 Subject: [PATCH 29/77] Make OsuButton correctly block hover events --- osu.Game/Graphics/UserInterface/OsuButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index c1810800a0..4124d2ad58 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -59,7 +59,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { hover.FadeIn(200); - return base.OnHover(e); + return true; } protected override void OnHoverLost(HoverLostEvent e) From 9c3e54909ca56882d230d4b4ebbd39d36fda96bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 16:20:54 +0900 Subject: [PATCH 30/77] Ensure tooltips of RowAttributes are up-to-date --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 8 ++++---- osu.Game/Screens/Edit/Timing/RowAttribute.cs | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 729e631d87..96e3ab48f2 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -128,17 +128,17 @@ namespace osu.Game.Screens.Edit.Timing switch (controlPoint) { case TimingControlPoint timing: - return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + return new RowAttribute("timing", () => $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); case DifficultyControlPoint difficulty: - return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); + return new RowAttribute("difficulty", () => $"{difficulty.SpeedMultiplier:n2}x"); case EffectControlPoint effect: - return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + return new RowAttribute("effect", () => $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); case SampleControlPoint sample: - return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + return new RowAttribute("sample", () => $"{sample.SampleBank} {sample.SampleVolume}%"); } return null; diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs index 8716382142..be8f693683 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -14,9 +15,9 @@ namespace osu.Game.Screens.Edit.Timing public class RowAttribute : CompositeDrawable, IHasTooltip { private readonly string header; - private readonly string content; + private readonly Func content; - public RowAttribute(string header, string content) + public RowAttribute(string header, Func content) { this.header = header; this.content = content; @@ -54,6 +55,6 @@ namespace osu.Game.Screens.Edit.Timing }; } - public string TooltipText => content; + public string TooltipText => content(); } } From f761eddec754ef130297d12b4e383e9988d55de2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 16:21:14 +0900 Subject: [PATCH 31/77] Add default bindable values --- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 2 ++ osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 5 +++-- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 7726eb0245..8b21098a51 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -12,6 +12,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1) { + Precision = 0.1, + Default = 1, MinValue = 0.1, MaxValue = 10 }; diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 37607c716e..42865c686c 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The default sample bank at this control point. /// - public readonly Bindable SampleBankBindable = new Bindable(DEFAULT_BANK); + public readonly Bindable SampleBankBindable = new Bindable(DEFAULT_BANK) { Default = DEFAULT_BANK }; /// /// The speed multiplier at this control point. @@ -30,7 +30,8 @@ namespace osu.Game.Beatmaps.ControlPoints public readonly BindableInt SampleVolumeBindable = new BindableInt(100) { MinValue = 0, - MaxValue = 100 + MaxValue = 100, + Default = 100 }; /// diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index fb548e60aa..51b3377394 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time signature at this control point. /// - public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignatures.SimpleQuadruple); + public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignatures.SimpleQuadruple) { Default = TimeSignatures.SimpleQuadruple }; /// /// The time signature at this control point. @@ -29,6 +29,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// public readonly BindableDouble BeatLengthBindable = new BindableDouble(DEFAULT_BEAT_LENGTH) { + Default = DEFAULT_BEAT_LENGTH, MinValue = 6, MaxValue = 60000 }; From 522572eacefefa13ae0f4524402984ce141932f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 16:21:31 +0900 Subject: [PATCH 32/77] Add ability to adjust all control point attributes --- .../Screens/Edit/Timing/DifficultySection.cs | 20 +++++-- osu.Game/Screens/Edit/Timing/EffectSection.cs | 17 +++--- osu.Game/Screens/Edit/Timing/SampleSection.cs | 28 ++++++--- osu.Game/Screens/Edit/Timing/TimingSection.cs | 59 ++++++++++++++++--- 4 files changed, 97 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index bd363c3158..d3e1b2a84f 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -2,21 +2,27 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class DifficultySection : Section { - private OsuSpriteText multiplier; + private SettingsSlider multiplier; [BackgroundDependencyLoader] private void load() { Flow.AddRange(new[] { - multiplier = new OsuSpriteText(), + multiplier = new SettingsSlider + { + LabelText = "Speed Multiplier", + Bindable = new DifficultyControlPoint().SpeedMultiplierBindable, + RelativeSizeAxes = Axes.X, + } }); } @@ -24,7 +30,13 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier::0.##}x"; }); + ControlPoint.BindValueChanged(point => + { + if (point.NewValue != null) + { + multiplier.Bindable = point.NewValue.SpeedMultiplierBindable; + } + }, true); } protected override DifficultyControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index bafdfd0b0d..fcd733cf00 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -3,22 +3,22 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Screens.Edit.Timing { internal class EffectSection : Section { - private OsuSpriteText kiai; - private OsuSpriteText omitBarLine; + private LabelledSwitchButton kiai; + private LabelledSwitchButton omitBarLine; [BackgroundDependencyLoader] private void load() { Flow.AddRange(new[] { - kiai = new OsuSpriteText(), - omitBarLine = new OsuSpriteText(), + kiai = new LabelledSwitchButton { Label = "Kiai Time" }, + omitBarLine = new LabelledSwitchButton { Label = "Skip Bar Line" }, }); } @@ -28,8 +28,11 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - kiai.Text = $"Kiai: {(point.NewValue?.KiaiMode == true ? "on" : "off")}"; - omitBarLine.Text = $"Skip Bar Line: {(point.NewValue?.OmitFirstBarLine == true ? "on" : "off")}"; + if (point.NewValue != null) + { + kiai.Current = point.NewValue.KiaiModeBindable; + omitBarLine.Current = point.NewValue.OmitFirstBarLineBindable; + } }); } diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index 0477ad4e78..816f90d19e 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -2,23 +2,32 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class SampleSection : Section { - private OsuSpriteText bank; - private OsuSpriteText volume; + private LabelledTextBox bank; + private SettingsSlider volume; [BackgroundDependencyLoader] private void load() { - Flow.AddRange(new[] + Flow.AddRange(new Drawable[] { - bank = new OsuSpriteText(), - volume = new OsuSpriteText(), + bank = new LabelledTextBox + { + Label = "Bank Name", + }, + volume = new SettingsSlider + { + Bindable = new SampleControlPoint().SampleVolumeBindable, + LabelText = "Volume", + } }); } @@ -39,8 +48,11 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - bank.Text = $"Bank: {point.NewValue?.SampleBank}"; - volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; + if (point.NewValue != null) + { + bank.Current = point.NewValue.SampleBankBindable; + volume.Bindable = point.NewValue.SampleVolumeBindable; + } }); } } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 8609da4c4d..9aecbe9160 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -2,23 +2,33 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; +using osu.Game.Beatmaps.Timing; +using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class TimingSection : Section { - private OsuSpriteText bpm; - private OsuSpriteText timeSignature; + private SettingsSlider bpm; + private SettingsEnumDropdown timeSignature; [BackgroundDependencyLoader] private void load() { - Flow.AddRange(new[] + Flow.AddRange(new Drawable[] { - bpm = new OsuSpriteText(), - timeSignature = new OsuSpriteText(), + bpm = new BPMSlider + { + Bindable = new TimingControlPoint().BeatLengthBindable, + LabelText = "BPM", + }, + timeSignature = new SettingsEnumDropdown + { + LabelText = "Time Signature" + }, }); } @@ -28,8 +38,11 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - bpm.Text = $"BPM: {point.NewValue?.BPM:0.##}"; - timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; + if (point.NewValue != null) + { + bpm.Bindable = point.NewValue.BeatLengthBindable; + timeSignature.Bindable = point.NewValue.TimeSignatureBindable; + } }); } @@ -43,5 +56,35 @@ namespace osu.Game.Screens.Edit.Timing TimeSignature = reference.TimeSignature }; } + + private class BPMSlider : SettingsSlider + { + private readonly BindableDouble beatLengthBindable = new BindableDouble(); + + private BindableDouble bpmBindable; + + public override Bindable Bindable + { + get => base.Bindable; + set + { + // incoming will be beatlength + + beatLengthBindable.UnbindBindings(); + beatLengthBindable.BindTo(value); + + base.Bindable = bpmBindable = new BindableDouble(beatLengthToBpm(beatLengthBindable.Value)) + { + MinValue = beatLengthToBpm(beatLengthBindable.MaxValue), + MaxValue = beatLengthToBpm(beatLengthBindable.MinValue), + Default = beatLengthToBpm(beatLengthBindable.Default), + }; + + bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); + } + } + + private double beatLengthToBpm(double beatLength) => 60000 / beatLength; + } } } From 5899bbd8a63d8a4035c180f0c507fc7b3676cda3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Oct 2019 18:44:07 +0900 Subject: [PATCH 33/77] Fix merge regressions --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 6 +++--- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index cf084e838e..2ecc516919 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); - Assert.AreEqual(6, controlPoints.DifficultyPoints.Count); + Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); Assert.AreEqual(34, controlPoints.SamplePoints.Count); - Assert.AreEqual(9, controlPoints.EffectPoints.Count); + Assert.AreEqual(8, controlPoints.EffectPoints.Count); var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(48428); - Assert.AreEqual(956, difficultyPoint.Time); + Assert.AreEqual(0, difficultyPoint.Time); Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(116999); diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index d9328d1636..51b3377394 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -34,6 +34,7 @@ namespace osu.Game.Beatmaps.ControlPoints MaxValue = 60000 }; + /// /// The beat length at this control point. /// public double BeatLength From fe374eabe04b3c51efa732c88a71ea6802b322ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2019 15:47:01 +0900 Subject: [PATCH 34/77] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 8b31be3f12..78c7583923 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 0cb09d9b14..42b930b1af 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 719aced705..dfc9c2d60a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From 44d079167676370c41e2a9677b8a8d2f4dce3341 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2019 16:26:31 +0900 Subject: [PATCH 35/77] Update button usage --- osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs | 2 +- osu.Game/Graphics/UserInterface/OsuButton.cs | 2 +- osu.Game/Overlays/Settings/SidebarButton.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index a8c2362910..b6b2b2f92d 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Editor } } - private class StartStopButton : Button + private class StartStopButton : BasicButton { private IAdjustableClock adjustableClock; private bool started; diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 4124d2ad58..319c3dd0b4 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics.UserInterface /// /// A button with added default sound effects. /// - public class OsuButton : Button + public class OsuButton : BasicButton { private Box hover; diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index a94f76e7af..cdb4dc463a 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -17,7 +17,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { - public class SidebarButton : Button + public class SidebarButton : BasicButton { private readonly SpriteIcon drawableIcon; private readonly SpriteText headerText; From 861268eb1ded856d1ebf6fb21115af38e7baec49 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 14:52:33 +0900 Subject: [PATCH 36/77] Add basic structure for new follow point renderer --- .../TestSceneFollowPoints.cs | 315 ++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs new file mode 100644 index 0000000000..c33c94c9c8 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -0,0 +1,315 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using JetBrains.Annotations; +using NUnit.Framework; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneFollowPoints : OsuTestScene + { + private Container hitObjectContainer; + private FollowPointRenderer followPointRenderer; + + [SetUp] + public void Setup() => Schedule(() => + { + Children = new Drawable[] + { + hitObjectContainer = new Container { RelativeSizeAxes = Axes.Both }, + followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } + }; + }); + + [Test] + public void TestAddSingleHitObject() + { + addObject(new HitCircle { Position = new Vector2(100, 100) }); + } + + [Test] + public void TestRemoveSingleHitObject() + { + } + + [Test] + public void TestAddMultipleHitObjects() + { + } + + [Test] + public void TestRemoveEndObject() + { + } + + [Test] + public void TestRemoveStartObject() + { + } + + [Test] + public void TestRemoveMiddleObject() + { + } + + private void addObject(HitCircle hitCircle, Action func = null) + { + AddStep("add hitobject", () => + { + hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + var drawableCircle = new DrawableHitCircle(hitCircle); + + hitObjectContainer.Add(drawableCircle); + followPointRenderer.AddFollowPoints(drawableCircle); + + func?.Invoke(drawableCircle); + }); + } + + private void removeObject(Func func) + { + AddStep("remove hitobject", () => + { + var drawableObject = func?.Invoke(); + + hitObjectContainer.Remove(drawableObject); + followPointRenderer.RemoveFollowPoints(drawableObject); + }); + } + + private class FollowPointRenderer : CompositeDrawable + { + /// + /// Adds the s around a . + /// This includes s leading into , and s exiting . + /// + /// The to add s for. + public void AddFollowPoints(DrawableHitObject hitObject) + { + var startGroup = new FollowPointGroup(hitObject); + AddInternal(startGroup); + + // Groups are sorted by their start time when added, so the index can be used to post-process other surrounding groups + int startIndex = IndexOfInternal(startGroup); + + if (startIndex < InternalChildren.Count - 1) + { + // h1 -> -> -> h2 + // hitObject nextGroup + + var nextGroup = (FollowPointGroup)InternalChildren[startIndex + 1]; + startGroup.End = nextGroup.Start; + } + + if (startIndex > 0) + { + // h1 -> -> -> h2 + // prevGroup hitObject + + var previousGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; + previousGroup.End = startGroup.Start; + } + } + + /// + /// Removes the s around a . + /// This includes s leading into , and s exiting . + /// + /// The to remove s for. + public void RemoveFollowPoints(DrawableHitObject hitObject) + { + var groups = findGroups(hitObject); + + // Regardless of the position of the hitobject in the beatmap, there will always be a group leading from the hitobject + RemoveInternal(groups.start); + + if (groups.end != null) + { + // When there were two groups referencing the same hitobject, merge them by updating the end group to point to the new end (the start group was already removed) + groups.end.End = groups.start.End; + } + } + + /// + /// Finds the s with as the start and end s. + /// + /// The to find the relevant of. + /// A tuple containing the end group (the where is the end of), + /// and the start group (the where is the start of). + private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableHitObject hitObject) + { + // endGroup startGroup + // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 + // hitObject + + FollowPointGroup startGroup = null; // The group which the hitobject is the start in + FollowPointGroup endGroup = null; // The group which the hitobject is the end in + + int startIndex = 0; + + for (; startIndex < InternalChildren.Count; startIndex++) + { + var group = (FollowPointGroup)InternalChildren[startIndex]; + + if (group.Start == hitObject) + { + startGroup = group; + break; + } + } + + if (startIndex > 0) + endGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; + + return (startGroup, endGroup); + } + + protected override int Compare(Drawable x, Drawable y) + { + var groupX = (FollowPointGroup)x; + var groupY = (FollowPointGroup)y; + + return groupX.Start.HitObject.StartTime.CompareTo(groupY.Start.HitObject.StartTime); + } + } + + private class FollowPointGroup : CompositeDrawable + { + private const int spacing = 32; + private const double preempt = 800; + + public FollowPointGroup(DrawableHitObject start) + { + Start = start; + } + + /// + /// The which s will exit from. + /// + [NotNull] + public DrawableHitObject Start { get; } + + private DrawableHitObject end; + + /// + /// The which s will enter. + /// + [CanBeNull] + public DrawableHitObject End + { + get => end; + set + { + end = value; + refreshFollowPoints(); + } + } + + private void refreshFollowPoints() + { + ClearInternal(); + + if (End == null) + return; + + OsuHitObject osuStart = (OsuHitObject)Start.HitObject; + OsuHitObject osuEnd = (OsuHitObject)End.HitObject; + + if (osuEnd.NewCombo) + return; + + if (osuStart is Spinner || osuEnd is Spinner) + return; + + Vector2 startPosition = osuStart.EndPosition; + Vector2 endPosition = osuEnd.Position; + double startTime = (osuStart as IHasEndTime)?.EndTime ?? osuStart.StartTime; + double endTime = osuEnd.StartTime; + + Vector2 distanceVector = endPosition - startPosition; + int distance = (int)distanceVector.Length; + float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); + double duration = endTime - startTime; + + for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) + { + float fraction = (float)d / distance; + Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; + Vector2 pointEndPosition = startPosition + fraction * distanceVector; + double fadeOutTime = startTime + fraction * duration; + double fadeInTime = fadeOutTime - preempt; + + FollowPoint fp; + + AddInternal(fp = new FollowPoint + { + Position = pointStartPosition, + Rotation = rotation, + Alpha = 0, + Scale = new Vector2(1.5f * osuEnd.Scale), + }); + + using (fp.BeginAbsoluteSequence(fadeInTime)) + { + fp.FadeIn(osuEnd.TimeFadeIn); + fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); + fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); + } + + fp.Expire(true); + } + } + } + + private class FollowPoint : CompositeDrawable + { + private const float width = 8; + + public override bool RemoveWhenNotAlive => false; + + public FollowPoint() + { + Origin = Anchor.Centre; + + InternalChild = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new Container + { + Masking = true, + AutoSizeAxes = Axes.Both, + CornerRadius = width / 2, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Color4.White.Opacity(0.2f), + Radius = 4, + }, + Child = new Box + { + Size = new Vector2(width), + Blending = BlendingParameters.Additive, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Alpha = 0.5f, + } + }, confineMode: ConfineMode.NoScaling); + } + } + } +} From 7e60bc724097d12c180a6ed5ff521257dc332f5e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:12:32 +0900 Subject: [PATCH 37/77] Fix groups having 0 size --- osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index c33c94c9c8..ca7be9bb64 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -198,6 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests public FollowPointGroup(DrawableHitObject start) { Start = start; + RelativeSizeAxes = Axes.Both; } /// From c1850b2353c1e85b0153c301f0b82d980ab87211 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:12:42 +0900 Subject: [PATCH 38/77] Add some basic tests --- .../TestSceneFollowPoints.cs | 100 ++++++++++++++++-- 1 file changed, 90 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index ca7be9bb64..66aea68a3c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; @@ -40,54 +42,132 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestAddSingleHitObject() { - addObject(new HitCircle { Position = new Vector2(100, 100) }); + addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }); } [Test] public void TestRemoveSingleHitObject() { + DrawableHitObject obj = null; + + addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); + removeObject(() => obj); } [Test] public void TestAddMultipleHitObjects() { + addObjects(() => new[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }); } [Test] public void TestRemoveEndObject() { + var objects = new List(); + + AddStep("reset", () => objects.Clear()); + + addObjects(() => new[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }, o => objects.Add(o)); + + removeObject(() => objects.Last()); } [Test] public void TestRemoveStartObject() { + var objects = new List(); + + AddStep("reset", () => objects.Clear()); + + addObjects(() => new[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }, o => objects.Add(o)); + + removeObject(() => objects.First()); } [Test] public void TestRemoveMiddleObject() { + var objects = new List(); + + AddStep("reset", () => objects.Clear()); + + addObjects(() => new[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }, o => objects.Add(o)); + + removeObject(() => objects[2]); } - private void addObject(HitCircle hitCircle, Action func = null) + [Test] + public void TestMoveHitObject() { - AddStep("add hitobject", () => + var objects = new List(); + + AddStep("reset", () => objects.Clear()); + + addObjects(() => new[] { - hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }, o => objects.Add(o)); - var drawableCircle = new DrawableHitCircle(hitCircle); + AddStep("move hitobject", () => ((OsuHitObject)objects[2].HitObject).Position = new Vector2(300, 100)); + } - hitObjectContainer.Add(drawableCircle); - followPointRenderer.AddFollowPoints(drawableCircle); + private void addObjects(Func ctorFunc, Action storeFunc = null) + { + AddStep("add hitobjects", () => + { + var circles = ctorFunc(); - func?.Invoke(drawableCircle); + for (int i = 0; i < circles.Length; i++) + { + circles[i].StartTime = Time.Current + 1000 + 500 * (i + 1); + circles[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + var drawableCircle = new DrawableHitCircle(circles[i]); + hitObjectContainer.Add(drawableCircle); + followPointRenderer.AddFollowPoints(drawableCircle); + + storeFunc?.Invoke(drawableCircle); + } }); } - private void removeObject(Func func) + private void removeObject(Func getFunc) { AddStep("remove hitobject", () => { - var drawableObject = func?.Invoke(); + var drawableObject = getFunc?.Invoke(); hitObjectContainer.Remove(drawableObject); followPointRenderer.RemoveFollowPoints(drawableObject); From f861d8099c1344c60f67db77ea955ad47c33013e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:13:50 +0900 Subject: [PATCH 39/77] Remove unnecessary class --- .../TestSceneFollowPoints.cs | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 66aea68a3c..ed1d8f5184 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -6,21 +6,17 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using NUnit.Framework; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Skinning; +using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Tests.Visual; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Tests { @@ -359,38 +355,5 @@ namespace osu.Game.Rulesets.Osu.Tests } } } - - private class FollowPoint : CompositeDrawable - { - private const float width = 8; - - public override bool RemoveWhenNotAlive => false; - - public FollowPoint() - { - Origin = Anchor.Centre; - - InternalChild = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new Container - { - Masking = true, - AutoSizeAxes = Axes.Both, - CornerRadius = width / 2, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = Color4.White.Opacity(0.2f), - Radius = 4, - }, - Child = new Box - { - Size = new Vector2(width), - Blending = BlendingParameters.Additive, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Alpha = 0.5f, - } - }, confineMode: ConfineMode.NoScaling); - } - } } } From 513ad96adf09130dcc613d9b15d934e257118909 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:15:13 +0900 Subject: [PATCH 40/77] Cleanup property --- .../TestSceneFollowPoints.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index ed1d8f5184..43d6cc9ab9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -268,21 +268,22 @@ namespace osu.Game.Rulesets.Osu.Tests private class FollowPointGroup : CompositeDrawable { + // Todo: These shouldn't be constants private const int spacing = 32; private const double preempt = 800; + /// + /// The which s will exit from. + /// + [NotNull] + public readonly DrawableHitObject Start; + public FollowPointGroup(DrawableHitObject start) { Start = start; RelativeSizeAxes = Axes.Both; } - /// - /// The which s will exit from. - /// - [NotNull] - public DrawableHitObject Start { get; } - private DrawableHitObject end; /// From c0badf1dce9d5a2463f40ce698b431c25ed23852 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:17:51 +0900 Subject: [PATCH 41/77] Type to DrawableOsuHitObject --- .../TestSceneFollowPoints.cs | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 43d6cc9ab9..bb3e6a5044 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneFollowPoints : OsuTestScene { - private Container hitObjectContainer; + private Container hitObjectContainer; private FollowPointRenderer followPointRenderer; [SetUp] @@ -30,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Children = new Drawable[] { - hitObjectContainer = new Container { RelativeSizeAxes = Axes.Both }, + hitObjectContainer = new Container { RelativeSizeAxes = Axes.Both }, followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } }; }); @@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestRemoveSingleHitObject() { - DrawableHitObject obj = null; + DrawableOsuHitObject obj = null; addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); removeObject(() => obj); @@ -66,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestRemoveEndObject() { - var objects = new List(); + var objects = new List(); AddStep("reset", () => objects.Clear()); @@ -85,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestRemoveStartObject() { - var objects = new List(); + var objects = new List(); AddStep("reset", () => objects.Clear()); @@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestRemoveMiddleObject() { - var objects = new List(); + var objects = new List(); AddStep("reset", () => objects.Clear()); @@ -123,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestMoveHitObject() { - var objects = new List(); + var objects = new List(); AddStep("reset", () => objects.Clear()); @@ -136,10 +135,10 @@ namespace osu.Game.Rulesets.Osu.Tests new HitCircle { Position = new Vector2(500, 500) }, }, o => objects.Add(o)); - AddStep("move hitobject", () => ((OsuHitObject)objects[2].HitObject).Position = new Vector2(300, 100)); + AddStep("move hitobject", () => objects[2].HitObject.Position = new Vector2(300, 100)); } - private void addObjects(Func ctorFunc, Action storeFunc = null) + private void addObjects(Func ctorFunc, Action storeFunc = null) { AddStep("add hitobjects", () => { @@ -159,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); } - private void removeObject(Func getFunc) + private void removeObject(Func getFunc) { AddStep("remove hitobject", () => { @@ -173,11 +172,11 @@ namespace osu.Game.Rulesets.Osu.Tests private class FollowPointRenderer : CompositeDrawable { /// - /// Adds the s around a . + /// Adds the s around a . /// This includes s leading into , and s exiting . /// - /// The to add s for. - public void AddFollowPoints(DrawableHitObject hitObject) + /// The to add s for. + public void AddFollowPoints(DrawableOsuHitObject hitObject) { var startGroup = new FollowPointGroup(hitObject); AddInternal(startGroup); @@ -205,11 +204,11 @@ namespace osu.Game.Rulesets.Osu.Tests } /// - /// Removes the s around a . + /// Removes the s around a . /// This includes s leading into , and s exiting . /// - /// The to remove s for. - public void RemoveFollowPoints(DrawableHitObject hitObject) + /// The to remove s for. + public void RemoveFollowPoints(DrawableOsuHitObject hitObject) { var groups = findGroups(hitObject); @@ -224,12 +223,12 @@ namespace osu.Game.Rulesets.Osu.Tests } /// - /// Finds the s with as the start and end s. + /// Finds the s with as the start and end s. /// - /// The to find the relevant of. + /// The to find the relevant of. /// A tuple containing the end group (the where is the end of), /// and the start group (the where is the start of). - private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableHitObject hitObject) + private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableOsuHitObject hitObject) { // endGroup startGroup // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 @@ -273,24 +272,24 @@ namespace osu.Game.Rulesets.Osu.Tests private const double preempt = 800; /// - /// The which s will exit from. + /// The which s will exit from. /// [NotNull] - public readonly DrawableHitObject Start; + public readonly DrawableOsuHitObject Start; - public FollowPointGroup(DrawableHitObject start) + public FollowPointGroup(DrawableOsuHitObject start) { Start = start; RelativeSizeAxes = Axes.Both; } - private DrawableHitObject end; + private DrawableOsuHitObject end; /// - /// The which s will enter. + /// The which s will enter. /// [CanBeNull] - public DrawableHitObject End + public DrawableOsuHitObject End { get => end; set @@ -307,8 +306,8 @@ namespace osu.Game.Rulesets.Osu.Tests if (End == null) return; - OsuHitObject osuStart = (OsuHitObject)Start.HitObject; - OsuHitObject osuEnd = (OsuHitObject)End.HitObject; + OsuHitObject osuStart = Start.HitObject; + OsuHitObject osuEnd = End.HitObject; if (osuEnd.NewCombo) return; From e0ba35db755e97abdd33a07db109dbb3ecfff3a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:22:37 +0900 Subject: [PATCH 42/77] Implement binding to hitobjects + refreshing --- .../TestSceneFollowPoints.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index bb3e6a5044..b914a8b29d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -283,6 +283,12 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both; } + protected override void LoadComplete() + { + base.LoadComplete(); + bindHitObject(Start); + } + private DrawableOsuHitObject end; /// @@ -295,10 +301,19 @@ namespace osu.Game.Rulesets.Osu.Tests set { end = value; + + bindHitObject(end); refreshFollowPoints(); } } + private void bindHitObject(DrawableOsuHitObject drawableObject) + { + drawableObject.HitObject.StartTimeBindable.BindValueChanged(_ => refreshFollowPoints()); + drawableObject.HitObject.PositionBindable.BindValueChanged(_ => refreshFollowPoints()); + drawableObject.HitObject.DefaultsApplied += refreshFollowPoints; + } + private void refreshFollowPoints() { ClearInternal(); From 02a7f92d18375176eee9827e17aa126b708f8106 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:34:13 +0900 Subject: [PATCH 43/77] Allow constructing arbitrary hitobject types --- .../TestSceneFollowPoints.cs | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index b914a8b29d..68f37e5d1d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -35,24 +35,24 @@ namespace osu.Game.Rulesets.Osu.Tests }); [Test] - public void TestAddSingleHitObject() + public void TestAddSingleHitCircle() { - addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }); + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); } [Test] - public void TestRemoveSingleHitObject() + public void TestRemoveSingleHitCircle() { DrawableOsuHitObject obj = null; - addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); removeObject(() => obj); } [Test] - public void TestAddMultipleHitObjects() + public void TestAddMultipleHitCircles() { - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -63,13 +63,13 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveEndObject() + public void TestRemoveEndHitCircle() { var objects = new List(); AddStep("reset", () => objects.Clear()); - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -82,13 +82,13 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveStartObject() + public void TestRemoveStartHitCircle() { var objects = new List(); AddStep("reset", () => objects.Clear()); - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -101,13 +101,13 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveMiddleObject() + public void TestRemoveMiddleHitCircle() { var objects = new List(); AddStep("reset", () => objects.Clear()); - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -120,13 +120,13 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestMoveHitObject() + public void TestMoveHitCircle() { var objects = new List(); AddStep("reset", () => objects.Clear()); - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -138,22 +138,38 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("move hitobject", () => objects[2].HitObject.Position = new Vector2(300, 100)); } - private void addObjects(Func ctorFunc, Action storeFunc = null) + private void addObjects(Func ctorFunc, Action storeFunc = null) { AddStep("add hitobjects", () => { - var circles = ctorFunc(); + var objects = ctorFunc(); - for (int i = 0; i < circles.Length; i++) + for (int i = 0; i < objects.Length; i++) { - circles[i].StartTime = Time.Current + 1000 + 500 * (i + 1); - circles[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1); + objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - var drawableCircle = new DrawableHitCircle(circles[i]); - hitObjectContainer.Add(drawableCircle); - followPointRenderer.AddFollowPoints(drawableCircle); + DrawableOsuHitObject drawableObject = null; - storeFunc?.Invoke(drawableCircle); + switch (objects[i]) + { + case HitCircle circle: + drawableObject = new DrawableHitCircle(circle); + break; + + case Slider slider: + drawableObject = new DrawableSlider(slider); + break; + + case Spinner spinner: + drawableObject = new DrawableSpinner(spinner); + break; + } + + hitObjectContainer.Add(drawableObject); + followPointRenderer.AddFollowPoints(drawableObject); + + storeFunc?.Invoke(drawableObject); } }); } From bfe7309964b772d1b5a33aa6ee89be9b5359023f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:34:17 +0900 Subject: [PATCH 44/77] Fix nullref --- osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 68f37e5d1d..9587c721c6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -318,7 +318,9 @@ namespace osu.Game.Rulesets.Osu.Tests { end = value; - bindHitObject(end); + if (end != null) + bindHitObject(end); + refreshFollowPoints(); } } From ddfcda9e029e5385ca8969a58e9aac33d70fc75c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:34:24 +0900 Subject: [PATCH 45/77] Remove abstract ConnectionRenderer class --- .../Connections/ConnectionRenderer.cs | 21 ------------------- .../Connections/FollowPointRenderer.cs | 5 +++-- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- 3 files changed, 4 insertions(+), 24 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs deleted file mode 100644 index 9106f4c7bd..0000000000 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections -{ - /// - /// Connects hit objects visually, for example with follow points. - /// - public abstract class ConnectionRenderer : LifetimeManagementContainer - where T : HitObject - { - /// - /// Hit objects to create connections for - /// - public abstract IEnumerable HitObjects { get; set; } - } -} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index a269b87c75..863ce869aa 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -5,11 +5,12 @@ using System; using System.Collections.Generic; using osuTK; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { - public class FollowPointRenderer : ConnectionRenderer + public class FollowPointRenderer : CompositeDrawable { private int pointDistance = 32; @@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private IEnumerable hitObjects; - public override IEnumerable HitObjects + public IEnumerable HitObjects { get => hitObjects; set diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 69e53d6eea..cbb29ce387 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI { private readonly ApproachCircleProxyContainer approachCircles; private readonly JudgementContainer judgementLayer; - private readonly ConnectionRenderer connectionLayer; + private readonly FollowPointRenderer connectionLayer; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); From 712253ff50ec387a324cc1a0e10bb3bb948d372c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:39:23 +0900 Subject: [PATCH 46/77] Replace follow point renderer with new implementation --- .../TestSceneFollowPoints.cs | 206 ------------------ .../Drawables/Connections/FollowPointGroup.cs | 120 ++++++++++ .../Connections/FollowPointRenderer.cs | 168 +++++++------- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 16 +- 4 files changed, 206 insertions(+), 304 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 9587c721c6..3c447e5009 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -4,13 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; @@ -184,209 +182,5 @@ namespace osu.Game.Rulesets.Osu.Tests followPointRenderer.RemoveFollowPoints(drawableObject); }); } - - private class FollowPointRenderer : CompositeDrawable - { - /// - /// Adds the s around a . - /// This includes s leading into , and s exiting . - /// - /// The to add s for. - public void AddFollowPoints(DrawableOsuHitObject hitObject) - { - var startGroup = new FollowPointGroup(hitObject); - AddInternal(startGroup); - - // Groups are sorted by their start time when added, so the index can be used to post-process other surrounding groups - int startIndex = IndexOfInternal(startGroup); - - if (startIndex < InternalChildren.Count - 1) - { - // h1 -> -> -> h2 - // hitObject nextGroup - - var nextGroup = (FollowPointGroup)InternalChildren[startIndex + 1]; - startGroup.End = nextGroup.Start; - } - - if (startIndex > 0) - { - // h1 -> -> -> h2 - // prevGroup hitObject - - var previousGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; - previousGroup.End = startGroup.Start; - } - } - - /// - /// Removes the s around a . - /// This includes s leading into , and s exiting . - /// - /// The to remove s for. - public void RemoveFollowPoints(DrawableOsuHitObject hitObject) - { - var groups = findGroups(hitObject); - - // Regardless of the position of the hitobject in the beatmap, there will always be a group leading from the hitobject - RemoveInternal(groups.start); - - if (groups.end != null) - { - // When there were two groups referencing the same hitobject, merge them by updating the end group to point to the new end (the start group was already removed) - groups.end.End = groups.start.End; - } - } - - /// - /// Finds the s with as the start and end s. - /// - /// The to find the relevant of. - /// A tuple containing the end group (the where is the end of), - /// and the start group (the where is the start of). - private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableOsuHitObject hitObject) - { - // endGroup startGroup - // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 - // hitObject - - FollowPointGroup startGroup = null; // The group which the hitobject is the start in - FollowPointGroup endGroup = null; // The group which the hitobject is the end in - - int startIndex = 0; - - for (; startIndex < InternalChildren.Count; startIndex++) - { - var group = (FollowPointGroup)InternalChildren[startIndex]; - - if (group.Start == hitObject) - { - startGroup = group; - break; - } - } - - if (startIndex > 0) - endGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; - - return (startGroup, endGroup); - } - - protected override int Compare(Drawable x, Drawable y) - { - var groupX = (FollowPointGroup)x; - var groupY = (FollowPointGroup)y; - - return groupX.Start.HitObject.StartTime.CompareTo(groupY.Start.HitObject.StartTime); - } - } - - private class FollowPointGroup : CompositeDrawable - { - // Todo: These shouldn't be constants - private const int spacing = 32; - private const double preempt = 800; - - /// - /// The which s will exit from. - /// - [NotNull] - public readonly DrawableOsuHitObject Start; - - public FollowPointGroup(DrawableOsuHitObject start) - { - Start = start; - RelativeSizeAxes = Axes.Both; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - bindHitObject(Start); - } - - private DrawableOsuHitObject end; - - /// - /// The which s will enter. - /// - [CanBeNull] - public DrawableOsuHitObject End - { - get => end; - set - { - end = value; - - if (end != null) - bindHitObject(end); - - refreshFollowPoints(); - } - } - - private void bindHitObject(DrawableOsuHitObject drawableObject) - { - drawableObject.HitObject.StartTimeBindable.BindValueChanged(_ => refreshFollowPoints()); - drawableObject.HitObject.PositionBindable.BindValueChanged(_ => refreshFollowPoints()); - drawableObject.HitObject.DefaultsApplied += refreshFollowPoints; - } - - private void refreshFollowPoints() - { - ClearInternal(); - - if (End == null) - return; - - OsuHitObject osuStart = Start.HitObject; - OsuHitObject osuEnd = End.HitObject; - - if (osuEnd.NewCombo) - return; - - if (osuStart is Spinner || osuEnd is Spinner) - return; - - Vector2 startPosition = osuStart.EndPosition; - Vector2 endPosition = osuEnd.Position; - double startTime = (osuStart as IHasEndTime)?.EndTime ?? osuStart.StartTime; - double endTime = osuEnd.StartTime; - - Vector2 distanceVector = endPosition - startPosition; - int distance = (int)distanceVector.Length; - float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); - double duration = endTime - startTime; - - for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) - { - float fraction = (float)d / distance; - Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; - Vector2 pointEndPosition = startPosition + fraction * distanceVector; - double fadeOutTime = startTime + fraction * duration; - double fadeInTime = fadeOutTime - preempt; - - FollowPoint fp; - - AddInternal(fp = new FollowPoint - { - Position = pointStartPosition, - Rotation = rotation, - Alpha = 0, - Scale = new Vector2(1.5f * osuEnd.Scale), - }); - - using (fp.BeginAbsoluteSequence(fadeInTime)) - { - fp.FadeIn(osuEnd.TimeFadeIn); - fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); - fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); - fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); - } - - fp.Expire(true); - } - } - } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs new file mode 100644 index 0000000000..06aadcc342 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -0,0 +1,120 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using JetBrains.Annotations; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections +{ + public class FollowPointGroup : CompositeDrawable + { + // Todo: These shouldn't be constants + private const int spacing = 32; + private const double preempt = 800; + + /// + /// The which s will exit from. + /// + [NotNull] + public readonly DrawableOsuHitObject Start; + + public FollowPointGroup(DrawableOsuHitObject start) + { + Start = start; + RelativeSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + bindHitObject(Start); + } + + private DrawableOsuHitObject end; + + /// + /// The which s will enter. + /// + [CanBeNull] + public DrawableOsuHitObject End + { + get => end; + set + { + end = value; + + if (end != null) + bindHitObject(end); + + refreshFollowPoints(); + } + } + + private void bindHitObject(DrawableOsuHitObject drawableObject) + { + drawableObject.HitObject.StartTimeBindable.BindValueChanged(_ => refreshFollowPoints()); + drawableObject.HitObject.PositionBindable.BindValueChanged(_ => refreshFollowPoints()); + drawableObject.HitObject.DefaultsApplied += refreshFollowPoints; + } + + private void refreshFollowPoints() + { + ClearInternal(); + + if (End == null) + return; + + OsuHitObject osuStart = Start.HitObject; + OsuHitObject osuEnd = End.HitObject; + + if (osuEnd.NewCombo) + return; + + if (osuStart is Spinner || osuEnd is Spinner) + return; + + Vector2 startPosition = osuStart.EndPosition; + Vector2 endPosition = osuEnd.Position; + double startTime = (osuStart as IHasEndTime)?.EndTime ?? osuStart.StartTime; + double endTime = osuEnd.StartTime; + + Vector2 distanceVector = endPosition - startPosition; + int distance = (int)distanceVector.Length; + float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); + double duration = endTime - startTime; + + for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) + { + float fraction = (float)d / distance; + Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; + Vector2 pointEndPosition = startPosition + fraction * distanceVector; + double fadeOutTime = startTime + fraction * duration; + double fadeInTime = fadeOutTime - preempt; + + FollowPoint fp; + + AddInternal(fp = new FollowPoint + { + Position = pointStartPosition, + Rotation = rotation, + Alpha = 0, + Scale = new Vector2(1.5f * osuEnd.Scale), + }); + + using (fp.BeginAbsoluteSequence(fadeInTime)) + { + fp.FadeIn(osuEnd.TimeFadeIn); + fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); + fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); + } + + fp.Expire(true); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 863ce869aa..47c35746f0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -1,122 +1,104 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointRenderer : CompositeDrawable { - private int pointDistance = 32; + /// + /// Adds the s around a . + /// This includes s leading into , and s exiting . + /// + /// The to add s for. + public void AddFollowPoints(DrawableOsuHitObject hitObject) + { + var startGroup = new FollowPointGroup(hitObject); + AddInternal(startGroup); + + // Groups are sorted by their start time when added, so the index can be used to post-process other surrounding groups + int startIndex = IndexOfInternal(startGroup); + + if (startIndex < InternalChildren.Count - 1) + { + // h1 -> -> -> h2 + // hitObject nextGroup + + var nextGroup = (FollowPointGroup)InternalChildren[startIndex + 1]; + startGroup.End = nextGroup.Start; + } + + if (startIndex > 0) + { + // h1 -> -> -> h2 + // prevGroup hitObject + + var previousGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; + previousGroup.End = startGroup.Start; + } + } /// - /// Determines how much space there is between points. + /// Removes the s around a . + /// This includes s leading into , and s exiting . /// - public int PointDistance + /// The to remove s for. + public void RemoveFollowPoints(DrawableOsuHitObject hitObject) { - get => pointDistance; - set - { - if (pointDistance == value) return; + var groups = findGroups(hitObject); - pointDistance = value; - update(); + // Regardless of the position of the hitobject in the beatmap, there will always be a group leading from the hitobject + RemoveInternal(groups.start); + + if (groups.end != null) + { + // When there were two groups referencing the same hitobject, merge them by updating the end group to point to the new end (the start group was already removed) + groups.end.End = groups.start.End; } } - private int preEmpt = 800; - /// - /// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time. + /// Finds the s with as the start and end s. /// - public int PreEmpt + /// The to find the relevant of. + /// A tuple containing the end group (the where is the end of), + /// and the start group (the where is the start of). + private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableOsuHitObject hitObject) { - get => preEmpt; - set + // endGroup startGroup + // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 + // hitObject + + FollowPointGroup startGroup = null; // The group which the hitobject is the start in + FollowPointGroup endGroup = null; // The group which the hitobject is the end in + + int startIndex = 0; + + for (; startIndex < InternalChildren.Count; startIndex++) { - if (preEmpt == value) return; + var group = (FollowPointGroup)InternalChildren[startIndex]; - preEmpt = value; - update(); - } - } - - private IEnumerable hitObjects; - - public IEnumerable HitObjects - { - get => hitObjects; - set - { - hitObjects = value; - update(); - } - } - - public override bool RemoveCompletedTransforms => false; - - private void update() - { - ClearInternal(); - - if (hitObjects == null) - return; - - OsuHitObject prevHitObject = null; - - foreach (var currHitObject in hitObjects) - { - if (prevHitObject != null && !currHitObject.NewCombo && !(prevHitObject is Spinner) && !(currHitObject is Spinner)) + if (group.Start == hitObject) { - Vector2 startPosition = prevHitObject.EndPosition; - Vector2 endPosition = currHitObject.Position; - double startTime = (prevHitObject as IHasEndTime)?.EndTime ?? prevHitObject.StartTime; - double endTime = currHitObject.StartTime; - - Vector2 distanceVector = endPosition - startPosition; - int distance = (int)distanceVector.Length; - float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); - double duration = endTime - startTime; - - for (int d = (int)(PointDistance * 1.5); d < distance - PointDistance; d += PointDistance) - { - float fraction = (float)d / distance; - Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; - Vector2 pointEndPosition = startPosition + fraction * distanceVector; - double fadeOutTime = startTime + fraction * duration; - double fadeInTime = fadeOutTime - PreEmpt; - - FollowPoint fp; - - AddInternal(fp = new FollowPoint - { - Position = pointStartPosition, - Rotation = rotation, - Alpha = 0, - Scale = new Vector2(1.5f * currHitObject.Scale), - }); - - using (fp.BeginAbsoluteSequence(fadeInTime)) - { - fp.FadeIn(currHitObject.TimeFadeIn); - fp.ScaleTo(currHitObject.Scale, currHitObject.TimeFadeIn, Easing.Out); - - fp.MoveTo(pointEndPosition, currHitObject.TimeFadeIn, Easing.Out); - - fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadeIn); - } - - fp.Expire(true); - } + startGroup = group; + break; } - - prevHitObject = currHitObject; } + + if (startIndex > 0) + endGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; + + return (startGroup, endGroup); + } + + protected override int Compare(Drawable x, Drawable y) + { + var groupX = (FollowPointGroup)x; + var groupY = (FollowPointGroup)y; + + return groupX.Start.HitObject.StartTime.CompareTo(groupY.Start.HitObject.StartTime); } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index cbb29ce387..6d1ea4bbfc 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -9,7 +9,6 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.UI; -using System.Linq; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Skinning; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.UI { private readonly ApproachCircleProxyContainer approachCircles; private readonly JudgementContainer judgementLayer; - private readonly FollowPointRenderer connectionLayer; + private readonly FollowPointRenderer followPoints; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -30,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.UI { InternalChildren = new Drawable[] { - connectionLayer = new FollowPointRenderer + followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both, Depth = 2, @@ -64,11 +63,18 @@ namespace osu.Game.Rulesets.Osu.UI }; base.Add(h); + + followPoints.AddFollowPoints((DrawableOsuHitObject)h); } - public override void PostProcess() + public override bool Remove(DrawableHitObject h) { - connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType(); + bool result = base.Remove(h); + + if (result) + followPoints.RemoveFollowPoints((DrawableOsuHitObject)h); + + return result; } private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) From a19e26f8aa6c8d1bbb5bf80c499704cb14f6f729 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 19:21:39 +0900 Subject: [PATCH 47/77] Improve performance of refreshes --- .../Drawables/Connections/FollowPointGroup.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 06aadcc342..750e9559fc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections protected override void LoadComplete() { base.LoadComplete(); - bindHitObject(Start); + bindEvents(Start); } private DrawableOsuHitObject end; @@ -48,20 +48,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections end = value; if (end != null) - bindHitObject(end); + bindEvents(end); - refreshFollowPoints(); + refresh(); } } - private void bindHitObject(DrawableOsuHitObject drawableObject) + private void bindEvents(DrawableOsuHitObject drawableObject) { - drawableObject.HitObject.StartTimeBindable.BindValueChanged(_ => refreshFollowPoints()); - drawableObject.HitObject.PositionBindable.BindValueChanged(_ => refreshFollowPoints()); - drawableObject.HitObject.DefaultsApplied += refreshFollowPoints; + drawableObject.HitObject.PositionBindable.BindValueChanged(_ => scheduleRefresh()); + drawableObject.HitObject.DefaultsApplied += scheduleRefresh; } - private void refreshFollowPoints() + private void scheduleRefresh() => Scheduler.AddOnce(refresh); + + private void refresh() { ClearInternal(); From 3b6064336b63f6bd00f7bdee699cb6dae2903279 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 19:22:07 +0900 Subject: [PATCH 48/77] Implement group re-ordering based on start time --- .../Drawables/Connections/FollowPointGroup.cs | 12 +- .../Connections/FollowPointRenderer.cs | 115 ++++++++---------- 2 files changed, 65 insertions(+), 62 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 750e9559fc..79d69acd67 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -3,6 +3,7 @@ using System; using JetBrains.Annotations; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Types; @@ -16,16 +17,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private const int spacing = 32; private const double preempt = 800; + public readonly Bindable StartTime = new Bindable(); + /// /// The which s will exit from. /// [NotNull] public readonly DrawableOsuHitObject Start; - public FollowPointGroup(DrawableOsuHitObject start) + /// + /// Creates a new . + /// + /// The which s will exit from. + public FollowPointGroup([NotNull] DrawableOsuHitObject start) { Start = start; + RelativeSizeAxes = Axes.Both; + + StartTime.BindTo(Start.HitObject.StartTimeBindable); } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 47c35746f0..892442f0a7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -1,6 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,97 +11,87 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointRenderer : CompositeDrawable { + private readonly List groups = new List(); + /// /// Adds the s around a . /// This includes s leading into , and s exiting . /// /// The to add s for. public void AddFollowPoints(DrawableOsuHitObject hitObject) - { - var startGroup = new FollowPointGroup(hitObject); - AddInternal(startGroup); - - // Groups are sorted by their start time when added, so the index can be used to post-process other surrounding groups - int startIndex = IndexOfInternal(startGroup); - - if (startIndex < InternalChildren.Count - 1) - { - // h1 -> -> -> h2 - // hitObject nextGroup - - var nextGroup = (FollowPointGroup)InternalChildren[startIndex + 1]; - startGroup.End = nextGroup.Start; - } - - if (startIndex > 0) - { - // h1 -> -> -> h2 - // prevGroup hitObject - - var previousGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; - previousGroup.End = startGroup.Start; - } - } + => addGroup(new FollowPointGroup(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g)))); /// /// Removes the s around a . /// This includes s leading into , and s exiting . /// /// The to remove s for. - public void RemoveFollowPoints(DrawableOsuHitObject hitObject) + public void RemoveFollowPoints(DrawableOsuHitObject hitObject) => removeGroup(groups.Single(g => g.Start == hitObject)); + + /// + /// Adds a to this . + /// + /// The to add. + /// The index of in . + private int addGroup(FollowPointGroup group) { - var groups = findGroups(hitObject); + AddInternal(group); - // Regardless of the position of the hitobject in the beatmap, there will always be a group leading from the hitobject - RemoveInternal(groups.start); + // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding groups + int index = groups.AddInPlace(group, Comparer.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value))); - if (groups.end != null) + if (index < groups.Count - 1) { - // When there were two groups referencing the same hitobject, merge them by updating the end group to point to the new end (the start group was already removed) - groups.end.End = groups.start.End; + // Update the group's end point to the next hitobject + // h1 -> -> -> h2 + // hitObject nextGroup + + FollowPointGroup nextGroup = groups[index + 1]; + group.End = nextGroup.Start; } + + if (index > 0) + { + // Previous group's end point to the current group's start point + // h1 -> -> -> h2 + // prevGroup hitObject + + FollowPointGroup previousGroup = groups[index - 1]; + previousGroup.End = group.Start; + } + + return index; } /// - /// Finds the s with as the start and end s. + /// Removes a from this . /// - /// The to find the relevant of. - /// A tuple containing the end group (the where is the end of), - /// and the start group (the where is the start of). - private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableOsuHitObject hitObject) + /// The to remove. + /// Whether was removed. + private bool removeGroup(FollowPointGroup group) { - // endGroup startGroup - // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 - // hitObject + RemoveInternal(group); - FollowPointGroup startGroup = null; // The group which the hitobject is the start in - FollowPointGroup endGroup = null; // The group which the hitobject is the end in + int index = groups.IndexOf(group); - int startIndex = 0; - - for (; startIndex < InternalChildren.Count; startIndex++) + if (index > 0) { - var group = (FollowPointGroup)InternalChildren[startIndex]; - - if (group.Start == hitObject) - { - startGroup = group; - break; - } + // Update the previous group's end point to the next group's start point + // h1 -> -> -> h2 -> -> -> h3 + // prevGroup group nextGroup + // The current group's end point is used since there may not be a next group + FollowPointGroup previousGroup = groups[index - 1]; + previousGroup.End = group.End; } - if (startIndex > 0) - endGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; - - return (startGroup, endGroup); + return groups.Remove(group); } - protected override int Compare(Drawable x, Drawable y) + private void onStartTimeChanged(FollowPointGroup group) { - var groupX = (FollowPointGroup)x; - var groupY = (FollowPointGroup)y; - - return groupX.Start.HitObject.StartTime.CompareTo(groupY.Start.HitObject.StartTime); + // Naive but can be improved if performance becomes problematic + removeGroup(group); + addGroup(group); } } } From 1ef2b81041dfd9225ffb7e792b8c55bfa76d6ff1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 22:08:50 +0900 Subject: [PATCH 49/77] Fix follow point lifetime not being updated correctly --- .../Drawables/Connections/FollowPointGroup.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 79d69acd67..47196f4893 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -118,13 +118,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections using (fp.BeginAbsoluteSequence(fadeInTime)) { - fp.FadeIn(osuEnd.TimeFadeIn); - fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); - fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); - fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); - } + // See: Expire calls are separated due to https://github.com/ppy/osu-framework/issues/2941 - fp.Expire(true); + fp.FadeIn(osuEnd.TimeFadeIn).Expire(true); + + fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out) + .MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out) + .Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn) + .Expire(); + } } } } From 6c58faf30cab3f8f36d2b8d77621f0bca2529f1a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 19:31:48 +0900 Subject: [PATCH 50/77] Fix group ends potentially not being updated correctly --- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 892442f0a7..e29cf6e128 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections FollowPointGroup nextGroup = groups[index + 1]; group.End = nextGroup.Start; } + else + group.End = null; if (index > 0) { From f2118b0eba6fa022023ef453b11b9ed61dcf2580 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 19:31:58 +0900 Subject: [PATCH 51/77] Add automated test cases --- .../TestSceneFollowPoints.cs | 178 +++++++++++------- .../Connections/FollowPointRenderer.cs | 2 + 2 files changed, 113 insertions(+), 67 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 3c447e5009..aac0119dc7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Children = new Drawable[] { - hitObjectContainer = new Container { RelativeSizeAxes = Axes.Both }, + hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both }, followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } }; }); @@ -35,108 +33,97 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestAddSingleHitCircle() { - addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); + addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); + + assertGroups(); } [Test] public void TestRemoveSingleHitCircle() { - DrawableOsuHitObject obj = null; + addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); - addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); - removeObject(() => obj); + removeObjectStep(() => getObject(0)); + + assertGroups(); } [Test] public void TestAddMultipleHitCircles() { - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }); + addMultipleObjectsStep(); + + assertGroups(); } [Test] public void TestRemoveEndHitCircle() { - var objects = new List(); + addMultipleObjectsStep(); - AddStep("reset", () => objects.Clear()); + removeObjectStep(() => getObject(4)); - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }, o => objects.Add(o)); - - removeObject(() => objects.Last()); + assertGroups(); } [Test] public void TestRemoveStartHitCircle() { - var objects = new List(); + addMultipleObjectsStep(); - AddStep("reset", () => objects.Clear()); + removeObjectStep(() => getObject(0)); - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }, o => objects.Add(o)); - - removeObject(() => objects.First()); + assertGroups(); } [Test] public void TestRemoveMiddleHitCircle() { - var objects = new List(); + addMultipleObjectsStep(); - AddStep("reset", () => objects.Clear()); + removeObjectStep(() => getObject(2)); - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }, o => objects.Add(o)); - - removeObject(() => objects[2]); + assertGroups(); } [Test] public void TestMoveHitCircle() { - var objects = new List(); + addMultipleObjectsStep(); - AddStep("reset", () => objects.Clear()); + AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100)); - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }, o => objects.Add(o)); - - AddStep("move hitobject", () => objects[2].HitObject.Position = new Vector2(300, 100)); + assertGroups(); } - private void addObjects(Func ctorFunc, Action storeFunc = null) + [TestCase(0, 0)] // Start -> Start + [TestCase(0, 2)] // Start -> Middle + [TestCase(0, 5)] // Start -> End + [TestCase(2, 0)] // Middle -> Start + [TestCase(1, 3)] // Middle -> Middle (forwards) + [TestCase(3, 1)] // Middle -> Middle (backwards) + [TestCase(4, 0)] // End -> Start + [TestCase(4, 2)] // End -> Middle + [TestCase(4, 4)] // End -> End + public void TestReorderHitObjects(int startIndex, int endIndex) + { + addMultipleObjectsStep(); + + reorderObjectStep(startIndex, endIndex); + + assertGroups(); + } + + private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }); + + private void addObjectsStep(Func ctorFunc) { AddStep("add hitobjects", () => { @@ -166,13 +153,11 @@ namespace osu.Game.Rulesets.Osu.Tests hitObjectContainer.Add(drawableObject); followPointRenderer.AddFollowPoints(drawableObject); - - storeFunc?.Invoke(drawableObject); } }); } - private void removeObject(Func getFunc) + private void removeObjectStep(Func getFunc) { AddStep("remove hitobject", () => { @@ -182,5 +167,64 @@ namespace osu.Game.Rulesets.Osu.Tests followPointRenderer.RemoveFollowPoints(drawableObject); }); } + + private void reorderObjectStep(int startIndex, int endIndex) + { + AddStep($"move object {startIndex} to {endIndex}", () => + { + DrawableOsuHitObject toReorder = getObject(startIndex); + + double targetTime; + if (endIndex < hitObjectContainer.Count) + targetTime = getObject(endIndex).HitObject.StartTime - 1; + else + targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1; + + hitObjectContainer.Remove(toReorder); + toReorder.HitObject.StartTime = targetTime; + hitObjectContainer.Add(toReorder); + }); + } + + private void assertGroups() + { + AddAssert("has correct group count", () => followPointRenderer.Groups.Count == hitObjectContainer.Count); + AddAssert("group endpoints are correct", () => + { + for (int i = 0; i < hitObjectContainer.Count; i++) + { + DrawableOsuHitObject expectedStart = getObject(i); + DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; + + if (getGroup(i).Start != expectedStart) + throw new AssertionException($"Object {i} expected to be the start of group {i}."); + + if (getGroup(i).End != expectedEnd) + throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}."); + } + + return true; + }); + } + + private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; + + private FollowPointGroup getGroup(int index) => followPointRenderer.Groups[index]; + + private class TestHitObjectContainer : Container + { + protected override int Compare(Drawable x, Drawable y) + { + var osuX = (DrawableOsuHitObject)x; + var osuY = (DrawableOsuHitObject)y; + + int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime); + + if (compare == 0) + return base.Compare(x, y); + + return compare; + } + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index e29cf6e128..e5e8efae8e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointRenderer : CompositeDrawable { + internal IReadOnlyList Groups => groups; + private readonly List groups = new List(); /// From d0286037696594660cc0641d94e68ca616ba9bdf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 22:50:01 +0900 Subject: [PATCH 52/77] Rename tests --- .../TestSceneFollowPoints.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index aac0119dc7..79a1bb579f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); [Test] - public void TestAddSingleHitCircle() + public void TestAddObject() { addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveSingleHitCircle() + public void TestRemoveObject() { addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestAddMultipleHitCircles() + public void TestAddMultipleObjects() { addMultipleObjectsStep(); @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveEndHitCircle() + public void TestRemoveEndObject() { addMultipleObjectsStep(); @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveStartHitCircle() + public void TestRemoveStartObject() { addMultipleObjectsStep(); @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveMiddleHitCircle() + public void TestRemoveMiddleObject() { addMultipleObjectsStep(); @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestMoveHitCircle() + public void TestMoveObject() { addMultipleObjectsStep(); @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(4, 0)] // End -> Start [TestCase(4, 2)] // End -> Middle [TestCase(4, 4)] // End -> End - public void TestReorderHitObjects(int startIndex, int endIndex) + public void TestReorderObjects(int startIndex, int endIndex) { addMultipleObjectsStep(); From 68a81e0eb0f813fc3710ea09aa273d75ac2b8671 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 22:50:21 +0900 Subject: [PATCH 53/77] Fix follow point transforms not working after rewind --- .../Objects/Drawables/Connections/FollowPoint.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 89ffddf4cb..7ba044b462 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public override bool RemoveWhenNotAlive => false; + public override bool RemoveCompletedTransforms => false; + public FollowPoint() { Origin = Anchor.Centre; From d762ec959c9240594a46ba2228095fea4b7e10ca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 22:50:38 +0900 Subject: [PATCH 54/77] Schedule group refresh when loaded --- .../Objects/Drawables/Connections/FollowPointGroup.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 47196f4893..c77ecd355f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -60,7 +60,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections if (end != null) bindEvents(end); - refresh(); + if (IsLoaded) + scheduleRefresh(); + else + refresh(); } } From 0a2af2b0fe067b3caece643b1acbdc29b427a823 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 23:02:39 +0900 Subject: [PATCH 55/77] Apply transform override at a higher level --- .../Objects/Drawables/Connections/FollowPoint.cs | 2 -- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 7ba044b462..89ffddf4cb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public override bool RemoveWhenNotAlive => false; - public override bool RemoveCompletedTransforms => false; - public FollowPoint() { Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index e5e8efae8e..aa9ec8d96d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private readonly List groups = new List(); + public override bool RemoveCompletedTransforms => false; + /// /// Adds the s around a . /// This includes s leading into , and s exiting . From aff275ea21784999ca29e37a695da2df430332a7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 23:03:05 +0900 Subject: [PATCH 56/77] Revert "Fix follow point lifetime not being updated correctly" This reverts commit 1ef2b81041dfd9225ffb7e792b8c55bfa76d6ff1. --- .../Drawables/Connections/FollowPointGroup.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index c77ecd355f..9d651373ff 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -121,15 +121,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections using (fp.BeginAbsoluteSequence(fadeInTime)) { - // See: Expire calls are separated due to https://github.com/ppy/osu-framework/issues/2941 - - fp.FadeIn(osuEnd.TimeFadeIn).Expire(true); - - fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out) - .MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out) - .Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn) - .Expire(); + fp.FadeIn(osuEnd.TimeFadeIn); + fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); + fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); } + + fp.Expire(true); } } } From 68ca5cb26a6b1f0d56ba258c0edb29afb808c646 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 23:20:46 +0900 Subject: [PATCH 57/77] Adjust comments --- .../Drawables/Connections/FollowPoint.cs | 3 +++ .../Drawables/Connections/FollowPointGroup.cs | 6 ++++++ .../Connections/FollowPointRenderer.cs | 19 ++++++++++++++----- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 89ffddf4cb..db34ae1d87 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -12,6 +12,9 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { + /// + /// A single follow point positioned between two adjacent s. + /// public class FollowPoint : Container { private const float width = 8; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 9d651373ff..168d2b8532 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -11,12 +11,18 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { + /// + /// Visualises the s between two s. + /// public class FollowPointGroup : CompositeDrawable { // Todo: These shouldn't be constants private const int spacing = 32; private const double preempt = 800; + /// + /// The start time of . + /// public readonly Bindable StartTime = new Bindable(); /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index aa9ec8d96d..afd86e004d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -9,8 +9,14 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { + /// + /// Visualises groups of s. + /// public class FollowPointRenderer : CompositeDrawable { + /// + /// All the s contained by this . + /// internal IReadOnlyList Groups => groups; private readonly List groups = new List(); @@ -46,21 +52,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections if (index < groups.Count - 1) { - // Update the group's end point to the next hitobject + // Update the group's end point to the next group's start point // h1 -> -> -> h2 - // hitObject nextGroup + // group nextGroup FollowPointGroup nextGroup = groups[index + 1]; group.End = nextGroup.Start; } else + { + // The end point may be non-null during re-ordering group.End = null; + } if (index > 0) { - // Previous group's end point to the current group's start point + // Update the previous group's end point to the current group's start point // h1 -> -> -> h2 - // prevGroup hitObject + // prevGroup group FollowPointGroup previousGroup = groups[index - 1]; previousGroup.End = group.Start; @@ -95,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private void onStartTimeChanged(FollowPointGroup group) { - // Naive but can be improved if performance becomes problematic + // Naive but can be improved if performance becomes an issue removeGroup(group); addGroup(group); } From 1f40641d05cc9a1da02d00eaf4ba14a30c367299 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 10:32:09 +0900 Subject: [PATCH 58/77] Add failing test --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 64022b2410..6e8975f11b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -237,6 +237,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("player not exited", () => Player.IsCurrentScreen()); AddStep("exit", () => Player.Exit()); confirmExited(); + confirmNoTrackAdjustments(); } private void confirmPaused() @@ -258,6 +259,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("player exited", () => !Player.IsCurrentScreen()); } + private void confirmNoTrackAdjustments() + { + AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1); + } + private void restart() => AddStep("restart", () => Player.Restart()); private void pause() => AddStep("pause", () => Player.Pause()); private void resume() => AddStep("resume", () => Player.Resume()); From cd1dd0f8981d2ed7c5c03b67d401ebc1ec0b4a62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 10:38:04 +0900 Subject: [PATCH 59/77] Fix adjustments not being removed correctly on retry from pause --- osu.Game/Screens/Play/GameplayClockContainer.cs | 16 ++++++++++------ osu.Game/Screens/Play/Player.cs | 2 -- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index f2efbe6073..2f2028ff53 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -162,16 +162,12 @@ namespace osu.Game.Screens.Play if (sourceClock != beatmap.Track) return; + removeSourceClockAdjustments(); + sourceClock = new TrackVirtual(beatmap.Track.Length); adjustableClock.ChangeSource(sourceClock); } - public void ResetLocalAdjustments() - { - // In the case of replays, we may have changed the playback rate. - UserPlaybackRate.Value = 1; - } - protected override void Update() { if (!IsPaused.Value) @@ -198,6 +194,14 @@ namespace osu.Game.Screens.Play protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + + removeSourceClockAdjustments(); + sourceClock = null; + } + + private void removeSourceClockAdjustments() + { + sourceClock.ResetSpeedAdjustments(); (sourceClock as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a3c39d9cc1..a9b0649fab 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -536,8 +536,6 @@ namespace osu.Game.Screens.Play return true; } - GameplayClockContainer.ResetLocalAdjustments(); - // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. GameplayClockContainer.StopUsingBeatmapClock(); From 892ef017ef3b85ba184e35202a3eb14f59a51c09 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2019 03:05:03 +0000 Subject: [PATCH 60/77] Bump ppy.osu.Framework.iOS from 2019.1029.0 to 2019.1106.0 Bumps [ppy.osu.Framework.iOS](https://github.com/ppy/osu-framework) from 2019.1029.0 to 2019.1106.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.1029.0...2019.1106.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 719aced705..d257948831 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -119,7 +119,7 @@ - + From 1d83f51a804f9a0972455433666f42219ed057c9 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2019 03:05:05 +0000 Subject: [PATCH 61/77] Bump ppy.osu.Framework.Android from 2019.1029.0 to 2019.1106.0 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.1029.0 to 2019.1106.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.1029.0...2019.1106.0) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index 8b31be3f12..d1860acbf9 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + From 8cf349c1eefa1996a1d5f9446181150bcf574ae8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 12:10:53 +0900 Subject: [PATCH 62/77] Update once more --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 42b930b1af..dbe6559c40 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + From f20bfe7a554849d6d1fc762741e0fd4f084a4e07 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 12:16:48 +0900 Subject: [PATCH 63/77] Fix extra semicolon --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index bd363c3158..cd33425ee5 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier::0.##}x"; }); + ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier:0.##}x"; }); } protected override DifficultyControlPoint CreatePoint() From 9dd7f997d2c01836a43ad9630261e095e14a8c36 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 12:17:18 +0900 Subject: [PATCH 64/77] Reoder SampleSection to match others --- osu.Game/Screens/Edit/Timing/SampleSection.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index 0477ad4e78..e665db0a4d 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -22,17 +22,6 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override SampleControlPoint CreatePoint() - { - var reference = Beatmap.Value.Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time); - - return new SampleControlPoint - { - SampleBank = reference.SampleBank, - SampleVolume = reference.SampleVolume, - }; - } - protected override void LoadComplete() { base.LoadComplete(); @@ -43,5 +32,16 @@ namespace osu.Game.Screens.Edit.Timing volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; }); } + + protected override SampleControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time); + + return new SampleControlPoint + { + SampleBank = reference.SampleBank, + SampleVolume = reference.SampleVolume, + }; + } } } From 7cd4cb8a935c797fd0d977198b99f7515bc225e8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 12:32:12 +0900 Subject: [PATCH 65/77] Rename selectedPoints to selectedGroup --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 6ae01e056f..9317448262 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Edit.Timing public class TimingScreen : EditorScreenWithTimeline { [Cached] - private Bindable selectedPoints = new Bindable(); + private Bindable selectedGroup = new Bindable(); [Resolved] private IAdjustableClock clock { get; set; } @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedPoints.BindValueChanged(selected => { clock.Seek(selected.NewValue.Time); }); + selectedGroup.BindValueChanged(selected => { clock.Seek(selected.NewValue.Time); }); } public class ControlPointList : CompositeDrawable From 322a1f0a860eff545c166426e026f14da48437f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 12:45:35 +0900 Subject: [PATCH 66/77] Fix potential nullref --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 9317448262..d9da3ff92d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -47,7 +47,11 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(selected => { clock.Seek(selected.NewValue.Time); }); + selectedGroup.BindValueChanged(selected => + { + if (selected.NewValue != null) + clock.Seek(selected.NewValue.Time); + }); } public class ControlPointList : CompositeDrawable From 4ce3450cfc6a003c3ff220d6f57cd566d49613f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 14:08:52 +0900 Subject: [PATCH 67/77] Move button implementation to OsuButton --- .../Editor/TestSceneEditorComposeTimeline.cs | 4 +- osu.Game/Graphics/UserInterface/OsuButton.cs | 106 ++++++++++++++---- osu.Game/Overlays/Settings/SidebarButton.cs | 28 +---- osu.Game/Overlays/SettingsPanel.cs | 4 +- 4 files changed, 87 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index b6b2b2f92d..6e5b3b93e9 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -10,9 +10,9 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Graphics; @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Editor } } - private class StartStopButton : BasicButton + private class StartStopButton : OsuButton { private IAdjustableClock adjustableClock; private bool started; diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 319c3dd0b4..2750e61f0d 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -17,55 +19,106 @@ namespace osu.Game.Graphics.UserInterface /// /// A button with added default sound effects. /// - public class OsuButton : BasicButton + public class OsuButton : Button { - private Box hover; + public string Text + { + get => SpriteText?.Text; + set + { + if (SpriteText != null) + SpriteText.Text = value; + } + } + + private Color4? backgroundColour; + + public Color4 BackgroundColour + { + set + { + backgroundColour = value; + Background.FadeColour(value); + } + } + + protected override Container Content { get; } + + protected Box Hover; + protected Box Background; + protected SpriteText SpriteText; public OsuButton() { Height = 40; - Content.Masking = true; - Content.CornerRadius = 5; + AddInternal(Content = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + CornerRadius = 5, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + Background = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }, + Hover = new Box + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = Color4.White.Opacity(.1f), + Blending = BlendingParameters.Additive, + Depth = float.MinValue + }, + SpriteText = CreateText(), + new HoverClickSounds(HoverSampleSet.Loud), + } + }); + + Enabled.BindValueChanged(enabledChanged, true); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - BackgroundColour = colours.BlueDark; - - AddRange(new Drawable[] - { - hover = new Box - { - RelativeSizeAxes = Axes.Both, - Blending = BlendingParameters.Additive, - Colour = Color4.White.Opacity(0.1f), - Alpha = 0, - Depth = -1 - }, - new HoverClickSounds(HoverSampleSet.Loud), - }); + if (backgroundColour == null) + BackgroundColour = colours.BlueDark; Enabled.ValueChanged += enabledChanged; Enabled.TriggerChange(); } - private void enabledChanged(ValueChangedEvent e) + protected override bool OnClick(ClickEvent e) { - this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint); + if (Enabled.Value) + { + Debug.Assert(backgroundColour != null); + Background.FlashColour(backgroundColour.Value, 200); + } + + return base.OnClick(e); } protected override bool OnHover(HoverEvent e) { - hover.FadeIn(200); - return true; + if (Enabled.Value) + Hover.FadeIn(200, Easing.OutQuint); + + return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - hover.FadeOut(200); base.OnHoverLost(e); + + Hover.FadeOut(300); } protected override bool OnMouseDown(MouseDownEvent e) @@ -80,12 +133,17 @@ namespace osu.Game.Graphics.UserInterface return base.OnMouseUp(e); } - protected override SpriteText CreateText() => new OsuSpriteText + protected virtual SpriteText CreateText() => new OsuSpriteText { Depth = -1, Origin = Anchor.Centre, Anchor = Anchor.Centre, Font = OsuFont.GetFont(weight: FontWeight.Bold) }; + + private void enabledChanged(ValueChangedEvent e) + { + this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint); + } } } diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index cdb4dc463a..5930c89d29 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -9,21 +8,18 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { - public class SidebarButton : BasicButton + public class SidebarButton : OsuButton { private readonly SpriteIcon drawableIcon; private readonly SpriteText headerText; private readonly Box selectionIndicator; private readonly Container text; - public new Action Action; private SettingsSection section; @@ -62,9 +58,6 @@ namespace osu.Game.Overlays.Settings public SidebarButton() { - BackgroundColour = OsuColour.Gray(60); - Background.Alpha = 0; - Height = Sidebar.DEFAULT_WIDTH; RelativeSizeAxes = Axes.X; @@ -99,7 +92,6 @@ namespace osu.Game.Overlays.Settings Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, }, - new HoverClickSounds(HoverSampleSet.Loud), }); } @@ -108,23 +100,5 @@ namespace osu.Game.Overlays.Settings { selectionIndicator.Colour = colours.Yellow; } - - protected override bool OnClick(ClickEvent e) - { - Action?.Invoke(section); - return base.OnClick(e); - } - - protected override bool OnHover(HoverEvent e) - { - Background.FadeTo(0.4f, 200); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - Background.FadeTo(0, 200); - base.OnHoverLost(e); - } } } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index d028664fe0..2948231c4b 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -123,9 +123,9 @@ namespace osu.Game.Overlays var button = new SidebarButton { Section = section, - Action = s => + Action = () => { - SectionsContainer.ScrollTo(s); + SectionsContainer.ScrollTo(section); Sidebar.State = ExpandedState.Contracted; }, }; From ebfb5d050d740e9df7ecd1d14d6ce716d57039c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 14:36:43 +0900 Subject: [PATCH 68/77] Move section update code to abstract method to avoid incorrect BindValue usage --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 7 +++---- osu.Game/Screens/Edit/Timing/EffectSection.cs | 12 ++++-------- osu.Game/Screens/Edit/Timing/SampleSection.cs | 12 ++++-------- osu.Game/Screens/Edit/Timing/Section.cs | 4 ++++ osu.Game/Screens/Edit/Timing/TimingSection.cs | 12 ++++-------- 5 files changed, 19 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index cd33425ee5..05e45014eb 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; @@ -20,11 +21,9 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override void LoadComplete() + protected override void OnControlPointChanged(ValueChangedEvent point) { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier:0.##}x"; }); + multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier:0.##}x"; } protected override DifficultyControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index bafdfd0b0d..2b37ffaaaf 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; @@ -22,15 +23,10 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override void LoadComplete() + protected override void OnControlPointChanged(ValueChangedEvent point) { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - kiai.Text = $"Kiai: {(point.NewValue?.KiaiMode == true ? "on" : "off")}"; - omitBarLine.Text = $"Skip Bar Line: {(point.NewValue?.OmitFirstBarLine == true ? "on" : "off")}"; - }); + kiai.Text = $"Kiai: {(point.NewValue?.KiaiMode == true ? "on" : "off")}"; + omitBarLine.Text = $"Skip Bar Line: {(point.NewValue?.OmitFirstBarLine == true ? "on" : "off")}"; } protected override EffectControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index e665db0a4d..1bedb1ff42 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; @@ -22,15 +23,10 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override void LoadComplete() + protected override void OnControlPointChanged(ValueChangedEvent point) { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bank.Text = $"Bank: {point.NewValue?.SampleBank}"; - volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; - }); + bank.Text = $"Bank: {point.NewValue?.SampleBank}"; + volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; } protected override SampleControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs index c6140ff497..ccf1582486 100644 --- a/osu.Game/Screens/Edit/Timing/Section.cs +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -119,8 +119,12 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.Value = points.NewValue?.ControlPoints.OfType().FirstOrDefault(); checkbox.Current.Value = ControlPoint.Value != null; }, true); + + ControlPoint.BindValueChanged(OnControlPointChanged, true); } + protected abstract void OnControlPointChanged(ValueChangedEvent point); + protected abstract T CreatePoint(); } } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 8609da4c4d..15007ffdb3 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; @@ -22,15 +23,10 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override void LoadComplete() + protected override void OnControlPointChanged(ValueChangedEvent point) { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bpm.Text = $"BPM: {point.NewValue?.BPM:0.##}"; - timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; - }); + bpm.Text = $"BPM: {point.NewValue?.BPM:0.##}"; + timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; } protected override TimingControlPoint CreatePoint() From 5e416bd18d5dc77b458cc1d1d59db2372d1b6ec8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 14:47:31 +0900 Subject: [PATCH 69/77] Fix tournament client crashing if beatmap added with an ID of zero --- osu.Game.Tournament/TournamentGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index dbfa70704b..21552882ef 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -215,7 +215,7 @@ namespace osu.Game.Tournament foreach (var r in ladder.Rounds) foreach (var b in r.Beatmaps) - if (b.BeatmapInfo == null) + if (b.BeatmapInfo == null && b.ID > 0) { var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); req.Perform(API); From 0b09fb293e0ad9bea8657a84e19dd135c1eb566d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 15:09:08 +0900 Subject: [PATCH 70/77] Fix background being coloured --- osu.Game/Overlays/Settings/SidebarButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index 5930c89d29..68836bc6b3 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -61,6 +61,8 @@ namespace osu.Game.Overlays.Settings Height = Sidebar.DEFAULT_WIDTH; RelativeSizeAxes = Axes.X; + BackgroundColour = Color4.Black; + AddRange(new Drawable[] { text = new Container From 020b08b4500c811702380b2c3286427a36d2e3b3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 14:55:05 +0900 Subject: [PATCH 71/77] Initial implementation of limited distance snap --- .../TestSceneOsuDistanceSnapGrid.cs | 2 +- .../Edit/OsuDistanceSnapGrid.cs | 4 +-- .../Edit/OsuHitObjectComposer.cs | 33 ++++++++++++------- .../Editor/TestSceneDistanceSnapGrid.cs | 2 +- .../Components/CircularDistanceSnapGrid.cs | 12 ++++--- .../Compose/Components/DistanceSnapGrid.cs | 21 +++++++++++- 6 files changed, 52 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index a9a6097182..aba1d8fa1d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Tests public new float DistanceSpacing => base.DistanceSpacing; public TestOsuDistanceSnapGrid(OsuHitObject hitObject) - : base(hitObject) + : base(hitObject, null) { } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index 79cd51a7f4..9b00204d51 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -8,8 +8,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { - public OsuDistanceSnapGrid(OsuHitObject hitObject) - : base(hitObject, hitObject.StackedEndPosition) + public OsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject) + : base(hitObject, nextHitObject, hitObject.StackedEndPosition) { Masking = true; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index fcf2772219..a021f70598 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; @@ -60,25 +61,33 @@ namespace osu.Game.Rulesets.Osu.Edit var objects = selectedHitObjects.ToList(); if (objects.Count == 0) + return createGrid(h => h.StartTime <= EditorClock.CurrentTime); + + double minTime = objects.Min(h => h.StartTime); + return createGrid(h => h.StartTime < minTime); + } + + private OsuDistanceSnapGrid createGrid(Func hitObjectSelector) + { + int lastIndex = -1; + + for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++) { - var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime <= EditorClock.CurrentTime); + HitObject hitObject = EditorBeatmap.HitObjects[i]; - if (lastObject == null) - return null; + if (!hitObjectSelector(hitObject)) + break; - return new OsuDistanceSnapGrid(lastObject); + lastIndex = i; } - else - { - double minTime = objects.Min(h => h.StartTime); - var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime < minTime); + if (lastIndex == -1) + return null; - if (lastObject == null) - return null; + OsuHitObject lastObject = EditorBeatmap.HitObjects[lastIndex]; + OsuHitObject nextObject = lastIndex == EditorBeatmap.HitObjects.Count - 1 ? null : EditorBeatmap.HitObjects[lastIndex + 1]; - return new OsuDistanceSnapGrid(lastObject); - } + return new OsuDistanceSnapGrid(lastObject, nextObject); } } } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index b8c31d5dbb..0681dff528 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Editor public new float DistanceSpacing => base.DistanceSpacing; public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) - : base(hitObject, centrePosition) + : base(hitObject, null, centrePosition) { } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index f45115e1e4..0f2bae6305 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -12,8 +12,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract class CircularDistanceSnapGrid : DistanceSnapGrid { - protected CircularDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) - : base(hitObject, centrePosition) + protected CircularDistanceSnapGrid(HitObject hitObject, HitObject nextHitObject, Vector2 centrePosition) + : base(hitObject, nextHitObject, centrePosition) { } @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X); float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y); float maxDistance = new Vector2(dx, dy).Length; - int requiredCircles = (int)(maxDistance / DistanceSpacing); + int requiredCircles = Math.Min(MaxIntervals, (int)(maxDistance / DistanceSpacing)); for (int i = 0; i < requiredCircles; i++) { @@ -65,15 +65,17 @@ namespace osu.Game.Screens.Edit.Compose.Components public override (Vector2 position, double time) GetSnappedPosition(Vector2 position) { - Vector2 direction = position - CentrePosition; + if (MaxIntervals == 0) + return (CentrePosition, StartTime); + Vector2 direction = position - CentrePosition; if (direction == Vector2.Zero) direction = new Vector2(0.001f, 0.001f); float distance = direction.Length; float radius = DistanceSpacing; - int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); + int radialCount = MathHelper.Clamp((int)Math.Round(distance / radius), 1, MaxIntervals); Vector2 normalisedDirection = direction * new Vector2(1f / distance); Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius; diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 193474093f..475b6e7274 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Caching; using osu.Framework.Graphics; @@ -29,6 +30,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected double StartTime { get; private set; } + /// + /// The maximum number of distance snapping intervals allowed. + /// + protected int MaxIntervals { get; private set; } + /// /// The position which the grid is centred on. /// The first beat snapping tick is located at + in the desired direction. @@ -49,12 +55,15 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly Cached gridCache = new Cached(); private readonly HitObject hitObject; + private readonly HitObject nextHitObject; - protected DistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) + protected DistanceSnapGrid(HitObject hitObject, [CanBeNull] HitObject nextHitObject, Vector2 centrePosition) { this.hitObject = hitObject; + this.nextHitObject = nextHitObject; CentrePosition = centrePosition; + RelativeSizeAxes = Axes.Both; } @@ -74,6 +83,16 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateSpacing() { DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime); + + if (nextHitObject == null) + MaxIntervals = int.MaxValue; + else + { + // +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors + double maxDuration = nextHitObject.StartTime - StartTime + 1; + MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing)); + } + gridCache.Invalidate(); } From 2588534eda26d2a00f6cf070f0d150a9ee556eb1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 16:04:20 +0900 Subject: [PATCH 72/77] Skin entire selection, add xmldocs --- .../Edit/OsuHitObjectComposer.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index a021f70598..812afaaa24 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -64,30 +64,37 @@ namespace osu.Game.Rulesets.Osu.Edit return createGrid(h => h.StartTime <= EditorClock.CurrentTime); double minTime = objects.Min(h => h.StartTime); - return createGrid(h => h.StartTime < minTime); + return createGrid(h => h.StartTime < minTime, objects.Count + 1); } - private OsuDistanceSnapGrid createGrid(Func hitObjectSelector) + /// + /// Creates a grid from the last matching a predicate to a target . + /// + /// A predicate that matches s where the grid can start from. + /// Only the last matching the predicate is used. + /// An offset from the selected via at which the grid should stop. + /// The from a selected to a target . + private OsuDistanceSnapGrid createGrid(Func sourceSelector, int targetOffset = 1) { - int lastIndex = -1; + if (targetOffset < 1) throw new ArgumentOutOfRangeException(nameof(targetOffset)); + + int sourceIndex = -1; for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++) { - HitObject hitObject = EditorBeatmap.HitObjects[i]; - - if (!hitObjectSelector(hitObject)) + if (!sourceSelector(EditorBeatmap.HitObjects[i])) break; - lastIndex = i; + sourceIndex = i; } - if (lastIndex == -1) + if (sourceIndex == -1) return null; - OsuHitObject lastObject = EditorBeatmap.HitObjects[lastIndex]; - OsuHitObject nextObject = lastIndex == EditorBeatmap.HitObjects.Count - 1 ? null : EditorBeatmap.HitObjects[lastIndex + 1]; + OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex]; + OsuHitObject targetObject = sourceIndex + targetOffset < EditorBeatmap.HitObjects.Count ? EditorBeatmap.HitObjects[sourceIndex + targetOffset] : null; - return new OsuDistanceSnapGrid(lastObject, nextObject); + return new OsuDistanceSnapGrid(sourceObject, targetObject); } } } From d985d048574ef01800fd3973d65583e273d4c45a Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Wed, 6 Nov 2019 14:07:05 +0700 Subject: [PATCH 73/77] Add background colour to music player ProgressBar --- osu.Game/Overlays/NowPlayingOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 6b79f2af07..8c7e717a82 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -174,6 +174,7 @@ namespace osu.Game.Overlays Anchor = Anchor.BottomCentre, Height = progress_height, FillColour = colours.Yellow, + BackgroundColour = colours.YellowDarker.Opacity(0.5f), OnSeek = musicController.SeekTo } }, From 02c21a1379e24a7ec2bb4c2b47c2c6efbcd0a103 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Wed, 6 Nov 2019 14:11:47 +0700 Subject: [PATCH 74/77] Make progress bar hoverable --- osu.Game/Overlays/NowPlayingOverlay.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 8c7e717a82..4f73cbfacd 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -168,11 +168,11 @@ namespace osu.Game.Overlays }, } }, - progressBar = new ProgressBar + progressBar = new HoverableProgressBar { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, - Height = progress_height, + Height = progress_height / 2, FillColour = colours.Yellow, BackgroundColour = colours.YellowDarker.Opacity(0.5f), OnSeek = musicController.SeekTo @@ -402,5 +402,20 @@ namespace osu.Game.Overlays return base.OnDragEnd(e); } } + + private class HoverableProgressBar : ProgressBar + { + protected override bool OnHover(HoverEvent e) + { + this.ResizeHeightTo(progress_height, 500, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + this.ResizeHeightTo(progress_height / 2, 500, Easing.OutQuint); + base.OnHoverLost(e); + } + } } } From b83ceab1c1b2ba594efa02d166f364a32142e4ac Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 16:20:13 +0900 Subject: [PATCH 75/77] Add tests --- .../TestSceneOsuDistanceSnapGrid.cs | 47 ++++++++++++++----- .../Editor/TestSceneDistanceSnapGrid.cs | 39 +++++++++++---- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index aba1d8fa1d..eff4d919b0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -42,11 +42,19 @@ namespace osu.Game.Rulesets.Osu.Tests [Cached(typeof(IDistanceSnapProvider))] private readonly SnapProvider snapProvider = new SnapProvider(); - private readonly TestOsuDistanceSnapGrid grid; + private TestOsuDistanceSnapGrid grid; public TestSceneOsuDistanceSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + } + + [SetUp] + public void Setup() => Schedule(() => + { + editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); Children = new Drawable[] { @@ -58,14 +66,6 @@ namespace osu.Game.Rulesets.Osu.Tests grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } }; - } - - [SetUp] - public void Setup() => Schedule(() => - { - editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; - editorBeatmap.ControlPointInfo.Clear(); - editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); }); [TestCase(1)] @@ -102,6 +102,27 @@ namespace osu.Game.Rulesets.Osu.Tests assertSnappedDistance((float)beat_length * 2); } + [Test] + public void TestLimitedDistance() + { + AddStep("create limited grid", () => + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }, new HitCircle { StartTime = 200 }), + new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } + }; + }); + + AddStep("move mouse outside grid", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 3f))); + assertSnappedDistance((float)beat_length * 2); + } + private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () => { Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)).position; @@ -152,8 +173,8 @@ namespace osu.Game.Rulesets.Osu.Tests { public new float DistanceSpacing => base.DistanceSpacing; - public TestOsuDistanceSnapGrid(OsuHitObject hitObject) - : base(hitObject, null) + public TestOsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject = null) + : base(hitObject, nextHitObject) { } } @@ -164,9 +185,9 @@ namespace osu.Game.Rulesets.Osu.Tests public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length; - public float DurationToDistance(double referenceTime, double duration) => 0; + public float DurationToDistance(double referenceTime, double duration) => (float)duration; - public double DistanceToDuration(double referenceTime, float distance) => 0; + public double DistanceToDuration(double referenceTime, float distance) => distance; public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index 0681dff528..e4c987923c 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -32,7 +32,11 @@ namespace osu.Game.Tests.Visual.Editor { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); + } + [SetUp] + public void Setup() => Schedule(() => + { Children = new Drawable[] { new Box @@ -42,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editor }, new TestDistanceSnapGrid(new HitObject(), grid_position) }; - } + }); [TestCase(1)] [TestCase(2)] @@ -57,12 +61,29 @@ namespace osu.Game.Tests.Visual.Editor AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor); } + [Test] + public void TestLimitedDistance() + { + AddStep("create limited grid", () => + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + new TestDistanceSnapGrid(new HitObject(), grid_position, new HitObject { StartTime = 100 }) + }; + }); + } + private class TestDistanceSnapGrid : DistanceSnapGrid { public new float DistanceSpacing => base.DistanceSpacing; - public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) - : base(hitObject, null, centrePosition) + public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition, HitObject nextHitObject = null) + : base(hitObject, nextHitObject, centrePosition) { } @@ -77,7 +98,7 @@ namespace osu.Game.Tests.Visual.Editor int beatIndex = 0; - for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth; s += DistanceSpacing, beatIndex++) + for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) { AddInternal(new Circle { @@ -90,7 +111,7 @@ namespace osu.Game.Tests.Visual.Editor beatIndex = 0; - for (float s = centrePosition.X - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++) + for (float s = centrePosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) { AddInternal(new Circle { @@ -103,7 +124,7 @@ namespace osu.Game.Tests.Visual.Editor beatIndex = 0; - for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight; s += DistanceSpacing, beatIndex++) + for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) { AddInternal(new Circle { @@ -116,7 +137,7 @@ namespace osu.Game.Tests.Visual.Editor beatIndex = 0; - for (float s = centrePosition.Y - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++) + for (float s = centrePosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) { AddInternal(new Circle { @@ -138,9 +159,9 @@ namespace osu.Game.Tests.Visual.Editor public float GetBeatSnapDistanceAt(double referenceTime) => 10; - public float DurationToDistance(double referenceTime, double duration) => 0; + public float DurationToDistance(double referenceTime, double duration) => (float)duration; - public double DistanceToDuration(double referenceTime, float distance) => 0; + public double DistanceToDuration(double referenceTime, float distance) => distance; public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; From ee544e174a3971716fcd9a6ea0f284c2cb213da3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 16:33:42 +0900 Subject: [PATCH 76/77] Group -> Connection --- .../TestSceneFollowPoints.cs | 4 +- ...PointGroup.cs => FollowPointConnection.cs} | 6 +- .../Connections/FollowPointRenderer.cs | 76 +++++++++---------- 3 files changed, 43 insertions(+), 43 deletions(-) rename osu.Game.Rulesets.Osu/Objects/Drawables/Connections/{FollowPointGroup.cs => FollowPointConnection.cs} (95%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 79a1bb579f..94ca2d4cd1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertGroups() { - AddAssert("has correct group count", () => followPointRenderer.Groups.Count == hitObjectContainer.Count); + AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count); AddAssert("group endpoints are correct", () => { for (int i = 0; i < hitObjectContainer.Count; i++) @@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Osu.Tests private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; - private FollowPointGroup getGroup(int index) => followPointRenderer.Groups[index]; + private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index]; private class TestHitObjectContainer : Container { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs similarity index 95% rename from osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 168d2b8532..1e032eb977 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// Visualises the s between two s. /// - public class FollowPointGroup : CompositeDrawable + public class FollowPointConnection : CompositeDrawable { // Todo: These shouldn't be constants private const int spacing = 32; @@ -32,10 +32,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public readonly DrawableOsuHitObject Start; /// - /// Creates a new . + /// Creates a new . /// /// The which s will exit from. - public FollowPointGroup([NotNull] DrawableOsuHitObject start) + public FollowPointConnection([NotNull] DrawableOsuHitObject start) { Start = start; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index afd86e004d..00ef0ba0d0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -10,16 +10,16 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { /// - /// Visualises groups of s. + /// Visualises connections between s. /// public class FollowPointRenderer : CompositeDrawable { /// - /// All the s contained by this . + /// All the s contained by this . /// - internal IReadOnlyList Groups => groups; + internal IReadOnlyList Connections => connections; - private readonly List groups = new List(); + private readonly List connections = new List(); public override bool RemoveCompletedTransforms => false; @@ -29,84 +29,84 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// The to add s for. public void AddFollowPoints(DrawableOsuHitObject hitObject) - => addGroup(new FollowPointGroup(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g)))); + => addConnection(new FollowPointConnection(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g)))); /// /// Removes the s around a . /// This includes s leading into , and s exiting . /// /// The to remove s for. - public void RemoveFollowPoints(DrawableOsuHitObject hitObject) => removeGroup(groups.Single(g => g.Start == hitObject)); + public void RemoveFollowPoints(DrawableOsuHitObject hitObject) => removeGroup(connections.Single(g => g.Start == hitObject)); /// - /// Adds a to this . + /// Adds a to this . /// - /// The to add. - /// The index of in . - private int addGroup(FollowPointGroup group) + /// The to add. + /// The index of in . + private int addConnection(FollowPointConnection connection) { - AddInternal(group); + AddInternal(connection); - // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding groups - int index = groups.AddInPlace(group, Comparer.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value))); + // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections + int index = connections.AddInPlace(connection, Comparer.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value))); - if (index < groups.Count - 1) + if (index < connections.Count - 1) { - // Update the group's end point to the next group's start point + // Update the connection's end point to the next connection's start point // h1 -> -> -> h2 - // group nextGroup + // connection nextGroup - FollowPointGroup nextGroup = groups[index + 1]; - group.End = nextGroup.Start; + FollowPointConnection nextConnection = connections[index + 1]; + connection.End = nextConnection.Start; } else { // The end point may be non-null during re-ordering - group.End = null; + connection.End = null; } if (index > 0) { - // Update the previous group's end point to the current group's start point + // Update the previous connection's end point to the current connection's start point // h1 -> -> -> h2 - // prevGroup group + // prevGroup connection - FollowPointGroup previousGroup = groups[index - 1]; - previousGroup.End = group.Start; + FollowPointConnection previousConnection = connections[index - 1]; + previousConnection.End = connection.Start; } return index; } /// - /// Removes a from this . + /// Removes a from this . /// - /// The to remove. - /// Whether was removed. - private bool removeGroup(FollowPointGroup group) + /// The to remove. + /// Whether was removed. + private bool removeGroup(FollowPointConnection connection) { - RemoveInternal(group); + RemoveInternal(connection); - int index = groups.IndexOf(group); + int index = connections.IndexOf(connection); if (index > 0) { - // Update the previous group's end point to the next group's start point + // Update the previous connection's end point to the next connection's start point // h1 -> -> -> h2 -> -> -> h3 - // prevGroup group nextGroup - // The current group's end point is used since there may not be a next group - FollowPointGroup previousGroup = groups[index - 1]; - previousGroup.End = group.End; + // prevGroup connection nextGroup + // The current connection's end point is used since there may not be a next connection + FollowPointConnection previousConnection = connections[index - 1]; + previousConnection.End = connection.End; } - return groups.Remove(group); + return connections.Remove(connection); } - private void onStartTimeChanged(FollowPointGroup group) + private void onStartTimeChanged(FollowPointConnection connection) { // Naive but can be improved if performance becomes an issue - removeGroup(group); - addGroup(group); + removeGroup(connection); + addConnection(connection); } } } From 7b5b3ff15c21d378d607b2e4cccafe2a8f366bc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 16:36:12 +0900 Subject: [PATCH 77/77] Remove unused returns --- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 00ef0ba0d0..be192080f9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// The to add. /// The index of in . - private int addConnection(FollowPointConnection connection) + private void addConnection(FollowPointConnection connection) { AddInternal(connection); @@ -74,8 +74,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections FollowPointConnection previousConnection = connections[index - 1]; previousConnection.End = connection.Start; } - - return index; } /// @@ -83,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// The to remove. /// Whether was removed. - private bool removeGroup(FollowPointConnection connection) + private void removeGroup(FollowPointConnection connection) { RemoveInternal(connection); @@ -99,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections previousConnection.End = connection.End; } - return connections.Remove(connection); + connections.Remove(connection); } private void onStartTimeChanged(FollowPointConnection connection)