From 2b2cfd91a6a48b7804870f4dfb19753372b54e9a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 28 Jan 2020 17:59:14 +0900 Subject: [PATCH 01/47] Initial re-implementation using rearrangeable list --- .../UserInterface/TestScenePlaylistOverlay.cs | 59 ++++ osu.Game/Overlays/Music/PlaylistList.cs | 301 ++++-------------- osu.Game/Overlays/Music/PlaylistOverlay.cs | 12 +- 3 files changed, 135 insertions(+), 237 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs new file mode 100644 index 0000000000..a470244f53 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.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 System; +using NUnit.Framework; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Utils; +using osu.Game.Beatmaps; +using osu.Game.Overlays.Music; +using osuTK; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestScenePlaylistOverlay : OsuTestScene + { + private readonly BindableList beatmapSets = new BindableList(); + + [SetUp] + public void Setup() => Schedule(() => + { + PlaylistOverlay overlay; + + Child = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(300, 500), + Child = overlay = new PlaylistOverlay + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + State = { Value = Visibility.Visible } + } + }; + + beatmapSets.Clear(); + + for (int i = 0; i < 100; i++) + { + beatmapSets.Add(new BeatmapSetInfo + { + Metadata = new BeatmapMetadata + { + // Create random metadata, then we can check if sorting works based on these + Artist = "Some Artist " + RNG.Next(0, 9), + Title = $"Some Song {i + 1}", + AuthorString = "Some Guy " + RNG.Next(0, 9), + }, + DateAdded = DateTimeOffset.UtcNow, + }); + } + + overlay.BeatmapSets.BindTo(beatmapSets); + }); + } +} diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 7bdcab6dff..5c091a21db 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -6,30 +6,16 @@ using System.Collections.Generic; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Input.Events; +using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; -using osu.Game.Graphics.Containers; -using osuTK; namespace osu.Game.Overlays.Music { - public class PlaylistList : CompositeDrawable + public class PlaylistList2 : BasicRearrangeableListContainer { - public Action Selected; - - private readonly ItemsScrollContainer items; - - public PlaylistList() - { - InternalChild = items = new ItemsScrollContainer - { - RelativeSizeAxes = Axes.Both, - Selected = set => Selected?.Invoke(set), - }; - } + public readonly BindableList BeatmapSets = new BindableList(); public new MarginPadding Padding { @@ -37,232 +23,81 @@ namespace osu.Game.Overlays.Music set => base.Padding = value; } - public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet; - - public void Filter(string searchTerm) => items.SearchTerm = searchTerm; - - private class ItemsScrollContainer : OsuScrollContainer + [BackgroundDependencyLoader] + private void load() { - public Action Selected; + BeatmapSets.ItemsAdded += addBeatmapSets; + BeatmapSets.ItemsRemoved += removeBeatmapSets; + } - private readonly SearchContainer search; - private readonly FillFlowContainer items; + public void Filter(string searchTerm) => ((PlaylistListFlowContainer)ListContainer).SearchTerm = searchTerm; - private readonly IBindable beatmapBacking = new Bindable(); + public BeatmapSetInfo FirstVisibleSet => ListContainer.FlowingChildren.Cast().FirstOrDefault(i => i.MatchingFilter)?.Model.BeatmapSetInfo; - private IBindableList beatmaps; + private void addBeatmapSets(IEnumerable sets) => Schedule(() => + { + foreach (var item in sets) + AddItem(new PlaylistListItem(item)); + }); - [Resolved] - private MusicController musicController { get; set; } + private void removeBeatmapSets(IEnumerable sets) => Schedule(() => + { + foreach (var item in sets) + RemoveItem(ListContainer.Children.Select(d => d.Model).FirstOrDefault(m => m.BeatmapSetInfo == item)); + }); - public ItemsScrollContainer() + protected override BasicDrawableRearrangeableListItem CreateBasicItem(PlaylistListItem item) => new DrawablePlaylistListItem(item); + + protected override FillFlowContainer CreateListFillFlowContainer() => new PlaylistListFlowContainer + { + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint + }; + } + + public class PlaylistListFlowContainer : SearchContainer.DrawableRearrangeableListItem> + { + } + + public class PlaylistListItem : IEquatable + { + public readonly BeatmapSetInfo BeatmapSetInfo; + + public PlaylistListItem(BeatmapSetInfo beatmapSetInfo) + { + BeatmapSetInfo = beatmapSetInfo; + } + + public override string ToString() => BeatmapSetInfo.ToString(); + + public bool Equals(PlaylistListItem other) => BeatmapSetInfo.Equals(other?.BeatmapSetInfo); + } + + public class DrawablePlaylistListItem : BasicRearrangeableListContainer.BasicDrawableRearrangeableListItem, IFilterable + { + public DrawablePlaylistListItem(PlaylistListItem item) + : base(item) + { + FilterTerms = item.BeatmapSetInfo.Metadata.SearchableTerms; + } + + public IEnumerable FilterTerms { get; } + + private bool matching = true; + + public bool MatchingFilter + { + get => matching; + set { - Children = new Drawable[] - { - search = new SearchContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - items = new ItemSearchContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - }, - } - } - }; - } + if (matching == value) return; - [BackgroundDependencyLoader] - private void load(IBindable beatmap) - { - beatmaps = musicController.BeatmapSets.GetBoundCopy(); - beatmaps.ItemsAdded += i => i.ForEach(addBeatmapSet); - beatmaps.ItemsRemoved += i => i.ForEach(removeBeatmapSet); - beatmaps.ForEach(addBeatmapSet); + matching = value; - beatmapBacking.BindTo(beatmap); - beatmapBacking.ValueChanged += _ => Scheduler.AddOnce(updateSelectedSet); - } - - private void addBeatmapSet(BeatmapSetInfo obj) - { - if (obj == draggedItem?.BeatmapSetInfo) return; - - Schedule(() => items.Insert(items.Count - 1, new PlaylistItem(obj) { OnSelect = set => Selected?.Invoke(set) })); - } - - private void removeBeatmapSet(BeatmapSetInfo obj) - { - if (obj == draggedItem?.BeatmapSetInfo) return; - - Schedule(() => - { - var itemToRemove = items.FirstOrDefault(i => i.BeatmapSetInfo.ID == obj.ID); - if (itemToRemove != null) - items.Remove(itemToRemove); - }); - } - - private void updateSelectedSet() - { - foreach (PlaylistItem s in items.Children) - { - s.Selected = s.BeatmapSetInfo.ID == beatmapBacking.Value.BeatmapSetInfo?.ID; - if (s.Selected) - ScrollIntoView(s); - } - } - - public string SearchTerm - { - get => search.SearchTerm; - set => search.SearchTerm = value; - } - - public BeatmapSetInfo FirstVisibleSet => items.FirstOrDefault(i => i.MatchingFilter)?.BeatmapSetInfo; - - private Vector2 nativeDragPosition; - private PlaylistItem draggedItem; - - private int? dragDestination; - - protected override bool OnDragStart(DragStartEvent e) - { - nativeDragPosition = e.ScreenSpaceMousePosition; - draggedItem = items.FirstOrDefault(d => d.IsDraggable); - return draggedItem != null || base.OnDragStart(e); - } - - protected override void OnDrag(DragEvent e) - { - nativeDragPosition = e.ScreenSpaceMousePosition; - - if (draggedItem == null) - base.OnDrag(e); - } - - protected override void OnDragEnd(DragEndEvent e) - { - nativeDragPosition = e.ScreenSpaceMousePosition; - - if (draggedItem == null) - { - base.OnDragEnd(e); - return; - } - - if (dragDestination != null) - musicController.ChangeBeatmapSetPosition(draggedItem.BeatmapSetInfo, dragDestination.Value); - - draggedItem = null; - dragDestination = null; - } - - protected override void Update() - { - base.Update(); - - if (draggedItem == null) - return; - - updateScrollPosition(); - updateDragPosition(); - } - - private void updateScrollPosition() - { - const float start_offset = 10; - const double max_power = 50; - const double exp_base = 1.05; - - var localPos = ToLocalSpace(nativeDragPosition); - - if (localPos.Y < start_offset) - { - if (Current <= 0) - return; - - var power = Math.Min(max_power, Math.Abs(start_offset - localPos.Y)); - ScrollBy(-(float)Math.Pow(exp_base, power)); - } - else if (localPos.Y > DrawHeight - start_offset) - { - if (IsScrolledToEnd()) - return; - - var power = Math.Min(max_power, Math.Abs(DrawHeight - start_offset - localPos.Y)); - ScrollBy((float)Math.Pow(exp_base, power)); - } - } - - private void updateDragPosition() - { - var itemsPos = items.ToLocalSpace(nativeDragPosition); - - int srcIndex = (int)items.GetLayoutPosition(draggedItem); - - // Find the last item with position < mouse position. Note we can't directly use - // the item positions as they are being transformed - float heightAccumulator = 0; - int dstIndex = 0; - - for (; dstIndex < items.Count; dstIndex++) - { - // Using BoundingBox here takes care of scale, paddings, etc... - heightAccumulator += items[dstIndex].BoundingBox.Height; - if (heightAccumulator > itemsPos.Y) - break; - } - - dstIndex = Math.Clamp(dstIndex, 0, items.Count - 1); - - if (srcIndex == dstIndex) - return; - - if (srcIndex < dstIndex) - { - for (int i = srcIndex + 1; i <= dstIndex; i++) - items.SetLayoutPosition(items[i], i - 1); - } - else - { - for (int i = dstIndex; i < srcIndex; i++) - items.SetLayoutPosition(items[i], i + 1); - } - - items.SetLayoutPosition(draggedItem, dstIndex); - dragDestination = dstIndex; - } - - private class ItemSearchContainer : FillFlowContainer, IHasFilterableChildren - { - public IEnumerable FilterTerms => Array.Empty(); - - public bool MatchingFilter - { - set - { - if (value) - InvalidateLayout(); - } - } - - public bool FilteringActive - { - set { } - } - - public IEnumerable FilterableChildren => Children; - - public ItemSearchContainer() - { - LayoutDuration = 200; - LayoutEasing = Easing.OutQuint; - } + this.FadeTo(matching ? 1 : 0, 200); } } + + public bool FilteringActive { get; set; } } } diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index b89a577282..ff1b26e335 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -21,12 +21,14 @@ namespace osu.Game.Overlays.Music private const float transition_duration = 600; private const float playlist_height = 510; + public readonly BindableList BeatmapSets = new BindableList(); + private readonly Bindable beatmap = new Bindable(); private BeatmapManager beatmaps; private FilterControl filter; - private PlaylistList list; - + private PlaylistList2 list; + [BackgroundDependencyLoader] private void load(OsuColour colours, Bindable beatmap, BeatmapManager beatmaps) { @@ -53,11 +55,11 @@ namespace osu.Game.Overlays.Music Colour = colours.Gray3, RelativeSizeAxes = Axes.Both, }, - list = new PlaylistList + list = new PlaylistList2 { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 }, - Selected = itemSelected, + // Selected = itemSelected, }, filter = new FilterControl { @@ -70,6 +72,8 @@ namespace osu.Game.Overlays.Music }, }; + list.BeatmapSets.BindTo(BeatmapSets); + filter.Search.OnCommit = (sender, newText) => { BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault(); From ebf15c6a1ceacb45aae17b8c384eb7daf2236581 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 29 Jan 2020 12:17:03 +0900 Subject: [PATCH 02/47] General work towards completion + framework updates --- .../UserInterface/TestScenePlaylistOverlay.cs | 7 + osu.Game/Overlays/Music/PlaylistList.cs | 197 ++++++++++++++++-- osu.Game/Overlays/Music/PlaylistOverlay.cs | 12 +- osu.Game/Overlays/NowPlayingOverlay.cs | 1 + 4 files changed, 200 insertions(+), 17 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index a470244f53..986cf458ab 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -15,6 +16,12 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestScenePlaylistOverlay : OsuTestScene { + public override IReadOnlyList RequiredTypes => new Type[] + { + typeof(PlaylistOverlay), + typeof(PlaylistList) + }; + private readonly BindableList beatmapSets = new BindableList(); [SetUp] diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index 5c091a21db..f7be69e1f2 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -8,14 +8,23 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.UserInterface; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Overlays.Music { - public class PlaylistList2 : BasicRearrangeableListContainer + public class PlaylistList : RearrangeableListContainer { - public readonly BindableList BeatmapSets = new BindableList(); + public Action RequestSelection; + + public readonly Bindable SelectedSet = new Bindable(); + public readonly IBindableList BeatmapSets = new BindableList(); public new MarginPadding Padding { @@ -23,21 +32,35 @@ namespace osu.Game.Overlays.Music set => base.Padding = value; } + private readonly HashSet existingItems = new HashSet(); + [BackgroundDependencyLoader] private void load() { BeatmapSets.ItemsAdded += addBeatmapSets; BeatmapSets.ItemsRemoved += removeBeatmapSets; + + foreach (var item in BeatmapSets) + addBeatmapSet(item); } public void Filter(string searchTerm) => ((PlaylistListFlowContainer)ListContainer).SearchTerm = searchTerm; public BeatmapSetInfo FirstVisibleSet => ListContainer.FlowingChildren.Cast().FirstOrDefault(i => i.MatchingFilter)?.Model.BeatmapSetInfo; - private void addBeatmapSets(IEnumerable sets) => Schedule(() => + private void addBeatmapSets(IEnumerable sets) { - foreach (var item in sets) - AddItem(new PlaylistListItem(item)); + foreach (var set in sets) + addBeatmapSet(set); + } + + private void addBeatmapSet(BeatmapSetInfo set) => Schedule(() => + { + if (existingItems.Contains(set)) + return; + + AddItem(new PlaylistListItem(set)); + existingItems.Add(set); }); private void removeBeatmapSets(IEnumerable sets) => Schedule(() => @@ -46,16 +69,23 @@ namespace osu.Game.Overlays.Music RemoveItem(ListContainer.Children.Select(d => d.Model).FirstOrDefault(m => m.BeatmapSetInfo == item)); }); - protected override BasicDrawableRearrangeableListItem CreateBasicItem(PlaylistListItem item) => new DrawablePlaylistListItem(item); - - protected override FillFlowContainer CreateListFillFlowContainer() => new PlaylistListFlowContainer + protected override DrawableRearrangeableListItem CreateDrawable(PlaylistListItem item) => new DrawablePlaylistListItem(item) { + SelectedSet = { BindTarget = SelectedSet }, + RequestSelection = set => RequestSelection?.Invoke(set) + }; + + protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); + + protected override FillFlowContainer> CreateListFillFlowContainer() => new PlaylistListFlowContainer + { + Spacing = new Vector2(0, 3), LayoutDuration = 200, - LayoutEasing = Easing.OutQuint + LayoutEasing = Easing.OutQuint, }; } - public class PlaylistListFlowContainer : SearchContainer.DrawableRearrangeableListItem> + public class PlaylistListFlowContainer : SearchContainer> { } @@ -73,14 +103,118 @@ namespace osu.Game.Overlays.Music public bool Equals(PlaylistListItem other) => BeatmapSetInfo.Equals(other?.BeatmapSetInfo); } - public class DrawablePlaylistListItem : BasicRearrangeableListContainer.BasicDrawableRearrangeableListItem, IFilterable + public class DrawablePlaylistListItem : DrawableRearrangeableListItem, IFilterable { + private const float fade_duration = 100; + + public readonly Bindable SelectedSet = new Bindable(); + public Action RequestSelection; + + private PlaylistItemHandle handle; + private TextFlowContainer text; + private IEnumerable titleSprites; + private ILocalisedBindableString titleBind; + private ILocalisedBindableString artistBind; + + private Color4 hoverColour; + private Color4 artistColour; + public DrawablePlaylistListItem(PlaylistListItem item) : base(item) { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding { Left = 5 }; + FilterTerms = item.BeatmapSetInfo.Metadata.SearchableTerms; } + [BackgroundDependencyLoader] + private void load(OsuColour colours, LocalisationManager localisation) + { + hoverColour = colours.Yellow; + artistColour = colours.Gray9; + + InternalChild = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + new Drawable[] + { + handle = new PlaylistItemHandle + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Size = new Vector2(12), + Colour = colours.Gray5, + Alpha = 0 + }, + text = new OsuTextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Left = 5 }, + }, + } + }, + ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }; + + titleBind = localisation.GetLocalisedString(new LocalisedString((Model.BeatmapSetInfo.Metadata.TitleUnicode, Model.BeatmapSetInfo.Metadata.Title))); + artistBind = localisation.GetLocalisedString(new LocalisedString((Model.BeatmapSetInfo.Metadata.ArtistUnicode, Model.BeatmapSetInfo.Metadata.Artist))); + + artistBind.BindValueChanged(_ => recreateText(), true); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + SelectedSet.BindValueChanged(set => + { + if (set.OldValue != Model.BeatmapSetInfo && set.NewValue != Model.BeatmapSetInfo) + return; + + foreach (Drawable s in titleSprites) + s.FadeColour(set.NewValue == Model.BeatmapSetInfo ? hoverColour : Color4.White, fade_duration); + }, true); + } + + private void recreateText() + { + text.Clear(); + + //space after the title to put a space between the title and artist + titleSprites = text.AddText(titleBind.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)).OfType(); + + text.AddText(artistBind.Value, sprite => + { + sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); + sprite.Colour = artistColour; + sprite.Padding = new MarginPadding { Top = 1 }; + }); + } + + protected override bool OnClick(ClickEvent e) + { + RequestSelection?.Invoke(Model.BeatmapSetInfo); + return true; + } + + protected override bool IsDraggableAt(Vector2 screenSpacePos) => handle.HandlingDrag; + + protected override bool OnHover(HoverEvent e) + { + handle.UpdateHoverState(true); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) => handle.UpdateHoverState(false); + public IEnumerable FilterTerms { get; } private bool matching = true; @@ -99,5 +233,44 @@ namespace osu.Game.Overlays.Music } public bool FilteringActive { get; set; } + + private class PlaylistItemHandle : SpriteIcon + { + public bool HandlingDrag { get; private set; } + private bool isHovering; + + public PlaylistItemHandle() + { + Icon = FontAwesome.Solid.Bars; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + base.OnMouseDown(e); + + HandlingDrag = true; + UpdateHoverState(isHovering); + + return false; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + base.OnMouseUp(e); + + HandlingDrag = false; + UpdateHoverState(isHovering); + } + + public void UpdateHoverState(bool hovering) + { + isHovering = hovering; + + if (isHovering || HandlingDrag) + this.FadeIn(fade_duration); + else + this.FadeOut(fade_duration); + } + } } } diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index ff1b26e335..87abbd142c 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -21,14 +21,14 @@ namespace osu.Game.Overlays.Music private const float transition_duration = 600; private const float playlist_height = 510; - public readonly BindableList BeatmapSets = new BindableList(); + public readonly IBindableList BeatmapSets = new BindableList(); private readonly Bindable beatmap = new Bindable(); private BeatmapManager beatmaps; private FilterControl filter; - private PlaylistList2 list; - + private PlaylistList list; + [BackgroundDependencyLoader] private void load(OsuColour colours, Bindable beatmap, BeatmapManager beatmaps) { @@ -55,11 +55,11 @@ namespace osu.Game.Overlays.Music Colour = colours.Gray3, RelativeSizeAxes = Axes.Both, }, - list = new PlaylistList2 + list = new PlaylistList { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 }, - // Selected = itemSelected, + RequestSelection = itemSelected }, filter = new FilterControl { @@ -84,6 +84,8 @@ namespace osu.Game.Overlays.Music beatmap.Value.Track.Restart(); } }; + + beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true); } protected override void PopIn() diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 042e95c6d7..dfcf99d30c 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -183,6 +183,7 @@ namespace osu.Game.Overlays } }; + playlist.BeatmapSets.BindTo(musicController.BeatmapSets); playlist.State.ValueChanged += s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint); } From f6ba98eec0f1087d9d7214681f41b8a35c1c47bd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 30 Jan 2020 19:00:59 +0900 Subject: [PATCH 03/47] Apply refactorings for framework-side changes --- osu.Game/Overlays/Music/PlaylistList.cs | 70 +++++----------------- osu.Game/Overlays/Music/PlaylistOverlay.cs | 6 +- 2 files changed, 18 insertions(+), 58 deletions(-) diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/PlaylistList.cs index f7be69e1f2..e183c14f10 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/PlaylistList.cs @@ -19,12 +19,11 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Music { - public class PlaylistList : RearrangeableListContainer + public class PlaylistList : RearrangeableListContainer { public Action RequestSelection; public readonly Bindable SelectedSet = new Bindable(); - public readonly IBindableList BeatmapSets = new BindableList(); public new MarginPadding Padding { @@ -32,44 +31,17 @@ namespace osu.Game.Overlays.Music set => base.Padding = value; } - private readonly HashSet existingItems = new HashSet(); - - [BackgroundDependencyLoader] - private void load() - { - BeatmapSets.ItemsAdded += addBeatmapSets; - BeatmapSets.ItemsRemoved += removeBeatmapSets; - - foreach (var item in BeatmapSets) - addBeatmapSet(item); - } - public void Filter(string searchTerm) => ((PlaylistListFlowContainer)ListContainer).SearchTerm = searchTerm; - public BeatmapSetInfo FirstVisibleSet => ListContainer.FlowingChildren.Cast().FirstOrDefault(i => i.MatchingFilter)?.Model.BeatmapSetInfo; - - private void addBeatmapSets(IEnumerable sets) - { - foreach (var set in sets) - addBeatmapSet(set); - } - - private void addBeatmapSet(BeatmapSetInfo set) => Schedule(() => - { - if (existingItems.Contains(set)) - return; - - AddItem(new PlaylistListItem(set)); - existingItems.Add(set); - }); + public BeatmapSetInfo FirstVisibleSet => ListContainer.FlowingChildren.Cast().FirstOrDefault(i => i.MatchingFilter)?.Model; private void removeBeatmapSets(IEnumerable sets) => Schedule(() => { foreach (var item in sets) - RemoveItem(ListContainer.Children.Select(d => d.Model).FirstOrDefault(m => m.BeatmapSetInfo == item)); + Items.Remove(ListContainer.Children.Select(d => d.Model).FirstOrDefault(m => m == item)); }); - protected override DrawableRearrangeableListItem CreateDrawable(PlaylistListItem item) => new DrawablePlaylistListItem(item) + protected override DrawableRearrangeableListItem CreateDrawable(BeatmapSetInfo item) => new DrawablePlaylistListItem(item) { SelectedSet = { BindTarget = SelectedSet }, RequestSelection = set => RequestSelection?.Invoke(set) @@ -77,7 +49,7 @@ namespace osu.Game.Overlays.Music protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); - protected override FillFlowContainer> CreateListFillFlowContainer() => new PlaylistListFlowContainer + protected override FillFlowContainer> CreateListFillFlowContainer() => new PlaylistListFlowContainer { Spacing = new Vector2(0, 3), LayoutDuration = 200, @@ -85,25 +57,11 @@ namespace osu.Game.Overlays.Music }; } - public class PlaylistListFlowContainer : SearchContainer> + public class PlaylistListFlowContainer : SearchContainer> { } - public class PlaylistListItem : IEquatable - { - public readonly BeatmapSetInfo BeatmapSetInfo; - - public PlaylistListItem(BeatmapSetInfo beatmapSetInfo) - { - BeatmapSetInfo = beatmapSetInfo; - } - - public override string ToString() => BeatmapSetInfo.ToString(); - - public bool Equals(PlaylistListItem other) => BeatmapSetInfo.Equals(other?.BeatmapSetInfo); - } - - public class DrawablePlaylistListItem : DrawableRearrangeableListItem, IFilterable + public class DrawablePlaylistListItem : DrawableRearrangeableListItem, IFilterable { private const float fade_duration = 100; @@ -119,7 +77,7 @@ namespace osu.Game.Overlays.Music private Color4 hoverColour; private Color4 artistColour; - public DrawablePlaylistListItem(PlaylistListItem item) + public DrawablePlaylistListItem(BeatmapSetInfo item) : base(item) { RelativeSizeAxes = Axes.X; @@ -127,7 +85,7 @@ namespace osu.Game.Overlays.Music Padding = new MarginPadding { Left = 5 }; - FilterTerms = item.BeatmapSetInfo.Metadata.SearchableTerms; + FilterTerms = item.Metadata.SearchableTerms; } [BackgroundDependencyLoader] @@ -164,8 +122,8 @@ namespace osu.Game.Overlays.Music RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } }; - titleBind = localisation.GetLocalisedString(new LocalisedString((Model.BeatmapSetInfo.Metadata.TitleUnicode, Model.BeatmapSetInfo.Metadata.Title))); - artistBind = localisation.GetLocalisedString(new LocalisedString((Model.BeatmapSetInfo.Metadata.ArtistUnicode, Model.BeatmapSetInfo.Metadata.Artist))); + titleBind = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.TitleUnicode, Model.Metadata.Title))); + artistBind = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.ArtistUnicode, Model.Metadata.Artist))); artistBind.BindValueChanged(_ => recreateText(), true); } @@ -176,11 +134,11 @@ namespace osu.Game.Overlays.Music SelectedSet.BindValueChanged(set => { - if (set.OldValue != Model.BeatmapSetInfo && set.NewValue != Model.BeatmapSetInfo) + if (set.OldValue != Model && set.NewValue != Model) return; foreach (Drawable s in titleSprites) - s.FadeColour(set.NewValue == Model.BeatmapSetInfo ? hoverColour : Color4.White, fade_duration); + s.FadeColour(set.NewValue == Model ? hoverColour : Color4.White, fade_duration); }, true); } @@ -201,7 +159,7 @@ namespace osu.Game.Overlays.Music protected override bool OnClick(ClickEvent e) { - RequestSelection?.Invoke(Model.BeatmapSetInfo); + RequestSelection?.Invoke(Model); return true; } diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 87abbd142c..a814712907 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -21,7 +21,9 @@ namespace osu.Game.Overlays.Music private const float transition_duration = 600; private const float playlist_height = 510; - public readonly IBindableList BeatmapSets = new BindableList(); + public IBindableList BeatmapSets => beatmapSets; + + private readonly BindableList beatmapSets = new BindableList(); private readonly Bindable beatmap = new Bindable(); private BeatmapManager beatmaps; @@ -72,7 +74,7 @@ namespace osu.Game.Overlays.Music }, }; - list.BeatmapSets.BindTo(BeatmapSets); + list.Items.BindTo(beatmapSets); filter.Search.OnCommit = (sender, newText) => { From 00a7adcdca53ccc475372b5f9d5221944d17a696 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 30 Jan 2020 19:21:24 +0900 Subject: [PATCH 04/47] Further refactorings --- .../UserInterface/TestScenePlaylistOverlay.cs | 4 +- ...laylistList.cs => DrawablePlaylistItem.cs} | 48 +---- osu.Game/Overlays/Music/Playlist.cs | 46 +++++ osu.Game/Overlays/Music/PlaylistItem.cs | 183 ------------------ osu.Game/Overlays/Music/PlaylistOverlay.cs | 4 +- 5 files changed, 53 insertions(+), 232 deletions(-) rename osu.Game/Overlays/Music/{PlaylistList.cs => DrawablePlaylistItem.cs} (75%) create mode 100644 osu.Game/Overlays/Music/Playlist.cs delete mode 100644 osu.Game/Overlays/Music/PlaylistItem.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs index 986cf458ab..7476b52b49 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePlaylistOverlay.cs @@ -16,10 +16,10 @@ namespace osu.Game.Tests.Visual.UserInterface { public class TestScenePlaylistOverlay : OsuTestScene { - public override IReadOnlyList RequiredTypes => new Type[] + public override IReadOnlyList RequiredTypes => new[] { typeof(PlaylistOverlay), - typeof(PlaylistList) + typeof(Playlist) }; private readonly BindableList beatmapSets = new BindableList(); diff --git a/osu.Game/Overlays/Music/PlaylistList.cs b/osu.Game/Overlays/Music/DrawablePlaylistItem.cs similarity index 75% rename from osu.Game/Overlays/Music/PlaylistList.cs rename to osu.Game/Overlays/Music/DrawablePlaylistItem.cs index e183c14f10..e3dd72ae8b 100644 --- a/osu.Game/Overlays/Music/PlaylistList.cs +++ b/osu.Game/Overlays/Music/DrawablePlaylistItem.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; @@ -19,49 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Music { - public class PlaylistList : RearrangeableListContainer - { - public Action RequestSelection; - - public readonly Bindable SelectedSet = new Bindable(); - - public new MarginPadding Padding - { - get => base.Padding; - set => base.Padding = value; - } - - public void Filter(string searchTerm) => ((PlaylistListFlowContainer)ListContainer).SearchTerm = searchTerm; - - public BeatmapSetInfo FirstVisibleSet => ListContainer.FlowingChildren.Cast().FirstOrDefault(i => i.MatchingFilter)?.Model; - - private void removeBeatmapSets(IEnumerable sets) => Schedule(() => - { - foreach (var item in sets) - Items.Remove(ListContainer.Children.Select(d => d.Model).FirstOrDefault(m => m == item)); - }); - - protected override DrawableRearrangeableListItem CreateDrawable(BeatmapSetInfo item) => new DrawablePlaylistListItem(item) - { - SelectedSet = { BindTarget = SelectedSet }, - RequestSelection = set => RequestSelection?.Invoke(set) - }; - - protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); - - protected override FillFlowContainer> CreateListFillFlowContainer() => new PlaylistListFlowContainer - { - Spacing = new Vector2(0, 3), - LayoutDuration = 200, - LayoutEasing = Easing.OutQuint, - }; - } - - public class PlaylistListFlowContainer : SearchContainer> - { - } - - public class DrawablePlaylistListItem : DrawableRearrangeableListItem, IFilterable + public class DrawablePlaylistItem : RearrangeableListItem, IFilterable { private const float fade_duration = 100; @@ -77,7 +35,7 @@ namespace osu.Game.Overlays.Music private Color4 hoverColour; private Color4 artistColour; - public DrawablePlaylistListItem(BeatmapSetInfo item) + public DrawablePlaylistItem(BeatmapSetInfo item) : base(item) { RelativeSizeAxes = Axes.X; diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs new file mode 100644 index 0000000000..ba9aaf03d4 --- /dev/null +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -0,0 +1,46 @@ +// 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.Linq; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Overlays.Music +{ + public class Playlist : RearrangeableListContainer + { + public Action RequestSelection; + + public readonly Bindable SelectedSet = new Bindable(); + + public new MarginPadding Padding + { + get => base.Padding; + set => base.Padding = value; + } + + public void Filter(string searchTerm) => ((SearchContainer>)ListContainer).SearchTerm = searchTerm; + + public BeatmapSetInfo FirstVisibleSet => Items.FirstOrDefault(i => ((DrawablePlaylistItem)ItemMap[i]).MatchingFilter); + + protected override RearrangeableListItem CreateDrawable(BeatmapSetInfo item) => new DrawablePlaylistItem(item) + { + SelectedSet = { BindTarget = SelectedSet }, + RequestSelection = set => RequestSelection?.Invoke(set) + }; + + protected override ScrollContainer CreateScrollContainer() => new OsuScrollContainer(); + + protected override FillFlowContainer> CreateListFillFlowContainer() => new SearchContainer> + { + Spacing = new Vector2(0, 3), + LayoutDuration = 200, + LayoutEasing = Easing.OutQuint, + }; + } +} diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs deleted file mode 100644 index d40f391982..0000000000 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ /dev/null @@ -1,183 +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 System; -using System.Collections.Generic; -using System.Linq; -using osuTK.Graphics; -using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; -using osu.Framework.Localisation; -using osu.Game.Beatmaps; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; -using osuTK; - -namespace osu.Game.Overlays.Music -{ - public class PlaylistItem : Container, IFilterable, IDraggable - { - private const float fade_duration = 100; - - private Color4 hoverColour; - private Color4 artistColour; - - private SpriteIcon handle; - private TextFlowContainer text; - private IEnumerable titleSprites; - private ILocalisedBindableString titleBind; - private ILocalisedBindableString artistBind; - - public readonly BeatmapSetInfo BeatmapSetInfo; - - public Action OnSelect; - - public bool IsDraggable { get; private set; } - - protected override bool OnMouseDown(MouseDownEvent e) - { - IsDraggable = handle.IsHovered; - return base.OnMouseDown(e); - } - - protected override void OnMouseUp(MouseUpEvent e) - { - IsDraggable = false; - base.OnMouseUp(e); - } - - private bool selected; - - public bool Selected - { - get => selected; - set - { - if (value == selected) return; - - selected = value; - - FinishTransforms(true); - foreach (Drawable s in titleSprites) - s.FadeColour(Selected ? hoverColour : Color4.White, fade_duration); - } - } - - public PlaylistItem(BeatmapSetInfo setInfo) - { - BeatmapSetInfo = setInfo; - - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - Padding = new MarginPadding { Top = 3, Bottom = 3 }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours, LocalisationManager localisation) - { - hoverColour = colours.Yellow; - artistColour = colours.Gray9; - - var metadata = BeatmapSetInfo.Metadata; - FilterTerms = metadata.SearchableTerms; - - Children = new Drawable[] - { - handle = new PlaylistItemHandle - { - Colour = colours.Gray5 - }, - text = new OsuTextFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Left = 20 }, - ContentIndent = 10f, - }, - }; - - titleBind = localisation.GetLocalisedString(new LocalisedString((metadata.TitleUnicode, metadata.Title))); - artistBind = localisation.GetLocalisedString(new LocalisedString((metadata.ArtistUnicode, metadata.Artist))); - - artistBind.BindValueChanged(_ => recreateText(), true); - } - - private void recreateText() - { - text.Clear(); - - //space after the title to put a space between the title and artist - titleSprites = text.AddText(titleBind.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)).OfType(); - - text.AddText(artistBind.Value, sprite => - { - sprite.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold); - sprite.Colour = artistColour; - sprite.Padding = new MarginPadding { Top = 1 }; - }); - } - - protected override bool OnHover(HoverEvent e) - { - handle.FadeIn(fade_duration); - - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - handle.FadeOut(fade_duration); - } - - protected override bool OnClick(ClickEvent e) - { - OnSelect?.Invoke(BeatmapSetInfo); - return true; - } - - public IEnumerable FilterTerms { get; private set; } - - private bool matching = true; - - public bool MatchingFilter - { - get => matching; - set - { - if (matching == value) return; - - matching = value; - - this.FadeTo(matching ? 1 : 0, 200); - } - } - - public bool FilteringActive { get; set; } - - private class PlaylistItemHandle : SpriteIcon - { - public PlaylistItemHandle() - { - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - Size = new Vector2(12); - Icon = FontAwesome.Solid.Bars; - Alpha = 0f; - Margin = new MarginPadding { Left = 5 }; - } - - public override bool HandlePositionalInput => IsPresent; - } - } - - public interface IDraggable : IDrawable - { - /// - /// Whether this can be dragged in its current state. - /// - bool IsDraggable { get; } - } -} diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index a814712907..7c391e27f9 100644 --- a/osu.Game/Overlays/Music/PlaylistOverlay.cs +++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs @@ -29,7 +29,7 @@ namespace osu.Game.Overlays.Music private BeatmapManager beatmaps; private FilterControl filter; - private PlaylistList list; + private Playlist list; [BackgroundDependencyLoader] private void load(OsuColour colours, Bindable beatmap, BeatmapManager beatmaps) @@ -57,7 +57,7 @@ namespace osu.Game.Overlays.Music Colour = colours.Gray3, RelativeSizeAxes = Axes.Both, }, - list = new PlaylistList + list = new Playlist { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 }, From 0c4540b551138d3ca0676469071c0f0d18315ce5 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 30 Jan 2020 19:23:53 +0900 Subject: [PATCH 05/47] Rename PlaylistItem --- osu.Game/Overlays/Music/Playlist.cs | 4 ++-- .../Music/{DrawablePlaylistItem.cs => PlaylistItem.cs} | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) rename osu.Game/Overlays/Music/{DrawablePlaylistItem.cs => PlaylistItem.cs} (97%) diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index ba9aaf03d4..8744a6db8b 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -26,9 +26,9 @@ namespace osu.Game.Overlays.Music public void Filter(string searchTerm) => ((SearchContainer>)ListContainer).SearchTerm = searchTerm; - public BeatmapSetInfo FirstVisibleSet => Items.FirstOrDefault(i => ((DrawablePlaylistItem)ItemMap[i]).MatchingFilter); + public BeatmapSetInfo FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter); - protected override RearrangeableListItem CreateDrawable(BeatmapSetInfo item) => new DrawablePlaylistItem(item) + protected override RearrangeableListItem CreateDrawable(BeatmapSetInfo item) => new PlaylistItem(item) { SelectedSet = { BindTarget = SelectedSet }, RequestSelection = set => RequestSelection?.Invoke(set) diff --git a/osu.Game/Overlays/Music/DrawablePlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs similarity index 97% rename from osu.Game/Overlays/Music/DrawablePlaylistItem.cs rename to osu.Game/Overlays/Music/PlaylistItem.cs index e3dd72ae8b..4ce67c2d66 100644 --- a/osu.Game/Overlays/Music/DrawablePlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -19,7 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays.Music { - public class DrawablePlaylistItem : RearrangeableListItem, IFilterable + public class PlaylistItem : RearrangeableListItem, IFilterable { private const float fade_duration = 100; @@ -35,7 +35,7 @@ namespace osu.Game.Overlays.Music private Color4 hoverColour; private Color4 artistColour; - public DrawablePlaylistItem(BeatmapSetInfo item) + public PlaylistItem(BeatmapSetInfo item) : base(item) { RelativeSizeAxes = Axes.X; From b6cfb987f327304cfee25efbf4134866794e6925 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 4 Feb 2020 17:00:36 +0900 Subject: [PATCH 06/47] Make drag handles not pop into existence --- osu.Game/Overlays/Music/PlaylistItem.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 4ce67c2d66..0f68df737e 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.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; @@ -66,6 +66,7 @@ namespace osu.Game.Overlays.Music Origin = Anchor.CentreLeft, Size = new Vector2(12), Colour = colours.Gray5, + AlwaysPresent = true, Alpha = 0 }, text = new OsuTextFlowContainer From 5e369534b6a6d949c526a0fea2b802cf7b2d540c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 4 Feb 2020 19:15:23 +0300 Subject: [PATCH 07/47] Allow guests to view comments --- osu.Game/Overlays/Comments/CommentsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 8761b88b1e..15443ace88 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Comments loadCancellation?.Cancel(); request = new GetCommentsRequest(type, id.Value, Sort.Value, currentPage++); request.Success += onSuccess; - api.Queue(request); + api.PerformAsync(request); } private void clearComments() From a84068448a2e40ffd81e94a8bacd42e4981c8403 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Tue, 4 Feb 2020 19:19:49 +0300 Subject: [PATCH 08/47] refetch comments on user change --- osu.Game/Overlays/Comments/CommentsContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 15443ace88..3d68f453b7 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -113,6 +113,7 @@ namespace osu.Game.Overlays.Comments protected override void LoadComplete() { + api.LocalUser.BindValueChanged(_ => refetchComments()); Sort.BindValueChanged(_ => refetchComments(), true); base.LoadComplete(); } From 9347c3f535e9820ed461ea5c11b62fd9d9a8d7a4 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Wed, 5 Feb 2020 11:13:32 +0300 Subject: [PATCH 09/47] Add trigger user change test --- .../Visual/Online/TestSceneCommentsContainer.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 9c526c4f81..33acc75fa8 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Game.Overlays.Comments; using osu.Game.Overlays; using osu.Framework.Allocation; +using osu.Game.Online.API; namespace osu.Game.Tests.Visual.Online { @@ -34,21 +35,26 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); + private CommentsContainer comments; + private readonly BasicScrollContainer scroll; + public TestSceneCommentsContainer() { - BasicScrollContainer scroll; - CommentsContainer comments; - Add(scroll = new BasicScrollContainer { RelativeSizeAxes = Axes.Both, Child = comments = new CommentsContainer() }); + } + [BackgroundDependencyLoader] + private void load(IAPIProvider api) + { AddStep("Big Black comments", () => comments.ShowComments(CommentableType.Beatmapset, 41823)); AddStep("Airman comments", () => comments.ShowComments(CommentableType.Beatmapset, 24313)); AddStep("Lazer build comments", () => comments.ShowComments(CommentableType.Build, 4772)); AddStep("News comments", () => comments.ShowComments(CommentableType.NewsPost, 715)); + AddStep("Trigger user change", api.LocalUser.TriggerChange); AddStep("Idle state", () => { scroll.Clear(); From 63c595ed974ef224e7e350599ceafffa202320d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 17:16:15 +0900 Subject: [PATCH 10/47] Make EditorBeatmap a component and move UpdateHitObject to it --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 20 ++-------------- osu.Game/Screens/Edit/Editor.cs | 6 ++--- osu.Game/Screens/Edit/EditorBeatmap.cs | 26 ++++++++++++++++++++- 3 files changed, 30 insertions(+), 22 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 5ac1d4ecc4..0014360d24 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -11,7 +11,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Framework.Logging; -using osu.Framework.Threading; using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; @@ -50,8 +49,6 @@ namespace osu.Game.Rulesets.Edit [Resolved] private IBeatSnapProvider beatSnapProvider { get; set; } - private IBeatmapProcessor beatmapProcessor; - private DrawableEditRulesetWrapper drawableRulesetWrapper; private ComposeBlueprintContainer blueprintContainer; private Container distanceSnapGridContainer; @@ -71,8 +68,6 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load(IFrameBasedClock framedClock) { - beatmapProcessor = Ruleset.CreateBeatmapProcessor(EditorBeatmap.PlayableBeatmap); - EditorBeatmap.HitObjectAdded += addHitObject; EditorBeatmap.HitObjectRemoved += removeHitObject; EditorBeatmap.StartTimeChanged += UpdateHitObject; @@ -240,19 +235,6 @@ namespace osu.Game.Rulesets.Edit lastGridUpdateTime = EditorClock.CurrentTime; } - private ScheduledDelegate scheduledUpdate; - - public override void UpdateHitObject(HitObject hitObject) - { - scheduledUpdate?.Cancel(); - scheduledUpdate = Schedule(() => - { - beatmapProcessor?.PreProcess(); - hitObject?.ApplyDefaults(EditorBeatmap.ControlPointInfo, EditorBeatmap.BeatmapInfo.BaseDifficulty); - beatmapProcessor?.PostProcess(); - }); - } - private void addHitObject(HitObject hitObject) => UpdateHitObject(hitObject); private void removeHitObject(HitObject hitObject) => UpdateHitObject(null); @@ -309,6 +291,8 @@ namespace osu.Game.Rulesets.Edit public override float GetSnappedDistanceFromDistance(double referenceTime, float distance) => DurationToDistance(referenceTime, beatSnapProvider.SnapTime(DistanceToDuration(referenceTime, distance), referenceTime)); + public override void UpdateHitObject(HitObject hitObject) => EditorBeatmap.UpdateHitObject(hitObject); + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 8c7270d3a2..9a60edadc4 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Edit clock.ChangeSource(sourceClock); playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); - editorBeatmap = new EditorBeatmap(playableBeatmap, beatDivisor); + AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, beatDivisor)); dependencies.CacheAs(clock); dependencies.CacheAs(clock); @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Edit fileMenuItems.Add(new EditorMenuItem("Exit", MenuItemType.Standard, this.Exit)); - InternalChild = new OsuContextMenuContainer + AddInternal(new OsuContextMenuContainer { RelativeSizeAxes = Axes.Both, Children = new[] @@ -189,7 +189,7 @@ namespace osu.Game.Screens.Edit } }, } - }; + }); menuBar.Mode.ValueChanged += onModeChanged; diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index 6edd62fa67..ba008e32e8 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -5,6 +5,8 @@ using System; using System.Collections; using System.Collections.Generic; using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; @@ -13,7 +15,7 @@ using osu.Game.Rulesets.Objects; namespace osu.Game.Screens.Edit { - public class EditorBeatmap : IBeatmap, IBeatSnapProvider + public class EditorBeatmap : Component, IBeatmap, IBeatSnapProvider { /// /// Invoked when a is added to this . @@ -36,17 +38,39 @@ namespace osu.Game.Screens.Edit private readonly BindableBeatDivisor beatDivisor; + private readonly IBeatmapProcessor beatmapProcessor; + private readonly Dictionary> startTimeBindables = new Dictionary>(); public EditorBeatmap(IBeatmap playableBeatmap, BindableBeatDivisor beatDivisor = null) { PlayableBeatmap = playableBeatmap; + this.beatDivisor = beatDivisor; + beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap); + foreach (var obj in HitObjects) trackStartTime(obj); } + private ScheduledDelegate scheduledUpdate; + + /// + /// Updates a , invoking and re-processing the beatmap. + /// + /// The to update. + public void UpdateHitObject(HitObject hitObject) + { + scheduledUpdate?.Cancel(); + scheduledUpdate = Scheduler.AddDelayed(() => + { + beatmapProcessor?.PreProcess(); + hitObject?.ApplyDefaults(ControlPointInfo, BeatmapInfo.BaseDifficulty); + beatmapProcessor?.PostProcess(); + }, 0); + } + public BeatmapInfo BeatmapInfo { get => PlayableBeatmap.BeatmapInfo; From 96986bf5fcf03e56acbf8926346665ef1fea42f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 17:32:33 +0900 Subject: [PATCH 11/47] Remove beat divisor from ctor and use DI instead --- ...tSceneHitObjectComposerDistanceSnapping.cs | 22 +++++++++++++++++-- .../Visual/Editor/TimelineTestScene.cs | 3 ++- osu.Game/Screens/Edit/Editor.cs | 6 ++--- osu.Game/Screens/Edit/EditorBeatmap.cs | 8 +++---- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs index 5a4e76d586..3cb5909ba9 100644 --- a/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs +++ b/osu.Game.Tests/Editor/TestSceneHitObjectComposerDistanceSnapping.cs @@ -3,6 +3,8 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Testing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit; @@ -23,15 +25,31 @@ namespace osu.Game.Tests.Editor [Cached(typeof(IBeatSnapProvider))] private readonly EditorBeatmap editorBeatmap; + protected override Container Content { get; } + public TestSceneHitObjectComposerDistanceSnapping() { - editorBeatmap = new EditorBeatmap(new OsuBeatmap(), BeatDivisor); + base.Content.Add(new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + editorBeatmap = new EditorBeatmap(new OsuBeatmap()), + Content = new Container + { + RelativeSizeAxes = Axes.Both, + } + }, + }); } [SetUp] public void Setup() => Schedule(() => { - Child = composer = new TestHitObjectComposer(); + Children = new Drawable[] + { + composer = new TestHitObjectComposer() + }; BeatDivisor.Value = 1; diff --git a/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs index b5e526d3c2..40c0fedc9e 100644 --- a/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs @@ -38,13 +38,14 @@ namespace osu.Game.Tests.Visual.Editor { Beatmap.Value = new WaveformTestBeatmap(audio); - var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap, BeatDivisor); + var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap); Dependencies.Cache(editorBeatmap); Dependencies.CacheAs(editorBeatmap); AddRange(new Drawable[] { + editorBeatmap, new FillFlowContainer { AutoSizeAxes = Axes.Both, diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 9a60edadc4..3bec4b8f6f 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -80,15 +80,15 @@ namespace osu.Game.Screens.Edit clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); - playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); - AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap, beatDivisor)); - dependencies.CacheAs(clock); dependencies.CacheAs(clock); // todo: remove caching of this and consume via editorBeatmap? dependencies.Cache(beatDivisor); + playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); + AddInternal(editorBeatmap = new EditorBeatmap(playableBeatmap)); + dependencies.CacheAs(editorBeatmap); EditorMenuBar menuBar; diff --git a/osu.Game/Screens/Edit/EditorBeatmap.cs b/osu.Game/Screens/Edit/EditorBeatmap.cs index ba008e32e8..cacb539891 100644 --- a/osu.Game/Screens/Edit/EditorBeatmap.cs +++ b/osu.Game/Screens/Edit/EditorBeatmap.cs @@ -4,6 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Threading; @@ -36,18 +37,17 @@ namespace osu.Game.Screens.Edit public readonly IBeatmap PlayableBeatmap; - private readonly BindableBeatDivisor beatDivisor; + [Resolved] + private BindableBeatDivisor beatDivisor { get; set; } private readonly IBeatmapProcessor beatmapProcessor; private readonly Dictionary> startTimeBindables = new Dictionary>(); - public EditorBeatmap(IBeatmap playableBeatmap, BindableBeatDivisor beatDivisor = null) + public EditorBeatmap(IBeatmap playableBeatmap) { PlayableBeatmap = playableBeatmap; - this.beatDivisor = beatDivisor; - beatmapProcessor = playableBeatmap.BeatmapInfo.Ruleset?.CreateInstance().CreateBeatmapProcessor(PlayableBeatmap); foreach (var obj in HitObjects) From dffc58c5fae9e18fc96af3b8128ce3a7b2d06226 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Feb 2020 12:23:02 +0900 Subject: [PATCH 12/47] Add blueprint type to timeline test --- .../Editor/TestSceneTimelineBlueprintContainer.cs | 13 +++++++++++++ .../Visual/Editor/TestSceneTimingScreen.cs | 7 ++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs index 3c75fd5310..4d8f877575 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimelineBlueprintContainer.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.Graphics; using osu.Game.Screens.Edit.Compose.Components.Timeline; @@ -10,6 +12,17 @@ namespace osu.Game.Tests.Visual.Editor [TestFixture] public class TestSceneTimelineBlueprintContainer : TimelineTestScene { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(TimelineHitObjectBlueprint), + }; + public override Drawable CreateTestComponent() => new TimelineBlueprintContainer(); + + protected override void LoadComplete() + { + base.LoadComplete(); + Clock.Seek(10000); + } } } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs index adfed9a299..ae09a7fa47 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -27,7 +27,12 @@ namespace osu.Game.Tests.Visual.Editor }; [Cached(typeof(EditorBeatmap))] - private readonly EditorBeatmap editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + private readonly EditorBeatmap editorBeatmap; + + public TestSceneTimingScreen() + { + editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + } [BackgroundDependencyLoader] private void load() From cd6902a312480e53a7f49d1fa264ef158750d4cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 17:12:26 +0900 Subject: [PATCH 13/47] Make EndTime and RepeatCount settable --- osu.Game.Rulesets.Catch/Objects/BananaShower.cs | 6 +++++- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 6 +++++- osu.Game.Rulesets.Mania/Objects/HoldNote.cs | 6 +++++- osu.Game.Rulesets.Osu/Objects/Slider.cs | 9 +++++++-- osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs | 6 +++++- osu.Game.Rulesets.Taiko/Objects/Swell.cs | 6 +++++- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 7 ++++++- osu.Game/Rulesets/Objects/Types/IHasEndTime.cs | 5 ++++- osu.Game/Rulesets/Objects/Types/IHasRepeats.cs | 2 +- 9 files changed, 43 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs index 267e6d12c7..0c754412e5 100644 --- a/osu.Game.Rulesets.Catch/Objects/BananaShower.cs +++ b/osu.Game.Rulesets.Catch/Objects/BananaShower.cs @@ -36,7 +36,11 @@ namespace osu.Game.Rulesets.Catch.Objects } } - public double EndTime => StartTime + Duration; + public double EndTime + { + get => StartTime + Duration; + set => Duration = value - StartTime; + } public double Duration { get; set; } } diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index a4ed966abb..4d68bf592c 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -110,7 +110,11 @@ namespace osu.Game.Rulesets.Catch.Objects } } - public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; + public double EndTime + { + get => StartTime + this.SpanCount() * Path.Distance / Velocity; + set => throw new System.NotImplementedException(); + } public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; diff --git a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs index bdba813eed..86d3d2b2b0 100644 --- a/osu.Game.Rulesets.Mania/Objects/HoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/HoldNote.cs @@ -15,7 +15,11 @@ namespace osu.Game.Rulesets.Mania.Objects /// public class HoldNote : ManiaHitObject, IHasEndTime { - public double EndTime => StartTime + Duration; + public double EndTime + { + get => StartTime + Duration; + set => Duration = value - StartTime; + } private double duration; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index fe65ab78d1..bab7e6dbee 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -18,7 +18,12 @@ namespace osu.Game.Rulesets.Osu.Objects { public class Slider : OsuHitObject, IHasCurve { - public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; + public double EndTime + { + get => StartTime + this.SpanCount() * Path.Distance / Velocity; + set => throw new System.NotImplementedException(); + } + public double Duration => EndTime - StartTime; private readonly Cached endPositionCache = new Cached(); @@ -81,7 +86,7 @@ namespace osu.Game.Rulesets.Osu.Objects set { repeatCount = value; - endPositionCache.Invalidate(); + updateNestedPositions(); } } diff --git a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs index 8956ca9c19..aacd78f176 100644 --- a/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs +++ b/osu.Game.Rulesets.Taiko/Objects/DrumRoll.cs @@ -18,7 +18,11 @@ namespace osu.Game.Rulesets.Taiko.Objects /// private const float base_distance = 100; - public double EndTime => StartTime + Duration; + public double EndTime + { + get => StartTime + Duration; + set => Duration = value - StartTime; + } public double Duration { get; set; } diff --git a/osu.Game.Rulesets.Taiko/Objects/Swell.cs b/osu.Game.Rulesets.Taiko/Objects/Swell.cs index e60984596d..2f06066a16 100644 --- a/osu.Game.Rulesets.Taiko/Objects/Swell.cs +++ b/osu.Game.Rulesets.Taiko/Objects/Swell.cs @@ -11,7 +11,11 @@ namespace osu.Game.Rulesets.Taiko.Objects { public class Swell : TaikoHitObject, IHasEndTime { - public double EndTime => StartTime + Duration; + public double EndTime + { + get => StartTime + Duration; + set => Duration = value - StartTime; + } public double Duration { get; set; } diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index 8d523022d6..b5cd9be245 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -26,7 +26,12 @@ namespace osu.Game.Rulesets.Objects.Legacy public List> NodeSamples { get; set; } public int RepeatCount { get; set; } - public double EndTime => StartTime + this.SpanCount() * Distance / Velocity; + public double EndTime + { + get => StartTime + this.SpanCount() * Distance / Velocity; + set => throw new System.NotImplementedException(); + } + public double Duration => EndTime - StartTime; public double Velocity = 1; diff --git a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs b/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs index 516f1002a4..bc7103c60d 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasEndTime.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasEndTime.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 Newtonsoft.Json; + namespace osu.Game.Rulesets.Objects.Types { /// @@ -11,7 +13,8 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The time at which the HitObject ends. /// - double EndTime { get; } + [JsonIgnore] + double EndTime { get; set; } /// /// The duration of the HitObject. diff --git a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs index b22752e902..256b1f3963 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasRepeats.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Objects.Types /// /// The amount of times the HitObject repeats. /// - int RepeatCount { get; } + int RepeatCount { get; set; } /// /// The samples to be played when each node of the is hit.
From d04cc0123df7c7c7d3a476a03a27f93dd79257f4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 17:16:37 +0900 Subject: [PATCH 14/47] Initial implementation of timeline blueprint dragbars --- .../Compose/Components/Timeline/Timeline.cs | 13 ++- .../Timeline/TimelineHitObjectBlueprint.cs | 99 ++++++++++++++----- 2 files changed, 85 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index a33040f400..0475e68e42 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -179,11 +179,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private IBeatSnapProvider beatSnapProvider { get; set; } - public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) - { - var targetTime = (position.X / Content.DrawWidth) * track.Length; - return (position, beatSnapProvider.SnapTime(targetTime)); - } + public double GetTimeFromScreenSpacePosition(Vector2 position) + => getTimeFromPosition(Content.ToLocalSpace(position)); + + public (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time) => + (position, beatSnapProvider.SnapTime(getTimeFromPosition(position))); + + private double getTimeFromPosition(Vector2 localPosition) => + (localPosition.X / Content.DrawWidth) * track.Length; public float GetBeatSnapDistanceAt(double referenceTime) => throw new NotImplementedException(); diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 2ed5471444..2ac35534f4 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using JetBrains.Annotations; +using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -24,7 +26,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [UsedImplicitly] private readonly Bindable startTime; - public const float THICKNESS = 3; + public const float THICKNESS = 5; private const float circle_size = 16; @@ -44,25 +46,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - if (hitObject is IHasEndTime) - { - AddInternal(extensionBar = new Container - { - CornerRadius = 2, - Masking = true, - Size = new Vector2(1, THICKNESS), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - RelativePositionAxes = Axes.X, - RelativeSizeAxes = Axes.X, - Colour = Color4.Black, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - } - }); - } - AddInternal(circle = new Circle { Size = new Vector2(circle_size), @@ -74,6 +57,78 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline BorderColour = Color4.Black, BorderThickness = THICKNESS, }); + + if (hitObject is IHasEndTime) + { + AddRangeInternal(new Drawable[] + { + extensionBar = new Container + { + CornerRadius = 2, + Masking = true, + Size = new Vector2(1, THICKNESS), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativePositionAxes = Axes.X, + RelativeSizeAxes = Axes.X, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + } + }, + new DragBar(hitObject), + }); + } + } + + public class DragBar : CompositeDrawable + { + private readonly HitObject hitObject; + + [Resolved] + private Timeline timeline { get; set; } + + public DragBar(HitObject hitObject) + { + this.hitObject = hitObject; + + CornerRadius = 2; + Masking = true; + Size = new Vector2(THICKNESS, 1.5f); + Anchor = Anchor.CentreRight; + Origin = Anchor.CentreRight; + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + InternalChild = new Box + { + RelativeSizeAxes = Axes.Both, + }; + } + + protected override bool OnDragStart(DragStartEvent e) => true; + + [Resolved] + private EditorBeatmap beatmap { get; set; } + + protected override void OnDrag(DragEvent e) + { + base.OnDrag(e); + + var time = timeline.GetTimeFromScreenSpacePosition(e.ScreenSpaceMousePosition); + + switch (hitObject) + { + case IHasRepeats repeatHitObject: + repeatHitObject.RepeatCount = (int)((time - hitObject.StartTime) / (repeatHitObject.Duration / repeatHitObject.RepeatCount)); + break; + + case IHasEndTime endTimeHitObject: + endTimeHitObject.EndTime = time; + break; + } + + beatmap.UpdateHitObject(hitObject); + } } protected override void Update() @@ -88,14 +143,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { circle.BorderColour = Color4.Orange; if (extensionBar != null) - extensionBar.Colour = Color4.Orange; + extensionBar.BorderColour = Color4.Orange; } protected override void OnDeselected() { circle.BorderColour = Color4.Black; if (extensionBar != null) - extensionBar.Colour = Color4.Black; + extensionBar.BorderColour = Color4.Black; } public override Quad SelectionQuad From 09273d1da97a5b2ffe75fc9185a0c43161fdcb7c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 18:14:44 +0900 Subject: [PATCH 15/47] Fix test scene not correctly building a playable beatmap --- .../Visual/Editor/TimelineTestScene.cs | 5 ++-- osu.Game.Tests/WaveformTestBeatmap.cs | 27 ++++++++++++++----- osu.Game/Tests/Beatmaps/TestBeatmap.cs | 4 ++- 3 files changed, 27 insertions(+), 9 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs index 40c0fedc9e..7081eb3af5 100644 --- a/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs +++ b/osu.Game.Tests/Visual/Editor/TimelineTestScene.cs @@ -13,7 +13,6 @@ using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; using osu.Game.Screens.Edit; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; @@ -38,7 +37,9 @@ namespace osu.Game.Tests.Visual.Editor { Beatmap.Value = new WaveformTestBeatmap(audio); - var editorBeatmap = new EditorBeatmap((Beatmap)Beatmap.Value.Beatmap); + var playable = Beatmap.Value.GetPlayableBeatmap(Beatmap.Value.BeatmapInfo.Ruleset); + + var editorBeatmap = new EditorBeatmap(playable); Dependencies.Cache(editorBeatmap); Dependencies.CacheAs(editorBeatmap); diff --git a/osu.Game.Tests/WaveformTestBeatmap.cs b/osu.Game.Tests/WaveformTestBeatmap.cs index b7d7bb1ee1..df6394ed34 100644 --- a/osu.Game.Tests/WaveformTestBeatmap.cs +++ b/osu.Game.Tests/WaveformTestBeatmap.cs @@ -11,6 +11,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.IO; using osu.Game.IO.Archives; +using osu.Game.Rulesets.Catch; +using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Resources; namespace osu.Game.Tests @@ -20,11 +22,18 @@ namespace osu.Game.Tests ///
public class WaveformTestBeatmap : WorkingBeatmap { + private readonly Beatmap beatmap; private readonly ITrackStore trackStore; public WaveformTestBeatmap(AudioManager audioManager) - : base(new BeatmapInfo(), audioManager) + : this(audioManager, new WaveformBeatmap()) { + } + + public WaveformTestBeatmap(AudioManager audioManager, Beatmap beatmap) + : base(beatmap.BeatmapInfo, audioManager) + { + this.beatmap = beatmap; trackStore = audioManager.GetTrackStore(getZipReader()); } @@ -34,11 +43,11 @@ namespace osu.Game.Tests trackStore?.Dispose(); } - private Stream getStream() => TestResources.GetTestBeatmapStream(); + private static Stream getStream() => TestResources.GetTestBeatmapStream(); - private ZipArchiveReader getZipReader() => new ZipArchiveReader(getStream()); + private static ZipArchiveReader getZipReader() => new ZipArchiveReader(getStream()); - protected override IBeatmap GetBeatmap() => createTestBeatmap(); + protected override IBeatmap GetBeatmap() => beatmap; protected override Texture GetBackground() => null; @@ -57,10 +66,16 @@ namespace osu.Game.Tests } } - private Beatmap createTestBeatmap() + private class WaveformBeatmap : TestBeatmap { - using (var reader = getZipReader()) + public WaveformBeatmap() + : base(new CatchRuleset().RulesetInfo) { + } + + protected override Beatmap CreateBeatmap() + { + using (var reader = getZipReader()) using (var beatmapStream = reader.GetStream(reader.Filenames.First(f => f.EndsWith(".osu")))) using (var beatmapReader = new LineBufferedReader(beatmapStream)) return Decoder.GetDecoder(beatmapReader).Decode(beatmapReader); diff --git a/osu.Game/Tests/Beatmaps/TestBeatmap.cs b/osu.Game/Tests/Beatmaps/TestBeatmap.cs index d6f92ba086..96e3c037a3 100644 --- a/osu.Game/Tests/Beatmaps/TestBeatmap.cs +++ b/osu.Game/Tests/Beatmaps/TestBeatmap.cs @@ -15,7 +15,7 @@ namespace osu.Game.Tests.Beatmaps { public TestBeatmap(RulesetInfo ruleset) { - var baseBeatmap = createTestBeatmap(); + var baseBeatmap = CreateBeatmap(); BeatmapInfo = baseBeatmap.BeatmapInfo; ControlPointInfo = baseBeatmap.ControlPointInfo; @@ -37,6 +37,8 @@ namespace osu.Game.Tests.Beatmaps }; } + protected virtual Beatmap CreateBeatmap() => createTestBeatmap(); + private static Beatmap createTestBeatmap() { using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(test_beatmap_data))) From d56accaef1ab193f33d214a16e53a56a8e22be49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 15:32:51 +0900 Subject: [PATCH 16/47] Disallow negative / zero repeat counts (and fix off-by-one) --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 2ac35534f4..b2da1577d0 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -119,7 +119,14 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline switch (hitObject) { case IHasRepeats repeatHitObject: - repeatHitObject.RepeatCount = (int)((time - hitObject.StartTime) / (repeatHitObject.Duration / repeatHitObject.RepeatCount)); + // find the number of repeats which can fit in the requested time. + var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); + var proposedCount = (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1; + + if (proposedCount == repeatHitObject.RepeatCount || proposedCount < 0) + return; + + repeatHitObject.RepeatCount = proposedCount; break; case IHasEndTime endTimeHitObject: From cef45afbc85eb24bad74f44d1e8d4c8d16913b86 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 15:33:04 +0900 Subject: [PATCH 17/47] Add a simple hover state --- .../Timeline/TimelineHitObjectBlueprint.cs | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index b2da1577d0..8a6dd40bde 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -23,6 +23,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private readonly Container extensionBar; + protected override bool ShouldBeConsideredForInput(Drawable child) => true; + [UsedImplicitly] private readonly Bindable startTime; @@ -105,6 +107,45 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }; } + protected override bool OnHover(HoverEvent e) + { + updateState(); + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + base.OnHoverLost(e); + } + + private bool hasMouseDown; + + protected override bool OnMouseDown(MouseDownEvent e) + { + hasMouseDown = true; + updateState(); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + hasMouseDown = false; + updateState(); + base.OnMouseUp(e); + } + + private void updateState() + { + if (IsHovered || hasMouseDown) + Colour = Color4.Orange; + else + { + Colour = Color4.White; + } + } + protected override bool OnDragStart(DragStartEvent e) => true; [Resolved] From 3d42973764434fbfe5359996e9edb270a56c759d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 15:58:35 +0900 Subject: [PATCH 18/47] Allow scrolling via drag while dragging a hold note handle --- .../Timeline/TimelineBlueprintContainer.cs | 40 ++++++++++++------- .../Timeline/TimelineHitObjectBlueprint.cs | 16 +++++++- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs index 3b9cb1df24..9f3d776e5c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineBlueprintContainer.cs @@ -49,20 +49,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void OnDrag(DragEvent e) { - if (timeline != null) - { - var timelineQuad = timeline.ScreenSpaceDrawQuad; - var mouseX = e.ScreenSpaceMousePosition.X; - - // scroll if in a drag and dragging outside visible extents - if (mouseX > timelineQuad.TopRight.X) - timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime)); - else if (mouseX < timelineQuad.TopLeft.X) - timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime)); - } + handleScrollViaDrag(e); base.OnDrag(e); - lastDragEvent = e; } protected override void OnDragEnd(DragEndEvent e) @@ -74,7 +63,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override void Update() { // trigger every frame so drags continue to update selection while playback is scrolling the timeline. - if (IsDragged) + if (lastDragEvent != null) OnDrag(lastDragEvent); base.Update(); @@ -82,10 +71,33 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override SelectionHandler CreateSelectionHandler() => new TimelineSelectionHandler(); - protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectBlueprint(hitObject); + protected override SelectionBlueprint CreateBlueprintFor(HitObject hitObject) => new TimelineHitObjectBlueprint(hitObject) + { + OnDragHandled = handleScrollViaDrag + }; protected override DragBox CreateDragBox(Action performSelect) => new TimelineDragBox(performSelect); + private void handleScrollViaDrag(DragEvent e) + { + lastDragEvent = e; + + if (lastDragEvent == null) + return; + + if (timeline != null) + { + var timelineQuad = timeline.ScreenSpaceDrawQuad; + var mouseX = e.ScreenSpaceMousePosition.X; + + // scroll if in a drag and dragging outside visible extents + if (mouseX > timelineQuad.TopRight.X) + timeline.ScrollBy((float)((mouseX - timelineQuad.TopRight.X) / 10 * Clock.ElapsedFrameTime)); + else if (mouseX < timelineQuad.TopLeft.X) + timeline.ScrollBy((float)((mouseX - timelineQuad.TopLeft.X) / 10 * Clock.ElapsedFrameTime)); + } + } + internal class TimelineSelectionHandler : SelectionHandler { // for now we always allow movement. snapping is provided by the Timeline's "distance" snap implementation diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 8a6dd40bde..3de1cd3a4c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -28,6 +29,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [UsedImplicitly] private readonly Bindable startTime; + public Action OnDragHandled; + public const float THICKNESS = 5; private const float circle_size = 16; @@ -78,7 +81,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.Both, } }, - new DragBar(hitObject), + new DragBar(hitObject) { OnDragHandled = e => OnDragHandled?.Invoke(e) } }); } } @@ -90,6 +93,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private Timeline timeline { get; set; } + public Action OnDragHandled; + public DragBar(HitObject hitObject) { this.hitObject = hitObject; @@ -155,6 +160,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.OnDrag(e); + OnDragHandled?.Invoke(e); + var time = timeline.GetTimeFromScreenSpacePosition(e.ScreenSpaceMousePosition); switch (hitObject) @@ -177,6 +184,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline beatmap.UpdateHitObject(hitObject); } + + protected override void OnDragEnd(DragEndEvent e) + { + base.OnDragEnd(e); + + OnDragHandled?.Invoke(null); + } } protected override void Update() From 98ab1f986272e89c0d092ec5cd01b726b74a4998 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 16:35:07 +0900 Subject: [PATCH 19/47] Fix negative spinners --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 3de1cd3a4c..5a15cb42fa 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -156,6 +156,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline [Resolved] private EditorBeatmap beatmap { get; set; } + [Resolved] + private IBeatSnapProvider beatSnapProvider { get; set; } + protected override void OnDrag(DragEvent e) { base.OnDrag(e); @@ -178,7 +181,12 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline break; case IHasEndTime endTimeHitObject: - endTimeHitObject.EndTime = time; + var snappedTime = beatSnapProvider.SnapTime(time); + + if (endTimeHitObject.EndTime == snappedTime || snappedTime <= hitObject.StartTime) + return; + + endTimeHitObject.EndTime = snappedTime; break; } From f5edad16e67d3a9151dd0118f11d5b48bf4c2e91 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 5 Feb 2020 19:43:13 +0900 Subject: [PATCH 20/47] Improve visuals --- .../Timeline/TimelineHitObjectBlueprint.cs | 184 ++++++++++++------ 1 file changed, 125 insertions(+), 59 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 5a15cb42fa..b46a373818 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -2,14 +2,17 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Types; @@ -22,8 +25,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { private readonly Circle circle; - private readonly Container extensionBar; - protected override bool ShouldBeConsideredForInput(Drawable child) => true; [UsedImplicitly] @@ -31,11 +32,20 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public Action OnDragHandled; - public const float THICKNESS = 5; + private readonly DragBar dragBar; + + private readonly List shadowComponents = new List(); + + private const float thickness = 5; + + private const float shadow_radius = 5; private const float circle_size = 16; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) || circle.ReceivePositionalInputAt(screenSpacePos); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + base.ReceivePositionalInputAt(screenSpacePos) || + circle.ReceivePositionalInputAt(screenSpacePos) || + dragBar?.ReceivePositionalInputAt(screenSpacePos) == true; public TimelineHitObjectBlueprint(HitObject hitObject) : base(hitObject) @@ -51,7 +61,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - AddInternal(circle = new Circle + circle = new Circle { Size = new Vector2(circle_size), Anchor = Anchor.CentreLeft, @@ -59,34 +69,126 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline RelativePositionAxes = Axes.X, AlwaysPresent = true, Colour = Color4.White, - BorderColour = Color4.Black, - BorderThickness = THICKNESS, - }); + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = shadow_radius, + Colour = Color4.Black + }, + }; + + shadowComponents.Add(circle); if (hitObject is IHasEndTime) { + DragBar dragBarUnderlay; + Container extensionBar; + AddRangeInternal(new Drawable[] { extensionBar = new Container { - CornerRadius = 2, Masking = true, - Size = new Vector2(1, THICKNESS), + Size = new Vector2(1, thickness), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, RelativePositionAxes = Axes.X, RelativeSizeAxes = Axes.X, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = shadow_radius, + Colour = Color4.Black + }, Child = new Box { RelativeSizeAxes = Axes.Both, } }, - new DragBar(hitObject) { OnDragHandled = e => OnDragHandled?.Invoke(e) } + circle, + // only used for drawing the shadow + dragBarUnderlay = new DragBar(null), + // cover up the shadow on the join + new Box + { + Height = thickness, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + }, + dragBar = new DragBar(hitObject) { OnDragHandled = e => OnDragHandled?.Invoke(e) }, }); + + shadowComponents.Add(dragBarUnderlay); + shadowComponents.Add(extensionBar); + } + else + { + AddInternal(circle); + } + + updateShadows(); + } + + protected override void Update() + { + base.Update(); + + // no bindable so we perform this every update + Width = (float)(HitObject.GetEndTime() - HitObject.StartTime); + } + + protected override void OnSelected() + { + updateShadows(); + } + + private void updateShadows() + { + foreach (var s in shadowComponents) + { + if (State == SelectionState.Selected) + { + s.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = shadow_radius / 2, + Colour = Color4.Orange, + }; + } + else + { + s.EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Shadow, + Radius = shadow_radius, + Colour = State == SelectionState.Selected ? Color4.Orange : Color4.Black + }; + } } } - public class DragBar : CompositeDrawable + protected override void OnDeselected() + { + updateShadows(); + } + + public override Quad SelectionQuad + { + get + { + // correctly include the circle in the selection quad region, as it is usually outside the blueprint itself. + var leftQuad = circle.ScreenSpaceDrawQuad; + var rightQuad = dragBar?.ScreenSpaceDrawQuad ?? ScreenSpaceDrawQuad; + + return new Quad(leftQuad.TopLeft, Vector2.ComponentMax(rightQuad.TopRight, leftQuad.TopRight), + leftQuad.BottomLeft, Vector2.ComponentMax(rightQuad.BottomRight, leftQuad.BottomRight)); + } + } + + public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft; + + public class DragBar : Container { private readonly HitObject hitObject; @@ -95,20 +197,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline public Action OnDragHandled; + public override bool HandlePositionalInput => hitObject != null; + public DragBar(HitObject hitObject) { this.hitObject = hitObject; CornerRadius = 2; Masking = true; - Size = new Vector2(THICKNESS, 1.5f); + Size = new Vector2(5, 1); Anchor = Anchor.CentreRight; - Origin = Anchor.CentreRight; + Origin = Anchor.Centre; RelativePositionAxes = Axes.X; RelativeSizeAxes = Axes.Y; - InternalChild = new Box + + InternalChildren = new Drawable[] { - RelativeSizeAxes = Axes.Both, + new Box + { + RelativeSizeAxes = Axes.Both, + } }; } @@ -143,12 +251,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private void updateState() { - if (IsHovered || hasMouseDown) - Colour = Color4.Orange; - else - { - Colour = Color4.White; - } + Colour = IsHovered || hasMouseDown ? Color4.OrangeRed : Color4.White; } protected override bool OnDragStart(DragStartEvent e) => true; @@ -200,42 +303,5 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline OnDragHandled?.Invoke(null); } } - - protected override void Update() - { - base.Update(); - - // no bindable so we perform this every update - Width = (float)(HitObject.GetEndTime() - HitObject.StartTime); - } - - protected override void OnSelected() - { - circle.BorderColour = Color4.Orange; - if (extensionBar != null) - extensionBar.BorderColour = Color4.Orange; - } - - protected override void OnDeselected() - { - circle.BorderColour = Color4.Black; - if (extensionBar != null) - extensionBar.BorderColour = Color4.Black; - } - - public override Quad SelectionQuad - { - get - { - // correctly include the circle in the selection quad region, as it is usually outside the blueprint itself. - var circleQuad = circle.ScreenSpaceDrawQuad; - var actualQuad = ScreenSpaceDrawQuad; - - return new Quad(circleQuad.TopLeft, Vector2.ComponentMax(actualQuad.TopRight, circleQuad.TopRight), - circleQuad.BottomLeft, Vector2.ComponentMax(actualQuad.BottomRight, circleQuad.BottomRight)); - } - } - - public override Vector2 SelectionPoint => ScreenSpaceDrawQuad.TopLeft; } } From 00edc7e66fd527d87f7ec2a80751544e863aec12 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 12:12:52 +0900 Subject: [PATCH 21/47] Fix RoomsContainer test scene not displaying --- .../Multi/Lounge/Components/RoomsContainer.cs | 22 +++++-------------- 1 file changed, 6 insertions(+), 16 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 607b081653..6ef0f6b6db 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -47,22 +47,13 @@ namespace osu.Game.Screens.Multi.Lounge.Components }; } - [BackgroundDependencyLoader] - private void load() - { - rooms.BindTo(roomManager.Rooms); - - rooms.ItemsAdded += addRooms; - rooms.ItemsRemoved += removeRooms; - - roomManager.RoomsUpdated += updateSorting; - - addRooms(rooms); - } - protected override void LoadComplete() { - filter?.BindValueChanged(f => Filter(f.NewValue), true); + rooms.ItemsAdded += addRooms; + rooms.ItemsRemoved += removeRooms; + roomManager.RoomsUpdated += updateSorting; + + rooms.BindTo(roomManager.Rooms); } public void Filter(FilterCriteria criteria) @@ -94,8 +85,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components foreach (var r in rooms) roomFlow.Add(new DrawableRoom(r) { Action = () => selectRoom(r) }); - if (filter != null) - Filter(filter.Value); + Filter(filter?.Value); } private void removeRooms(IEnumerable rooms) From 7f0a134bc8c55ec7dac0dac6daf4c5e48ef6936c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 12:17:05 +0900 Subject: [PATCH 22/47] Bring test scene structure up-to-date --- .../Multiplayer/TestSceneLoungeRoomsContainer.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index eb4dc909df..cb873fc3eb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -27,11 +28,11 @@ namespace osu.Game.Tests.Visual.Multiplayer [Cached(Type = typeof(IRoomManager))] private TestRoomManager roomManager = new TestRoomManager(); + private RoomsContainer container; + [BackgroundDependencyLoader] private void load() { - RoomsContainer container; - Child = container = new RoomsContainer { Anchor = Anchor.Centre, @@ -39,9 +40,18 @@ namespace osu.Game.Tests.Visual.Multiplayer Width = 0.5f, JoinRequested = joinRequested }; + } + + public override void SetUpSteps() + { + base.SetUpSteps(); AddStep("clear rooms", () => roomManager.Rooms.Clear()); + } + [Test] + public void TestBasicListChanges() + { AddStep("add rooms", () => { for (int i = 0; i < 3; i++) From 50c4e34c92091616053871ae652e11f10a7800e5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 11:56:09 +0900 Subject: [PATCH 23/47] Add ruleset to multiplayer filter criteria --- osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs | 8 +++++++- .../Screens/Multi/Lounge/Components/FilterCriteria.cs | 3 +++ .../Screens/Multi/Lounge/Components/RoomsContainer.cs | 3 +++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs index 29d41132a7..9f93afec9d 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterControl.cs @@ -7,6 +7,7 @@ using osu.Framework.Bindables; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Overlays.SearchableList; +using osu.Game.Rulesets; using osuTK.Graphics; namespace osu.Game.Screens.Multi.Lounge.Components @@ -22,6 +23,9 @@ namespace osu.Game.Screens.Multi.Lounge.Components [Resolved(CanBeNull = true)] private Bindable filter { get; set; } + [Resolved] + private IBindable ruleset { get; set; } + public FilterControl() { DisplayStyleControl.Hide(); @@ -38,6 +42,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components { base.LoadComplete(); + ruleset.BindValueChanged(_ => updateFilter()); Search.Current.BindValueChanged(_ => scheduleUpdateFilter()); Tabs.Current.BindValueChanged(_ => updateFilter(), true); } @@ -58,7 +63,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components { SearchString = Search.Current.Value ?? string.Empty, PrimaryFilter = Tabs.Current.Value, - SecondaryFilter = DisplayStyleControl.Dropdown.Current.Value + SecondaryFilter = DisplayStyleControl.Dropdown.Current.Value, + Ruleset = ruleset.Value }; } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs b/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs index 666bc44a8d..26d445e151 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/FilterCriteria.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 osu.Game.Rulesets; + namespace osu.Game.Screens.Multi.Lounge.Components { public class FilterCriteria @@ -8,5 +10,6 @@ namespace osu.Game.Screens.Multi.Lounge.Components public string SearchString; public PrimaryFilter PrimaryFilter; public SecondaryFilter SecondaryFilter; + public RulesetInfo Ruleset; } } diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 6ef0f6b6db..f5e0e2bbee 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -65,6 +65,9 @@ namespace osu.Game.Screens.Multi.Lounge.Components else { bool matchingFilter = true; + + matchingFilter &= r.Room.Playlist.Any(i => i.Ruleset.Equals(criteria.Ruleset)); + matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0); switch (criteria.SecondaryFilter) From 304a071e39578def381a4edc9b5c6638d2c9f613 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 11:56:20 +0900 Subject: [PATCH 24/47] Remove leasing at a Multiplayer screen level --- osu.Game/Screens/Multi/Multiplayer.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 9d6a459d14..72bf5a37e8 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -32,8 +32,6 @@ namespace osu.Game.Screens.Multi { public override bool CursorVisible => (screenStack.CurrentScreen as IMultiplayerSubScreen)?.CursorVisible ?? true; - public override bool DisallowExternalBeatmapRulesetChanges => true; - private readonly MultiplayerWaveContainer waves; private readonly OsuButton createButton; @@ -277,11 +275,7 @@ namespace osu.Game.Screens.Multi private void updateTrack(ValueChangedEvent _ = null) { - bool isMatch = screenStack.CurrentScreen is MatchSubScreen; - - Beatmap.Disabled = isMatch; - - if (isMatch) + if (screenStack.CurrentScreen is MatchSubScreen) { var track = Beatmap.Value?.Track; From a79d6ff27a68b6a9dd5b3abc901451b30dab3bd5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 11:59:33 +0900 Subject: [PATCH 25/47] Change transition to better handle mass filter cases --- osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs index f6cbe300f3..d45dac1ae6 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/DrawableRoom.cs @@ -75,8 +75,13 @@ namespace osu.Game.Screens.Multi.Lounge.Components { matchingFilter = value; - if (IsLoaded) - this.FadeTo(MatchingFilter ? 1 : 0, 200); + if (!IsLoaded) + return; + + if (matchingFilter) + this.FadeIn(200); + else + Hide(); } } From 1dc7fc4a33aee7e68dcc25789892d49cb69caa2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 12:26:14 +0900 Subject: [PATCH 26/47] Fix case where playlist is empty --- osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index f5e0e2bbee..a5881cc2f7 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -66,7 +66,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components { bool matchingFilter = true; - matchingFilter &= r.Room.Playlist.Any(i => i.Ruleset.Equals(criteria.Ruleset)); + matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Equals(criteria.Ruleset)); matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0); From d32bb79e5a7bb3a78bc670f35cf074a6c2a656a4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 12:26:08 +0900 Subject: [PATCH 27/47] Add simple filtering test --- .../TestSceneLoungeRoomsContainer.cs | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index cb873fc3eb..8bc780f4e0 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -51,10 +51,42 @@ namespace osu.Game.Tests.Visual.Multiplayer [Test] public void TestBasicListChanges() + { + addRooms(3); + + AddAssert("has 3 rooms", () => container.Rooms.Count == 3); + AddStep("remove first room", () => roomManager.Rooms.Remove(roomManager.Rooms.FirstOrDefault())); + AddAssert("has 2 rooms", () => container.Rooms.Count == 2); + AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0)); + + AddStep("select first room", () => container.Rooms.First().Action?.Invoke()); + AddAssert("first room selected", () => Room == roomManager.Rooms.First()); + + AddStep("join first room", () => container.Rooms.First().Action?.Invoke()); + AddAssert("first room joined", () => roomManager.Rooms.First().Status.Value is JoinedRoomStatus); + } + + [Test] + public void TestStringFiltering() + { + addRooms(4); + + AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4); + + AddStep("filter one room", () => container.Filter(new FilterCriteria { SearchString = "1" })); + + AddUntilStep("1 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 1); + + AddStep("remove filter", () => container.Filter(null)); + + AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4); + } + + private void addRooms(int count) { AddStep("add rooms", () => { - for (int i = 0; i < 3; i++) + for (int i = 0; i < count; i++) { roomManager.Rooms.Add(new Room { @@ -65,17 +97,6 @@ namespace osu.Game.Tests.Visual.Multiplayer }); } }); - - AddAssert("has 2 rooms", () => container.Rooms.Count == 3); - AddStep("remove first room", () => roomManager.Rooms.Remove(roomManager.Rooms.FirstOrDefault())); - AddAssert("has 2 rooms", () => container.Rooms.Count == 2); - AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0)); - - AddStep("select first room", () => container.Rooms.First().Action?.Invoke()); - AddAssert("first room selected", () => Room == roomManager.Rooms.First()); - - AddStep("join first room", () => container.Rooms.First().Action?.Invoke()); - AddAssert("first room joined", () => roomManager.Rooms.First().Status.Value is JoinedRoomStatus); } private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus(); From 061b8c389fa12112f17071e5044de143f187a999 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 13:02:38 +0900 Subject: [PATCH 28/47] Fix null search string causing an exception --- osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index a5881cc2f7..88cc348000 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -68,7 +68,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components matchingFilter &= r.Room.Playlist.Count == 0 || r.Room.Playlist.Any(i => i.Ruleset.Equals(criteria.Ruleset)); - matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0); + if (!string.IsNullOrEmpty(criteria.SearchString)) + matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0); switch (criteria.SecondaryFilter) { From 43b22e3b63d9480e0a20b8f83ff3420577f575bf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 13:03:15 +0900 Subject: [PATCH 29/47] Add a ruleset filtering test --- .../TestSceneLoungeRoomsContainer.cs | 39 +++++++++++++++++-- 1 file changed, 36 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index 8bc780f4e0..af02f1e514 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -8,8 +8,12 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Online.Multiplayer; +using osu.Game.Rulesets; +using osu.Game.Rulesets.Catch; +using osu.Game.Rulesets.Osu; using osu.Game.Screens.Multi; using osu.Game.Screens.Multi.Lounge.Components; using osu.Game.Users; @@ -82,19 +86,48 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("4 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 4); } - private void addRooms(int count) + [Test] + public void TestRulesetFiltering() + { + addRooms(2, new OsuRuleset().RulesetInfo); + addRooms(3, new CatchRuleset().RulesetInfo); + + AddUntilStep("5 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 5); + + AddStep("filter osu! rooms", () => container.Filter(new FilterCriteria { Ruleset = new OsuRuleset().RulesetInfo })); + + AddUntilStep("2 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 2); + + AddStep("filter catch rooms", () => container.Filter(new FilterCriteria { Ruleset = new CatchRuleset().RulesetInfo })); + + AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3); + } + + private void addRooms(int count, RulesetInfo ruleset = null) { AddStep("add rooms", () => { for (int i = 0; i < count; i++) { - roomManager.Rooms.Add(new Room + var room = new Room { RoomID = { Value = i }, Name = { Value = $"Room {i}" }, Host = { Value = new User { Username = "Host" } }, EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) } - }); + }; + + if (ruleset != null) + room.Playlist.Add(new PlaylistItem + { + Ruleset = ruleset, + Beatmap = new BeatmapInfo + { + Metadata = new BeatmapMetadata() + } + }); + + roomManager.Rooms.Add(room); } }); } From 49ec1d4a9938e02f07b24a1167e08efd391612d8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 13:04:29 +0900 Subject: [PATCH 30/47] Fix braces style --- .../Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs index af02f1e514..fe14a1ff0a 100644 --- a/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs +++ b/osu.Game.Tests/Visual/Multiplayer/TestSceneLoungeRoomsContainer.cs @@ -118,6 +118,7 @@ namespace osu.Game.Tests.Visual.Multiplayer }; if (ruleset != null) + { room.Playlist.Add(new PlaylistItem { Ruleset = ruleset, @@ -126,6 +127,7 @@ namespace osu.Game.Tests.Visual.Multiplayer Metadata = new BeatmapMetadata() } }); + } roomManager.Rooms.Add(room); } From daf5fa9da4ade1124f75a250e0c1c8964372fe16 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 13:16:32 +0900 Subject: [PATCH 31/47] Throw NotSupportedException instead --- osu.Game.Rulesets.Catch/Objects/JuiceStream.cs | 2 +- osu.Game.Rulesets.Osu/Objects/Slider.cs | 2 +- osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs index 4d68bf592c..b014b32305 100644 --- a/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs +++ b/osu.Game.Rulesets.Catch/Objects/JuiceStream.cs @@ -113,7 +113,7 @@ namespace osu.Game.Rulesets.Catch.Objects public double EndTime { get => StartTime + this.SpanCount() * Path.Distance / Velocity; - set => throw new System.NotImplementedException(); + set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } public float EndX => X + this.CurvePositionAt(1).X / CatchPlayfield.BASE_WIDTH; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index bab7e6dbee..95fb6d9d48 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Objects public double EndTime { get => StartTime + this.SpanCount() * Path.Distance / Velocity; - set => throw new System.NotImplementedException(); + set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } public double Duration => EndTime - StartTime; diff --git a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs index b5cd9be245..53cdf457c4 100644 --- a/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs +++ b/osu.Game/Rulesets/Objects/Legacy/ConvertSlider.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Objects.Legacy public double EndTime { get => StartTime + this.SpanCount() * Distance / Velocity; - set => throw new System.NotImplementedException(); + set => throw new System.NotSupportedException($"Adjust via {nameof(RepeatCount)} instead"); // can be implemented if/when needed. } public double Duration => EndTime - StartTime; From e548a4b8ddba4b6c90dfdab0935715a5da008341 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 13:36:49 +0900 Subject: [PATCH 32/47] Fix missing bind causing regression in filter --- osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index 88cc348000..e306706be9 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -54,6 +54,8 @@ namespace osu.Game.Screens.Multi.Lounge.Components roomManager.RoomsUpdated += updateSorting; rooms.BindTo(roomManager.Rooms); + + filter.BindValueChanged(criteria => Filter(criteria.NewValue)); } public void Filter(FilterCriteria criteria) From 114fd967c190988dab88aca1fc0f3eea0bcf9627 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 13:47:34 +0900 Subject: [PATCH 33/47] Revert "Remove leasing at a Multiplayer screen level" This reverts commit 304a071e39578def381a4edc9b5c6638d2c9f613. --- osu.Game/Screens/Multi/Multiplayer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 72bf5a37e8..9d6a459d14 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -32,6 +32,8 @@ namespace osu.Game.Screens.Multi { public override bool CursorVisible => (screenStack.CurrentScreen as IMultiplayerSubScreen)?.CursorVisible ?? true; + public override bool DisallowExternalBeatmapRulesetChanges => true; + private readonly MultiplayerWaveContainer waves; private readonly OsuButton createButton; @@ -275,7 +277,11 @@ namespace osu.Game.Screens.Multi private void updateTrack(ValueChangedEvent _ = null) { - if (screenStack.CurrentScreen is MatchSubScreen) + bool isMatch = screenStack.CurrentScreen is MatchSubScreen; + + Beatmap.Disabled = isMatch; + + if (isMatch) { var track = Beatmap.Value?.Track; From 81cadb787569559fe22e058c59b8f456443136bd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 14:22:01 +0900 Subject: [PATCH 34/47] Simplify the way multiple subscreens handle their disable states via a custom stack --- .../Screens/Multi/Lounge/LoungeSubScreen.cs | 24 ++++++++++++------- osu.Game/Screens/Multi/Multiplayer.cs | 10 ++++---- .../Multi/MultiplayerSubScreenStack.cs | 24 +++++++++++++++++++ osu.Game/Screens/OsuScreenStack.cs | 6 ++--- osu.Game/Screens/Select/MatchSongSelect.cs | 13 ---------- 5 files changed, 47 insertions(+), 30 deletions(-) create mode 100644 osu.Game/Screens/Multi/MultiplayerSubScreenStack.cs diff --git a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs index 0a48f761cf..3709b85fcb 100644 --- a/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs +++ b/osu.Game/Screens/Multi/Lounge/LoungeSubScreen.cs @@ -91,6 +91,22 @@ namespace osu.Game.Screens.Multi.Lounge public override void OnEntering(IScreen last) { base.OnEntering(last); + + onReturning(); + } + + public override void OnResuming(IScreen last) + { + base.OnResuming(last); + + if (currentRoom.Value?.RoomID.Value == null) + currentRoom.Value = new Room(); + + onReturning(); + } + + private void onReturning() + { Filter.Search.HoldFocus = true; } @@ -106,14 +122,6 @@ namespace osu.Game.Screens.Multi.Lounge Filter.Search.HoldFocus = false; } - public override void OnResuming(IScreen last) - { - base.OnResuming(last); - - if (currentRoom.Value?.RoomID.Value == null) - currentRoom.Value = new Room(); - } - private void joinRequested(Room room) { processingOverlay.Show(); diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 9d6a459d14..2277157134 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -32,6 +32,8 @@ namespace osu.Game.Screens.Multi { public override bool CursorVisible => (screenStack.CurrentScreen as IMultiplayerSubScreen)?.CursorVisible ?? true; + // this is required due to PlayerLoader eventually being pushed to the main stack + // while leases may be taken out by a subscreen. public override bool DisallowExternalBeatmapRulesetChanges => true; private readonly MultiplayerWaveContainer waves; @@ -96,7 +98,7 @@ namespace osu.Game.Screens.Multi { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = Header.HEIGHT }, - Child = screenStack = new OsuScreenStack { RelativeSizeAxes = Axes.Both } + Child = screenStack = new MultiplayerSubScreenStack { RelativeSizeAxes = Axes.Both } }, new Header(screenStack), createButton = new HeaderButton @@ -277,11 +279,7 @@ namespace osu.Game.Screens.Multi private void updateTrack(ValueChangedEvent _ = null) { - bool isMatch = screenStack.CurrentScreen is MatchSubScreen; - - Beatmap.Disabled = isMatch; - - if (isMatch) + if (screenStack.CurrentScreen is MatchSubScreen) { var track = Beatmap.Value?.Track; diff --git a/osu.Game/Screens/Multi/MultiplayerSubScreenStack.cs b/osu.Game/Screens/Multi/MultiplayerSubScreenStack.cs new file mode 100644 index 0000000000..3b0ed0dba1 --- /dev/null +++ b/osu.Game/Screens/Multi/MultiplayerSubScreenStack.cs @@ -0,0 +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.Screens; + +namespace osu.Game.Screens.Multi +{ + public class MultiplayerSubScreenStack : OsuScreenStack + { + protected override void ScreenChanged(IScreen prev, IScreen next) + { + base.ScreenChanged(prev, next); + + // because this is a screen stack within a screen stack, let's manually handle disabled changes to simplify things. + var osuScreen = ((OsuScreen)next); + + bool disallowChanges = osuScreen.DisallowExternalBeatmapRulesetChanges; + + osuScreen.Beatmap.Disabled = disallowChanges; + osuScreen.Ruleset.Disabled = disallowChanges; + osuScreen.Mods.Disabled = disallowChanges; + } + } +} diff --git a/osu.Game/Screens/OsuScreenStack.cs b/osu.Game/Screens/OsuScreenStack.cs index a05933ef0e..e2a0414df7 100644 --- a/osu.Game/Screens/OsuScreenStack.cs +++ b/osu.Game/Screens/OsuScreenStack.cs @@ -26,7 +26,7 @@ namespace osu.Game.Screens }; ScreenPushed += screenPushed; - ScreenExited += screenExited; + ScreenExited += ScreenChanged; } private void screenPushed(IScreen prev, IScreen next) @@ -42,10 +42,10 @@ namespace osu.Game.Screens // create dependencies synchronously to ensure leases are in a sane state. ((OsuScreen)next).CreateLeasedDependencies((prev as OsuScreen)?.Dependencies ?? Dependencies); - setParallax(next); + ScreenChanged(prev, next); } - private void screenExited(IScreen prev, IScreen next) + protected virtual void ScreenChanged(IScreen prev, IScreen next) { setParallax(next); } diff --git a/osu.Game/Screens/Select/MatchSongSelect.cs b/osu.Game/Screens/Select/MatchSongSelect.cs index a78477c771..6ba4157797 100644 --- a/osu.Game/Screens/Select/MatchSongSelect.cs +++ b/osu.Game/Screens/Select/MatchSongSelect.cs @@ -65,20 +65,7 @@ namespace osu.Game.Screens.Select Mods.Value = CurrentItem.Value.RequiredMods?.ToArray() ?? Array.Empty(); } - Beatmap.Disabled = true; - Ruleset.Disabled = true; - Mods.Disabled = true; - return false; } - - public override void OnEntering(IScreen last) - { - base.OnEntering(last); - - Beatmap.Disabled = false; - Ruleset.Disabled = false; - Mods.Disabled = false; - } } } From fd71a53717bf57782cb2e0f8379e4379ea7b191d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 14:28:09 +0900 Subject: [PATCH 35/47] Fix test regression --- osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs index e306706be9..063957d816 100644 --- a/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs +++ b/osu.Game/Screens/Multi/Lounge/Components/RoomsContainer.cs @@ -55,7 +55,7 @@ namespace osu.Game.Screens.Multi.Lounge.Components rooms.BindTo(roomManager.Rooms); - filter.BindValueChanged(criteria => Filter(criteria.NewValue)); + filter?.BindValueChanged(criteria => Filter(criteria.NewValue)); } public void Filter(FilterCriteria criteria) From c138e3907e13543a95a86a25021d5f35b56af285 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Feb 2020 14:35:45 +0900 Subject: [PATCH 36/47] Move methods below ctor --- .../Timeline/TimelineHitObjectBlueprint.cs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index b46a373818..5225a4299e 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -25,8 +25,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { private readonly Circle circle; - protected override bool ShouldBeConsideredForInput(Drawable child) => true; - [UsedImplicitly] private readonly Bindable startTime; @@ -42,11 +40,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private const float circle_size = 16; - public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => - base.ReceivePositionalInputAt(screenSpacePos) || - circle.ReceivePositionalInputAt(screenSpacePos) || - dragBar?.ReceivePositionalInputAt(screenSpacePos) == true; - public TimelineHitObjectBlueprint(HitObject hitObject) : base(hitObject) { @@ -138,6 +131,13 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline Width = (float)(HitObject.GetEndTime() - HitObject.StartTime); } + protected override bool ShouldBeConsideredForInput(Drawable child) => true; + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => + base.ReceivePositionalInputAt(screenSpacePos) || + circle.ReceivePositionalInputAt(screenSpacePos) || + dragBar?.ReceivePositionalInputAt(screenSpacePos) == true; + protected override void OnSelected() { updateShadows(); From 6ae0efa40d350521d28720fccd050ce7502fb8e3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 6 Feb 2020 14:47:43 +0900 Subject: [PATCH 37/47] Fix adjustment not working when dragged before object --- .../Components/Timeline/TimelineHitObjectBlueprint.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs index 5225a4299e..8f12c2f0ed 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineHitObjectBlueprint.cs @@ -275,18 +275,18 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline case IHasRepeats repeatHitObject: // find the number of repeats which can fit in the requested time. var lengthOfOneRepeat = repeatHitObject.Duration / (repeatHitObject.RepeatCount + 1); - var proposedCount = (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1; + var proposedCount = Math.Max(0, (int)((time - hitObject.StartTime) / lengthOfOneRepeat) - 1); - if (proposedCount == repeatHitObject.RepeatCount || proposedCount < 0) + if (proposedCount == repeatHitObject.RepeatCount) return; repeatHitObject.RepeatCount = proposedCount; break; case IHasEndTime endTimeHitObject: - var snappedTime = beatSnapProvider.SnapTime(time); + var snappedTime = Math.Max(hitObject.StartTime, beatSnapProvider.SnapTime(time)); - if (endTimeHitObject.EndTime == snappedTime || snappedTime <= hitObject.StartTime) + if (endTimeHitObject.EndTime == snappedTime) return; endTimeHitObject.EndTime = snappedTime; From 6b67b601e9033f6d48d813561fd602be25863bef Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 17:35:13 +0900 Subject: [PATCH 38/47] 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 e5a1ec2f4e..702591231b 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ce58be52ee..81f59a4993 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 6ab3c0f2d2..8e359970a1 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From 48350638a247faea83506b24b2eb5bd4b5a48803 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 18:38:00 +0900 Subject: [PATCH 39/47] Hide drag handles of all playlist items not currently being dragged --- osu.Game/Overlays/Music/Playlist.cs | 6 ++++++ osu.Game/Overlays/Music/PlaylistItem.cs | 20 +++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Music/Playlist.cs b/osu.Game/Overlays/Music/Playlist.cs index 8744a6db8b..1ba568443d 100644 --- a/osu.Game/Overlays/Music/Playlist.cs +++ b/osu.Game/Overlays/Music/Playlist.cs @@ -18,6 +18,11 @@ namespace osu.Game.Overlays.Music public readonly Bindable SelectedSet = new Bindable(); + /// + /// Whether any item is currently being dragged. Used to hide other items' drag handles. + /// + private readonly BindableBool playlistDragActive = new BindableBool(); + public new MarginPadding Padding { get => base.Padding; @@ -31,6 +36,7 @@ namespace osu.Game.Overlays.Music protected override RearrangeableListItem CreateDrawable(BeatmapSetInfo item) => new PlaylistItem(item) { SelectedSet = { BindTarget = SelectedSet }, + PlaylistDragActive = { BindTarget = playlistDragActive }, RequestSelection = set => RequestSelection?.Invoke(set) }; diff --git a/osu.Game/Overlays/Music/PlaylistItem.cs b/osu.Game/Overlays/Music/PlaylistItem.cs index 0f68df737e..0569261867 100644 --- a/osu.Game/Overlays/Music/PlaylistItem.cs +++ b/osu.Game/Overlays/Music/PlaylistItem.cs @@ -23,7 +23,10 @@ namespace osu.Game.Overlays.Music { private const float fade_duration = 100; + public BindableBool PlaylistDragActive = new BindableBool(); + public readonly Bindable SelectedSet = new Bindable(); + public Action RequestSelection; private PlaylistItemHandle handle; @@ -122,11 +125,26 @@ namespace osu.Game.Overlays.Music return true; } + protected override bool OnDragStart(DragStartEvent e) + { + if (!base.OnDragStart(e)) + return false; + + PlaylistDragActive.Value = true; + return true; + } + + protected override void OnDragEnd(DragEndEvent e) + { + PlaylistDragActive.Value = false; + base.OnDragEnd(e); + } + protected override bool IsDraggableAt(Vector2 screenSpacePos) => handle.HandlingDrag; protected override bool OnHover(HoverEvent e) { - handle.UpdateHoverState(true); + handle.UpdateHoverState(IsDragged || !PlaylistDragActive.Value); return base.OnHover(e); } From 1613198834e3b32bda05096704a0f47c3ddddb5c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 6 Feb 2020 19:43:33 +0900 Subject: [PATCH 40/47] Set a sane default keyboard step for mod settings --- osu.Game/Configuration/SettingSourceAttribute.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/SettingSourceAttribute.cs b/osu.Game/Configuration/SettingSourceAttribute.cs index f859dccc80..a3788e4582 100644 --- a/osu.Game/Configuration/SettingSourceAttribute.cs +++ b/osu.Game/Configuration/SettingSourceAttribute.cs @@ -45,7 +45,8 @@ namespace osu.Game.Configuration yield return new SettingsSlider { LabelText = attr.Label, - Bindable = bNumber + Bindable = bNumber, + KeyboardStep = 0.1f, }; break; @@ -54,7 +55,8 @@ namespace osu.Game.Configuration yield return new SettingsSlider { LabelText = attr.Label, - Bindable = bNumber + Bindable = bNumber, + KeyboardStep = 0.1f, }; break; From 5946ad7d809f7e7500ecc1a18d1f5310d47b635a Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Thu, 6 Feb 2020 16:54:02 +0300 Subject: [PATCH 41/47] Fix possible memory leak and better user change test support --- .../Online/TestSceneCommentsContainer.cs | 24 ++++++++++--------- .../Overlays/Comments/CommentsContainer.cs | 7 +++++- 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs index 33acc75fa8..2a43ba3f99 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCommentsContainer.cs @@ -10,7 +10,8 @@ using osu.Framework.Graphics; using osu.Game.Overlays.Comments; using osu.Game.Overlays; using osu.Framework.Allocation; -using osu.Game.Online.API; +using osu.Framework.Bindables; +using osu.Game.Users; namespace osu.Game.Tests.Visual.Online { @@ -35,31 +36,32 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple); - private CommentsContainer comments; - private readonly BasicScrollContainer scroll; - public TestSceneCommentsContainer() { + BasicScrollContainer scroll; + TestCommentsContainer comments; + Add(scroll = new BasicScrollContainer { RelativeSizeAxes = Axes.Both, - Child = comments = new CommentsContainer() + Child = comments = new TestCommentsContainer() }); - } - [BackgroundDependencyLoader] - private void load(IAPIProvider api) - { AddStep("Big Black comments", () => comments.ShowComments(CommentableType.Beatmapset, 41823)); AddStep("Airman comments", () => comments.ShowComments(CommentableType.Beatmapset, 24313)); AddStep("Lazer build comments", () => comments.ShowComments(CommentableType.Build, 4772)); AddStep("News comments", () => comments.ShowComments(CommentableType.NewsPost, 715)); - AddStep("Trigger user change", api.LocalUser.TriggerChange); + AddStep("Trigger user change", comments.User.TriggerChange); AddStep("Idle state", () => { scroll.Clear(); - scroll.Add(comments = new CommentsContainer()); + scroll.Add(comments = new TestCommentsContainer()); }); } + + private class TestCommentsContainer : CommentsContainer + { + public new Bindable User => base.User; + } } } diff --git a/osu.Game/Overlays/Comments/CommentsContainer.cs b/osu.Game/Overlays/Comments/CommentsContainer.cs index 3d68f453b7..33a6be0d7a 100644 --- a/osu.Game/Overlays/Comments/CommentsContainer.cs +++ b/osu.Game/Overlays/Comments/CommentsContainer.cs @@ -12,6 +12,7 @@ using osu.Game.Online.API.Requests.Responses; using System.Threading; using System.Linq; using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Users; namespace osu.Game.Overlays.Comments { @@ -23,6 +24,8 @@ namespace osu.Game.Overlays.Comments public readonly Bindable Sort = new Bindable(); public readonly BindableBool ShowDeleted = new BindableBool(); + protected readonly Bindable User = new Bindable(); + [Resolved] private IAPIProvider api { get; set; } @@ -109,11 +112,13 @@ namespace osu.Game.Overlays.Comments } } }); + + User.BindTo(api.LocalUser); } protected override void LoadComplete() { - api.LocalUser.BindValueChanged(_ => refetchComments()); + User.BindValueChanged(_ => refetchComments()); Sort.BindValueChanged(_ => refetchComments(), true); base.LoadComplete(); } From ecde641729da944bd819136b6fb9080012222339 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 5 Feb 2020 21:52:36 +0100 Subject: [PATCH 42/47] Randomise colours in scrolling test scene Switch to using randomised colours in TestSceneScrollingHitObjects to better distinguish individual hit objects. --- .../Visual/Gameplay/TestSceneScrollingHitObjects.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs index 8629522dc2..da5a83e194 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Threading; +using osu.Framework.Utils; using osu.Game.Configuration; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Objects; @@ -19,6 +20,7 @@ using osu.Game.Rulesets.Timing; using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI.Scrolling; using osuTK; +using osuTK.Graphics; namespace osu.Game.Tests.Visual.Gameplay { @@ -236,7 +238,11 @@ namespace osu.Game.Tests.Visual.Gameplay AutoSizeAxes = Axes.Both; - AddInternal(new Box { Size = new Vector2(75) }); + AddInternal(new Box + { + Size = new Vector2(75), + Colour = new Color4(RNG.NextSingle(), RNG.NextSingle(), RNG.NextSingle(), 1) + }); } } } From 12469469ad14bd48de4721832ede56b972028b72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 5 Feb 2020 22:56:44 +0100 Subject: [PATCH 43/47] Add reproduction test steps for lifetime bug Modify TestSceneScrollingHitObjects to contain a test case that serves as a reproduction for a visual bug in which using the overlapping scroll algorithm results in an incorrect origin adjustment for lifetime. --- .../Gameplay/TestSceneScrollingHitObjects.cs | 88 ++++++++++++------- 1 file changed, 56 insertions(+), 32 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs index da5a83e194..d03716db2e 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneScrollingHitObjects.cs @@ -32,7 +32,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Cached(typeof(IReadOnlyList))] private IReadOnlyList mods { get; set; } = Array.Empty(); - private const int spawn_interval = 5000; + private const int time_range = 5000; + private const int spawn_rate = time_range / 10; private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4]; private readonly TestPlayfield[] playfields = new TestPlayfield[4]; @@ -52,13 +53,13 @@ namespace osu.Game.Tests.Visual.Gameplay { RelativeSizeAxes = Axes.Both, Child = playfields[0] = new TestPlayfield(), - TimeRange = spawn_interval + TimeRange = time_range }, scrollContainers[1] = new ScrollingTestContainer(ScrollingDirection.Down) { RelativeSizeAxes = Axes.Both, Child = playfields[1] = new TestPlayfield(), - TimeRange = spawn_interval + TimeRange = time_range }, }, new Drawable[] @@ -67,13 +68,13 @@ namespace osu.Game.Tests.Visual.Gameplay { RelativeSizeAxes = Axes.Both, Child = playfields[2] = new TestPlayfield(), - TimeRange = spawn_interval + TimeRange = time_range }, scrollContainers[3] = new ScrollingTestContainer(ScrollingDirection.Right) { RelativeSizeAxes = Axes.Both, Child = playfields[3] = new TestPlayfield(), - TimeRange = spawn_interval + TimeRange = time_range } } } @@ -86,31 +87,55 @@ namespace osu.Game.Tests.Visual.Gameplay { scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0))); - for (int i = 0; i <= spawn_interval; i += 1000) + for (int i = spawn_rate / 2; i <= time_range; i += spawn_rate) addHitObject(Time.Current + i); hitObjectSpawnDelegate?.Cancel(); - hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + spawn_interval), 1000, true); + hitObjectSpawnDelegate = Scheduler.AddDelayed(() => addHitObject(Time.Current + time_range), spawn_rate, true); } + private IList testControlPoints => new List + { + new MultiplierControlPoint(time_range) { DifficultyPoint = { SpeedMultiplier = 1.25 } }, + new MultiplierControlPoint(1.5 * time_range) { DifficultyPoint = { SpeedMultiplier = 1 } }, + new MultiplierControlPoint(2 * time_range) { DifficultyPoint = { SpeedMultiplier = 1.5 } } + }; + [Test] public void TestScrollAlgorithms() { - AddStep("Constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant)); - AddStep("Overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping)); - AddStep("Sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential)); + AddStep("constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant)); + AddStep("overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping)); + AddStep("sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential)); - AddSliderStep("Time range", 100, 10000, spawn_interval, v => scrollContainers.Where(c => c != null).ForEach(c => c.TimeRange = v)); - AddStep("Add control point", () => addControlPoint(Time.Current + spawn_interval)); + AddSliderStep("time range", 100, 10000, time_range, v => scrollContainers.Where(c => c != null).ForEach(c => c.TimeRange = v)); + + AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current)); } [Test] - public void TestScrollLifetime() + public void TestConstantScrollLifetime() { - AddStep("Set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant)); + AddStep("set constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant)); // scroll container time range must be less than the rate of spawning hitobjects // otherwise the hitobjects will spawn already partly visible on screen and look wrong - AddStep("Set time range", () => scrollContainers.ForEach(c => c.TimeRange = spawn_interval / 2.0)); + AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0)); + } + + [Test] + public void TestSequentialScrollLifetime() + { + AddStep("set sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential)); + AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0)); + AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current)); + } + + [Test] + public void TestOverlappingScrollLifetime() + { + AddStep("set overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping)); + AddStep("set time range", () => scrollContainers.ForEach(c => c.TimeRange = time_range / 2.0)); + AddStep("add control points", () => addControlPoints(testControlPoints, Time.Current)); } private void addHitObject(double time) @@ -124,28 +149,27 @@ namespace osu.Game.Tests.Visual.Gameplay }); } - private void addControlPoint(double time) + private TestDrawableControlPoint createDrawablePoint(TestPlayfield playfield, double t) { - scrollContainers.ForEach(c => + var obj = new TestDrawableControlPoint(playfield.Direction, t); + setAnchor(obj, playfield); + return obj; + } + + private void addControlPoints(IList controlPoints, double sequenceStartTime) + { + controlPoints.ForEach(point => point.StartTime += sequenceStartTime); + + scrollContainers.ForEach(container => { - c.ControlPoints.Add(new MultiplierControlPoint(time) { DifficultyPoint = { SpeedMultiplier = 3 } }); - c.ControlPoints.Add(new MultiplierControlPoint(time + 2000) { DifficultyPoint = { SpeedMultiplier = 2 } }); - c.ControlPoints.Add(new MultiplierControlPoint(time + 3000) { DifficultyPoint = { SpeedMultiplier = 1 } }); + container.ControlPoints.AddRange(controlPoints); }); - playfields.ForEach(p => + foreach (var playfield in playfields) { - TestDrawableControlPoint createDrawablePoint(double t) - { - var obj = new TestDrawableControlPoint(p.Direction, t); - setAnchor(obj, p); - return obj; - } - - p.Add(createDrawablePoint(time)); - p.Add(createDrawablePoint(time + 2000)); - p.Add(createDrawablePoint(time + 3000)); - }); + foreach (var controlPoint in controlPoints) + playfield.Add(createDrawablePoint(playfield, controlPoint.StartTime)); + } } private void setAnchor(DrawableHitObject obj, TestPlayfield playfield) From 5fde4f2c0cda1c48a130fb4771eac1230ff5ba45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Thu, 6 Feb 2020 22:46:31 +0100 Subject: [PATCH 44/47] Fix lifetime calculation in overlapping algorithm Changes to lifetime calculation in scrolling rulesets introduced in #7367, which aimed to account for the distance between hit objects' origin and its edge entering the scrolling area, fixed some issues with hitobjects appearing abruptly, but also regressed some other scenarios. Upon investigation, the regression was localised to the overlapping scroll algorithm. The reason for this was two-fold: * The previous code used TimeAt() to calculate the time of travel from the hit object's edge to its origin. For other algorithms, that time can be accurately reconstructed, because they don't have periods of time where there are multiple hit objects scrolling at different velocities. That invariant does not hold for the overlapping algorithm, therefore it is possible for different values to be technically correct for TimeAt(). However, the only value that matters for the adjustment is the one that's indicated by the control point that applies to the hit object origin, which can be uniquely identified. * Additionally, the offset returned (even if correct) was applied externally to the hit object's start time and passed to GetDisplayStartTime(). In the overlapping algorithm, the choice of control point used in GetDisplayStartTime() is important, since the value of the speed multiplier is read within. Externally rewinding the hit object's start time meant that in some cases the speed multiplier of the *previous* control point is applied, which led to hit objects appearing too late if the scrolling rate decreased. Because of the above, modify GetDisplayStartTime() to take the offset into account in all algorithms, and apply the adjustment correctly inside of them. The constant and sequential algorithms needed no adjustment from the previous logic, since: * the constant algorithm disregarded control points, and * the sequential algorithm would effectively rewind to time = 0, calculate the absolute distance from time = 0 to the hit object start, apply the origin offset *to the absolute distance*, and then convert back to time, applying all control points in sequence. Due to this it was impossible for control points to get mixed up while calculating. As for the overlapping algorithm, the high-level logic is as follows: * The distance that the origin has to travel is the length of the scroll plus the distance from the origin to the object edge. * The above distance divided by the scroll length gives the relative scroll lengths that the object has to travel. * As one relative scroll length takes one time range, the relative travel length multiplied by the time range gives the absolute travel time of the object origin. * Finally, the control point multiplier applicable at origin time is applied to the whole travel time. Correctness of the above is demonstrated by visual tests added before and headless unit tests of the algorithms themselves. The sequential scroll algorithm was not covered by unit tests, and remains uncovered due to floating-point inaccuracies that should be addressed separately. --- .../ScrollAlgorithms/ConstantScrollTest.cs | 19 ++++++++--- .../ScrollAlgorithms/OverlappingScrollTest.cs | 19 ++++++++--- .../Algorithms/ConstantScrollAlgorithm.cs | 6 +++- .../Scrolling/Algorithms/IScrollAlgorithm.cs | 32 +++++++++++++++---- .../Algorithms/OverlappingScrollAlgorithm.cs | 7 ++-- .../Algorithms/SequentialScrollAlgorithm.cs | 6 +++- .../Scrolling/ScrollingHitObjectContainer.cs | 3 +- .../Tests/Visual/ScrollingTestContainer.cs | 4 +-- 8 files changed, 71 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs index d7f709dc03..a6e8622b6f 100644 --- a/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs +++ b/osu.Game.Tests/ScrollAlgorithms/ConstantScrollTest.cs @@ -18,12 +18,21 @@ namespace osu.Game.Tests.ScrollAlgorithms } [Test] - public void TestDisplayStartTime() + public void TestPointDisplayStartTime() { - Assert.AreEqual(-8000, algorithm.GetDisplayStartTime(2000, 10000)); - Assert.AreEqual(-3000, algorithm.GetDisplayStartTime(2000, 5000)); - Assert.AreEqual(2000, algorithm.GetDisplayStartTime(7000, 5000)); - Assert.AreEqual(7000, algorithm.GetDisplayStartTime(17000, 10000)); + Assert.AreEqual(-8000, algorithm.GetDisplayStartTime(2000, 0, 10000, 1)); + Assert.AreEqual(-3000, algorithm.GetDisplayStartTime(2000, 0, 5000, 1)); + Assert.AreEqual(2000, algorithm.GetDisplayStartTime(7000, 0, 5000, 1)); + Assert.AreEqual(7000, algorithm.GetDisplayStartTime(17000, 0, 10000, 1)); + } + + [Test] + public void TestObjectDisplayStartTime() + { + Assert.AreEqual(900, algorithm.GetDisplayStartTime(2000, 50, 1000, 500)); // 2000 - (1 + 50 / 500) * 1000 + Assert.AreEqual(8900, algorithm.GetDisplayStartTime(10000, 50, 1000, 500)); // 10000 - (1 + 50 / 500) * 1000 + Assert.AreEqual(13500, algorithm.GetDisplayStartTime(15000, 250, 1000, 500)); // 15000 - (1 + 250 / 500) * 1000 + Assert.AreEqual(19000, algorithm.GetDisplayStartTime(25000, 100, 5000, 500)); // 25000 - (1 + 100 / 500) * 5000 } [Test] diff --git a/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs b/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs index 106aa88be3..1429d22c1a 100644 --- a/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs +++ b/osu.Game.Tests/ScrollAlgorithms/OverlappingScrollTest.cs @@ -27,11 +27,22 @@ namespace osu.Game.Tests.ScrollAlgorithms } [Test] - public void TestDisplayStartTime() + public void TestPointDisplayStartTime() { - Assert.AreEqual(1000, algorithm.GetDisplayStartTime(2000, 1000)); // Like constant - Assert.AreEqual(10000, algorithm.GetDisplayStartTime(10500, 1000)); // 10500 - (1000 * 0.5) - Assert.AreEqual(20000, algorithm.GetDisplayStartTime(22000, 1000)); // 23000 - (1000 / 0.5) + Assert.AreEqual(1000, algorithm.GetDisplayStartTime(2000, 0, 1000, 1)); // Like constant + Assert.AreEqual(10000, algorithm.GetDisplayStartTime(10500, 0, 1000, 1)); // 10500 - (1000 * 0.5) + Assert.AreEqual(20000, algorithm.GetDisplayStartTime(22000, 0, 1000, 1)); // 23000 - (1000 / 0.5) + } + + [Test] + public void TestObjectDisplayStartTime() + { + Assert.AreEqual(900, algorithm.GetDisplayStartTime(2000, 50, 1000, 500)); // 2000 - (1 + 50 / 500) * 1000 / 1 + Assert.AreEqual(9450, algorithm.GetDisplayStartTime(10000, 50, 1000, 500)); // 10000 - (1 + 50 / 500) * 1000 / 2 + Assert.AreEqual(14250, algorithm.GetDisplayStartTime(15000, 250, 1000, 500)); // 15000 - (1 + 250 / 500) * 1000 / 2 + Assert.AreEqual(16500, algorithm.GetDisplayStartTime(18000, 250, 2000, 500)); // 18000 - (1 + 250 / 500) * 2000 / 2 + Assert.AreEqual(17800, algorithm.GetDisplayStartTime(20000, 50, 1000, 500)); // 20000 - (1 + 50 / 500) * 1000 / 0.5 + Assert.AreEqual(19800, algorithm.GetDisplayStartTime(22000, 50, 1000, 500)); // 22000 - (1 + 50 / 500) * 1000 / 0.5 } [Test] diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs index 75ea3efdf2..0d4283e319 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/ConstantScrollAlgorithm.cs @@ -5,7 +5,11 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms { public class ConstantScrollAlgorithm : IScrollAlgorithm { - public double GetDisplayStartTime(double time, double timeRange) => time - timeRange; + public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength) + { + var adjustedTime = TimeAt(-offset, originTime, timeRange, scrollLength); + return adjustedTime - timeRange; + } public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) { diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs index 5f053975c7..c394a05bcc 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/IScrollAlgorithm.cs @@ -6,15 +6,33 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms public interface IScrollAlgorithm { /// - /// Given a point in time, computes the time at which it enters the time range. + /// Given a point in time associated with an object's origin + /// and the spatial distance between the edge and the origin of the object along the scrolling axis, + /// computes the time at which the object initially enters the time range. /// - /// - /// E.g. For a constant time range of 5000ms, the time at which t=7000ms enters the time range is 2000ms. - /// - /// The point in time. + /// + /// Let's assume the following parameters: + /// + /// = 7000ms, + /// = 100px, + /// = 5000ms, + /// = 1000px + /// + /// and a constant scrolling rate. + /// To arrive at the end of the scrolling container, the object's origin has to cover + /// 1000 + 100 = 1100px + /// so that the edge starts at the end of the scrolling container. + /// One scroll length of 1000px covers 5000ms of time, so the time required to cover 1100px is equal to + /// 5000 * (1100 / 1000) = 5500ms, + /// and therefore the object should start being visible at + /// 7000 - 5500 = 1500ms. + /// + /// The time point at which the object origin should enter the time range. + /// The spatial distance between the object's edge and its origin along the scrolling axis. /// The amount of visible time. - /// The time at which enters . - double GetDisplayStartTime(double time, double timeRange); + /// The absolute spatial length through . + /// The time at which the object should enter the time range. + double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength); /// /// Computes the spatial length within a start and end time. diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs index fe22a86fad..7b827e0c63 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/OverlappingScrollAlgorithm.cs @@ -20,11 +20,12 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms searchPoint = new MultiplierControlPoint(); } - public double GetDisplayStartTime(double time, double timeRange) + public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength) { + var controlPoint = controlPointAt(originTime); // The total amount of time that the hitobject will remain visible within the timeRange, which decreases as the speed multiplier increases - double visibleDuration = timeRange / controlPointAt(time).Multiplier; - return time - visibleDuration; + double visibleDuration = (scrollLength + offset) * timeRange / controlPoint.Multiplier / scrollLength; + return originTime - visibleDuration; } public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) diff --git a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs index 3c9a205412..41f9ebdb82 100644 --- a/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs +++ b/osu.Game/Rulesets/UI/Scrolling/Algorithms/SequentialScrollAlgorithm.cs @@ -20,7 +20,11 @@ namespace osu.Game.Rulesets.UI.Scrolling.Algorithms positionCache = new Dictionary(); } - public double GetDisplayStartTime(double time, double timeRange) => time - timeRange - 1000; + public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength) + { + double adjustedTime = TimeAt(-offset, originTime, timeRange, scrollLength); + return adjustedTime - timeRange - 1000; + } public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) { diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index 04b4374fc4..83a7f7289f 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -133,8 +133,7 @@ namespace osu.Game.Rulesets.UI.Scrolling break; } - var adjustedStartTime = scrollingInfo.Algorithm.TimeAt(-originAdjustment, hitObject.HitObject.StartTime, timeRange.Value, scrollLength); - return scrollingInfo.Algorithm.GetDisplayStartTime(adjustedStartTime, timeRange.Value); + return scrollingInfo.Algorithm.GetDisplayStartTime(hitObject.HitObject.StartTime, originAdjustment, timeRange.Value, scrollLength); } // Cant use AddOnce() since the delegate is re-constructed every invocation diff --git a/osu.Game/Tests/Visual/ScrollingTestContainer.cs b/osu.Game/Tests/Visual/ScrollingTestContainer.cs index 161ebe7030..18326a78ad 100644 --- a/osu.Game/Tests/Visual/ScrollingTestContainer.cs +++ b/osu.Game/Tests/Visual/ScrollingTestContainer.cs @@ -86,8 +86,8 @@ namespace osu.Game.Tests.Visual } } - public double GetDisplayStartTime(double time, double timeRange) - => implementation.GetDisplayStartTime(time, timeRange); + public double GetDisplayStartTime(double originTime, float offset, double timeRange, float scrollLength) + => implementation.GetDisplayStartTime(originTime, offset, timeRange, scrollLength); public float GetLength(double startTime, double endTime, double timeRange, float scrollLength) => implementation.GetLength(startTime, endTime, timeRange, scrollLength); From c392ba6a7e35c7a6546cb21168ea9241264158e6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Feb 2020 15:46:10 +0900 Subject: [PATCH 45/47] 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 702591231b..25bde037db 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -54,6 +54,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 81f59a4993..21c9eab4c6 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -23,7 +23,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 8e359970a1..3ed25360c5 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -74,7 +74,7 @@ - + @@ -82,7 +82,7 @@ - + From f63bf0637305ecdd866dfa9667a8dfa6442c80c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Feb 2020 16:09:54 +0900 Subject: [PATCH 46/47] Fix incorrect distance snap grid being displayed when in selection mode --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 0014360d24..afe5d2b93b 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -174,7 +174,7 @@ namespace osu.Game.Rulesets.Edit { base.Update(); - if (EditorClock.CurrentTime != lastGridUpdateTime && blueprintContainer.CurrentTool != null) + if (EditorClock.CurrentTime != lastGridUpdateTime && !(blueprintContainer.CurrentTool is SelectTool)) showGridFor(Enumerable.Empty()); } @@ -328,7 +328,7 @@ namespace osu.Game.Rulesets.Edit /// Creates the applicable for a selection. /// /// The selection. - /// The for . + /// The for . If empty, a grid is returned for the current poitn in time. [CanBeNull] protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null; From 3aa18abd99b492cd6b01aa1d777ed5559134dbe9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Feb 2020 16:22:59 +0900 Subject: [PATCH 47/47] Fix typo in xmldoc Co-Authored-By: Tree --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index afe5d2b93b..6c81d70190 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -328,7 +328,7 @@ namespace osu.Game.Rulesets.Edit /// Creates the applicable for a selection. ///
/// The selection. - /// The for . If empty, a grid is returned for the current poitn in time. + /// The for . If empty, a grid is returned for the current point in time. [CanBeNull] protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null;