// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Graphics; using osuTK; using osuTK.Graphics; using Realms; namespace osu.Game.Overlays.Music { public class PlaylistOverlay : VisibilityContainer { private const float transition_duration = 600; private const float playlist_height = 510; public IBindableList> BeatmapSets => beatmapSets; private readonly BindableList> beatmapSets = new BindableList>(); private readonly Bindable beatmap = new Bindable(); [Resolved] private BeatmapManager beatmaps { get; set; } [Resolved] private RealmAccess realm { get; set; } private IDisposable beatmapSubscription; private FilterControl filter; private Playlist list; [BackgroundDependencyLoader] private void load(OsuColour colours, Bindable beatmap) { this.beatmap.BindTo(beatmap); Children = new Drawable[] { new Container { RelativeSizeAxes = Axes.Both, CornerRadius = 5, Masking = true, EdgeEffect = new EdgeEffectParameters { Type = EdgeEffectType.Shadow, Colour = Color4.Black.Opacity(40), Radius = 5, }, Children = new Drawable[] { new Box { Colour = colours.Gray3, RelativeSizeAxes = Axes.Both, }, list = new Playlist { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 }, RequestSelection = itemSelected }, filter = new FilterControl { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, FilterChanged = criteria => list.Filter(criteria), Padding = new MarginPadding(10), }, }, }, }; filter.Search.OnCommit += (sender, newText) => { list.FirstVisibleSet?.PerformRead(set => { BeatmapInfo toSelect = set.Beatmaps.FirstOrDefault(); if (toSelect != null) { beatmap.Value = beatmaps.GetWorkingBeatmap(toSelect); beatmap.Value.Track.Restart(); } }); }; } protected override void LoadComplete() { base.LoadComplete(); // tests might bind externally, in which case we don't want to involve realm. if (beatmapSets.Count == 0) beatmapSubscription = realm.RegisterForNotifications(r => r.All().Where(s => !s.DeletePending), beatmapsChanged); list.Items.BindTo(beatmapSets); beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo.ToLive(realm), true); } private void beatmapsChanged(IRealmCollection sender, ChangeSet changes, Exception error) { if (changes == null) { beatmapSets.Clear(); // must use AddRange to avoid RearrangeableList sort overhead per add op. beatmapSets.AddRange(sender.Select(b => b.ToLive(realm))); return; } foreach (int i in changes.InsertedIndices) beatmapSets.Insert(i, sender[i].ToLive(realm)); foreach (int i in changes.DeletedIndices.OrderByDescending(i => i)) beatmapSets.RemoveAt(i); } protected override void PopIn() { filter.Search.HoldFocus = true; Schedule(() => filter.Search.TakeFocus()); this.ResizeTo(new Vector2(1, playlist_height), transition_duration, Easing.OutQuint); this.FadeIn(transition_duration, Easing.OutQuint); } protected override void PopOut() { filter.Search.HoldFocus = false; this.ResizeTo(new Vector2(1, 0), transition_duration, Easing.OutQuint); this.FadeOut(transition_duration); } private void itemSelected(Live beatmapSet) { beatmapSet.PerformRead(set => { if (set.Equals((beatmap.Value?.BeatmapSetInfo))) { beatmap.Value?.Track.Seek(0); return; } beatmap.Value = beatmaps.GetWorkingBeatmap(set.Beatmaps.First()); beatmap.Value.Track.Restart(); }); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); beatmapSubscription?.Dispose(); } } }