mirror of
https://github.com/osukey/osukey.git
synced 2025-07-01 00:09:55 +09:00
Merge pull request #7680 from smoogipoo/rearrangeable-playlist
Reimplement music playlist using framework's RearrangeableListContainer
This commit is contained in:
@ -54,6 +54,6 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1230.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1230.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.131.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2020.206.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -0,0 +1,66 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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.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
|
||||||
|
{
|
||||||
|
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||||
|
{
|
||||||
|
typeof(PlaylistOverlay),
|
||||||
|
typeof(Playlist)
|
||||||
|
};
|
||||||
|
|
||||||
|
private readonly BindableList<BeatmapSetInfo> beatmapSets = new BindableList<BeatmapSetInfo>();
|
||||||
|
|
||||||
|
[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);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
52
osu.Game/Overlays/Music/Playlist.cs
Normal file
52
osu.Game/Overlays/Music/Playlist.cs
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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<BeatmapSetInfo>
|
||||||
|
{
|
||||||
|
public Action<BeatmapSetInfo> RequestSelection;
|
||||||
|
|
||||||
|
public readonly Bindable<BeatmapSetInfo> SelectedSet = new Bindable<BeatmapSetInfo>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether any item is currently being dragged. Used to hide other items' drag handles.
|
||||||
|
/// </summary>
|
||||||
|
private readonly BindableBool playlistDragActive = new BindableBool();
|
||||||
|
|
||||||
|
public new MarginPadding Padding
|
||||||
|
{
|
||||||
|
get => base.Padding;
|
||||||
|
set => base.Padding = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Filter(string searchTerm) => ((SearchContainer<RearrangeableListItem<BeatmapSetInfo>>)ListContainer).SearchTerm = searchTerm;
|
||||||
|
|
||||||
|
public BeatmapSetInfo FirstVisibleSet => Items.FirstOrDefault(i => ((PlaylistItem)ItemMap[i]).MatchingFilter);
|
||||||
|
|
||||||
|
protected override RearrangeableListItem<BeatmapSetInfo> CreateDrawable(BeatmapSetInfo item) => new PlaylistItem(item)
|
||||||
|
{
|
||||||
|
SelectedSet = { BindTarget = SelectedSet },
|
||||||
|
PlaylistDragActive = { BindTarget = playlistDragActive },
|
||||||
|
RequestSelection = set => RequestSelection?.Invoke(set)
|
||||||
|
};
|
||||||
|
|
||||||
|
protected override ScrollContainer<Drawable> CreateScrollContainer() => new OsuScrollContainer();
|
||||||
|
|
||||||
|
protected override FillFlowContainer<RearrangeableListItem<BeatmapSetInfo>> CreateListFillFlowContainer() => new SearchContainer<RearrangeableListItem<BeatmapSetInfo>>
|
||||||
|
{
|
||||||
|
Spacing = new Vector2(0, 3),
|
||||||
|
LayoutDuration = 200,
|
||||||
|
LayoutEasing = Easing.OutQuint,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -4,8 +4,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osuTK.Graphics;
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
@ -15,64 +15,38 @@ using osu.Game.Beatmaps;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Music
|
namespace osu.Game.Overlays.Music
|
||||||
{
|
{
|
||||||
public class PlaylistItem : Container, IFilterable, IDraggable
|
public class PlaylistItem : RearrangeableListItem<BeatmapSetInfo>, IFilterable
|
||||||
{
|
{
|
||||||
private const float fade_duration = 100;
|
private const float fade_duration = 100;
|
||||||
|
|
||||||
private Color4 hoverColour;
|
public BindableBool PlaylistDragActive = new BindableBool();
|
||||||
private Color4 artistColour;
|
|
||||||
|
|
||||||
private SpriteIcon handle;
|
public readonly Bindable<BeatmapSetInfo> SelectedSet = new Bindable<BeatmapSetInfo>();
|
||||||
|
|
||||||
|
public Action<BeatmapSetInfo> RequestSelection;
|
||||||
|
|
||||||
|
private PlaylistItemHandle handle;
|
||||||
private TextFlowContainer text;
|
private TextFlowContainer text;
|
||||||
private IEnumerable<Drawable> titleSprites;
|
private IEnumerable<Drawable> titleSprites;
|
||||||
private ILocalisedBindableString titleBind;
|
private ILocalisedBindableString titleBind;
|
||||||
private ILocalisedBindableString artistBind;
|
private ILocalisedBindableString artistBind;
|
||||||
|
|
||||||
public readonly BeatmapSetInfo BeatmapSetInfo;
|
private Color4 hoverColour;
|
||||||
|
private Color4 artistColour;
|
||||||
|
|
||||||
public Action<BeatmapSetInfo> OnSelect;
|
public PlaylistItem(BeatmapSetInfo item)
|
||||||
|
: base(item)
|
||||||
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;
|
RelativeSizeAxes = Axes.X;
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
Padding = new MarginPadding { Top = 3, Bottom = 3 };
|
|
||||||
|
Padding = new MarginPadding { Left = 5 };
|
||||||
|
|
||||||
|
FilterTerms = item.Metadata.SearchableTerms;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -81,30 +55,55 @@ namespace osu.Game.Overlays.Music
|
|||||||
hoverColour = colours.Yellow;
|
hoverColour = colours.Yellow;
|
||||||
artistColour = colours.Gray9;
|
artistColour = colours.Gray9;
|
||||||
|
|
||||||
var metadata = BeatmapSetInfo.Metadata;
|
InternalChild = new GridContainer
|
||||||
FilterTerms = metadata.SearchableTerms;
|
|
||||||
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
handle = new PlaylistItemHandle
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Content = new[]
|
||||||
{
|
{
|
||||||
Colour = colours.Gray5
|
new Drawable[]
|
||||||
},
|
{
|
||||||
text = new OsuTextFlowContainer
|
handle = new PlaylistItemHandle
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
Anchor = Anchor.CentreLeft,
|
||||||
AutoSizeAxes = Axes.Y,
|
Origin = Anchor.CentreLeft,
|
||||||
Padding = new MarginPadding { Left = 20 },
|
Size = new Vector2(12),
|
||||||
ContentIndent = 10f,
|
Colour = colours.Gray5,
|
||||||
|
AlwaysPresent = true,
|
||||||
|
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((metadata.TitleUnicode, metadata.Title)));
|
titleBind = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.TitleUnicode, Model.Metadata.Title)));
|
||||||
artistBind = localisation.GetLocalisedString(new LocalisedString((metadata.ArtistUnicode, metadata.Artist)));
|
artistBind = localisation.GetLocalisedString(new LocalisedString((Model.Metadata.ArtistUnicode, Model.Metadata.Artist)));
|
||||||
|
|
||||||
artistBind.BindValueChanged(_ => recreateText(), true);
|
artistBind.BindValueChanged(_ => recreateText(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
SelectedSet.BindValueChanged(set =>
|
||||||
|
{
|
||||||
|
if (set.OldValue != Model && set.NewValue != Model)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (Drawable s in titleSprites)
|
||||||
|
s.FadeColour(set.NewValue == Model ? hoverColour : Color4.White, fade_duration);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
private void recreateText()
|
private void recreateText()
|
||||||
{
|
{
|
||||||
text.Clear();
|
text.Clear();
|
||||||
@ -120,25 +119,38 @@ namespace osu.Game.Overlays.Music
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
OnSelect?.Invoke(BeatmapSetInfo);
|
RequestSelection?.Invoke(Model);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public IEnumerable<string> FilterTerms { get; private set; }
|
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(IsDragged || !PlaylistDragActive.Value);
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e) => handle.UpdateHoverState(false);
|
||||||
|
|
||||||
|
public IEnumerable<string> FilterTerms { get; }
|
||||||
|
|
||||||
private bool matching = true;
|
private bool matching = true;
|
||||||
|
|
||||||
@ -159,25 +171,41 @@ namespace osu.Game.Overlays.Music
|
|||||||
|
|
||||||
private class PlaylistItemHandle : SpriteIcon
|
private class PlaylistItemHandle : SpriteIcon
|
||||||
{
|
{
|
||||||
|
public bool HandlingDrag { get; private set; }
|
||||||
|
private bool isHovering;
|
||||||
|
|
||||||
public PlaylistItemHandle()
|
public PlaylistItemHandle()
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft;
|
|
||||||
Origin = Anchor.CentreLeft;
|
|
||||||
Size = new Vector2(12);
|
|
||||||
Icon = FontAwesome.Solid.Bars;
|
Icon = FontAwesome.Solid.Bars;
|
||||||
Alpha = 0f;
|
|
||||||
Margin = new MarginPadding { Left = 5 };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool HandlePositionalInput => IsPresent;
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public interface IDraggable : IDrawable
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Whether this <see cref="IDraggable"/> can be dragged in its current state.
|
|
||||||
/// </summary>
|
|
||||||
bool IsDraggable { get; }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1,268 +0,0 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.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.Game.Beatmaps;
|
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Music
|
|
||||||
{
|
|
||||||
public class PlaylistList : CompositeDrawable
|
|
||||||
{
|
|
||||||
public Action<BeatmapSetInfo> Selected;
|
|
||||||
|
|
||||||
private readonly ItemsScrollContainer items;
|
|
||||||
|
|
||||||
public PlaylistList()
|
|
||||||
{
|
|
||||||
InternalChild = items = new ItemsScrollContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Selected = set => Selected?.Invoke(set),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public new MarginPadding Padding
|
|
||||||
{
|
|
||||||
get => base.Padding;
|
|
||||||
set => base.Padding = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BeatmapSetInfo FirstVisibleSet => items.FirstVisibleSet;
|
|
||||||
|
|
||||||
public void Filter(string searchTerm) => items.SearchTerm = searchTerm;
|
|
||||||
|
|
||||||
private class ItemsScrollContainer : OsuScrollContainer
|
|
||||||
{
|
|
||||||
public Action<BeatmapSetInfo> Selected;
|
|
||||||
|
|
||||||
private readonly SearchContainer search;
|
|
||||||
private readonly FillFlowContainer<PlaylistItem> items;
|
|
||||||
|
|
||||||
private readonly IBindable<WorkingBeatmap> beatmapBacking = new Bindable<WorkingBeatmap>();
|
|
||||||
|
|
||||||
private IBindableList<BeatmapSetInfo> beatmaps;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private MusicController musicController { get; set; }
|
|
||||||
|
|
||||||
public ItemsScrollContainer()
|
|
||||||
{
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
search = new SearchContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
items = new ItemSearchContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(IBindable<WorkingBeatmap> beatmap)
|
|
||||||
{
|
|
||||||
beatmaps = musicController.BeatmapSets.GetBoundCopy();
|
|
||||||
beatmaps.ItemsAdded += i => i.ForEach(addBeatmapSet);
|
|
||||||
beatmaps.ItemsRemoved += i => i.ForEach(removeBeatmapSet);
|
|
||||||
beatmaps.ForEach(addBeatmapSet);
|
|
||||||
|
|
||||||
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<PlaylistItem>, IHasFilterableChildren
|
|
||||||
{
|
|
||||||
public IEnumerable<string> FilterTerms => Array.Empty<string>();
|
|
||||||
|
|
||||||
public bool MatchingFilter
|
|
||||||
{
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (value)
|
|
||||||
InvalidateLayout();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool FilteringActive
|
|
||||||
{
|
|
||||||
set { }
|
|
||||||
}
|
|
||||||
|
|
||||||
public IEnumerable<IFilterable> FilterableChildren => Children;
|
|
||||||
|
|
||||||
public ItemSearchContainer()
|
|
||||||
{
|
|
||||||
LayoutDuration = 200;
|
|
||||||
LayoutEasing = Easing.OutQuint;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,11 +21,15 @@ namespace osu.Game.Overlays.Music
|
|||||||
private const float transition_duration = 600;
|
private const float transition_duration = 600;
|
||||||
private const float playlist_height = 510;
|
private const float playlist_height = 510;
|
||||||
|
|
||||||
|
public IBindableList<BeatmapSetInfo> BeatmapSets => beatmapSets;
|
||||||
|
|
||||||
|
private readonly BindableList<BeatmapSetInfo> beatmapSets = new BindableList<BeatmapSetInfo>();
|
||||||
|
|
||||||
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
|
||||||
private BeatmapManager beatmaps;
|
private BeatmapManager beatmaps;
|
||||||
|
|
||||||
private FilterControl filter;
|
private FilterControl filter;
|
||||||
private PlaylistList list;
|
private Playlist list;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours, Bindable<WorkingBeatmap> beatmap, BeatmapManager beatmaps)
|
private void load(OsuColour colours, Bindable<WorkingBeatmap> beatmap, BeatmapManager beatmaps)
|
||||||
@ -53,11 +57,11 @@ namespace osu.Game.Overlays.Music
|
|||||||
Colour = colours.Gray3,
|
Colour = colours.Gray3,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
list = new PlaylistList
|
list = new Playlist
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 },
|
Padding = new MarginPadding { Top = 95, Bottom = 10, Right = 10 },
|
||||||
Selected = itemSelected,
|
RequestSelection = itemSelected
|
||||||
},
|
},
|
||||||
filter = new FilterControl
|
filter = new FilterControl
|
||||||
{
|
{
|
||||||
@ -70,6 +74,8 @@ namespace osu.Game.Overlays.Music
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
list.Items.BindTo(beatmapSets);
|
||||||
|
|
||||||
filter.Search.OnCommit = (sender, newText) =>
|
filter.Search.OnCommit = (sender, newText) =>
|
||||||
{
|
{
|
||||||
BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault();
|
BeatmapInfo toSelect = list.FirstVisibleSet?.Beatmaps?.FirstOrDefault();
|
||||||
@ -80,6 +86,8 @@ namespace osu.Game.Overlays.Music
|
|||||||
beatmap.Value.Track.Restart();
|
beatmap.Value.Track.Restart();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
beatmap.BindValueChanged(working => list.SelectedSet.Value = working.NewValue.BeatmapSetInfo, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void PopIn()
|
protected override void PopIn()
|
||||||
|
@ -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);
|
playlist.State.ValueChanged += s => playlistButton.FadeColour(s.NewValue == Visibility.Visible ? colours.Yellow : Color4.White, 200, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1230.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1230.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2020.131.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.206.0" />
|
||||||
<PackageReference Include="Sentry" Version="2.0.1" />
|
<PackageReference Include="Sentry" Version="2.0.1" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.24.0" />
|
<PackageReference Include="SharpCompress" Version="0.24.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
|
@ -74,7 +74,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Package References">
|
<ItemGroup Label="Package References">
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1230.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2019.1230.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.131.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2020.206.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
<!-- Xamarin.iOS does not automatically handle transitive dependencies from NuGet packages. -->
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
@ -82,7 +82,7 @@
|
|||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
|
||||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite.Core" Version="2.2.6" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2020.131.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2020.206.0" />
|
||||||
<PackageReference Include="SharpCompress" Version="0.24.0" />
|
<PackageReference Include="SharpCompress" Version="0.24.0" />
|
||||||
<PackageReference Include="NUnit" Version="3.12.0" />
|
<PackageReference Include="NUnit" Version="3.12.0" />
|
||||||
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
<PackageReference Include="SharpRaven" Version="2.4.0" />
|
||||||
|
Reference in New Issue
Block a user