// 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.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Database; using osu.Game.Graphics.Containers; using osuTK; using Realms; namespace osu.Game.Collections { /// /// Visualises a list of s. /// public class DrawableCollectionList : OsuRearrangeableListContainer> { protected override ScrollContainer CreateScrollContainer() => scroll = new Scroll(); [Resolved] private RealmAccess realm { get; set; } = null!; private Scroll scroll = null!; private IDisposable? realmSubscription; protected override FillFlowContainer>> CreateListFillFlowContainer() => new Flow { DragActive = { BindTarget = DragActive } }; protected override void LoadComplete() { base.LoadComplete(); realmSubscription = realm.RegisterForNotifications(r => r.All().OrderBy(c => c.Name), collectionsChanged); } private void collectionsChanged(IRealmCollection collections, ChangeSet? changes, Exception error) { Items.Clear(); Items.AddRange(collections.AsEnumerable().Select(c => c.ToLive(realm))); } protected override OsuRearrangeableListItem> CreateOsuDrawable(Live item) { if (item.ID == scroll.PlaceholderItem.Model.ID) return scroll.ReplacePlaceholder(); return new DrawableCollectionListItem(item, true); } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); realmSubscription?.Dispose(); } /// /// The scroll container for this . /// Contains the main flow of and attaches a placeholder item to the end of the list. /// /// /// Use to transfer the placeholder into the main list. /// private class Scroll : OsuScrollContainer { /// /// The currently-displayed placeholder item. /// public DrawableCollectionListItem PlaceholderItem { get; private set; } = null!; protected override Container Content => content; private readonly Container content; private readonly Container placeholderContainer; public Scroll() { ScrollbarVisible = false; Padding = new MarginPadding(10); base.Content.Add(new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, LayoutDuration = 200, LayoutEasing = Easing.OutQuint, Children = new Drawable[] { content = new Container { RelativeSizeAxes = Axes.X }, placeholderContainer = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y } } }); ReplacePlaceholder(); Debug.Assert(PlaceholderItem != null); } protected override void Update() { base.Update(); // AutoSizeAxes cannot be used as the height should represent the post-layout-transform height at all times, so that the placeholder doesn't bounce around. content.Height = ((Flow)Child).Children.Sum(c => c.DrawHeight + 5); } /// /// Replaces the current with a new one, and returns the previous. /// /// The current . public DrawableCollectionListItem ReplacePlaceholder() { var previous = PlaceholderItem; placeholderContainer.Clear(false); placeholderContainer.Add(PlaceholderItem = new DrawableCollectionListItem(new BeatmapCollection().ToLiveUnmanaged(), false)); return previous; } } /// /// The flow of . Disables layout easing unless a drag is in progress. /// private class Flow : FillFlowContainer>> { public readonly IBindable DragActive = new Bindable(); public Flow() { Spacing = new Vector2(0, 5); LayoutEasing = Easing.OutQuint; } protected override void LoadComplete() { base.LoadComplete(); DragActive.BindValueChanged(active => LayoutDuration = active.NewValue ? 200 : 0); } } } }