mirror of
https://github.com/SK-la/Ez2Lazer.git
synced 2026-03-13 11:20:28 +00:00
更换为最新版批量收藏功能
This commit is contained in:
138
osu.Game/Collections/BatchAddToCollectionHandler.cs
Normal file
138
osu.Game/Collections/BatchAddToCollectionHandler.cs
Normal file
@@ -0,0 +1,138 @@
|
||||
// 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 System.Threading.Tasks;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.LAsEzExtensions.Localization;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
|
||||
namespace osu.Game.Collections
|
||||
{
|
||||
internal static partial class BatchAddToCollectionHandler
|
||||
{
|
||||
public static void RequestSaveToCollection(
|
||||
Live<BeatmapCollection> collection,
|
||||
Func<IEnumerable<BeatmapInfo>>? filteredBeatmapsProvider,
|
||||
Action<PopupDialog> showDialog)
|
||||
{
|
||||
if (filteredBeatmapsProvider == null)
|
||||
return;
|
||||
|
||||
var hashes = filteredBeatmapsProvider().Select(b => b.MD5Hash)
|
||||
.Where(h => !string.IsNullOrEmpty(h))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (hashes.Count == 0)
|
||||
return;
|
||||
|
||||
var existing = collection.PerformRead(c => c.BeatmapMD5Hashes.ToList());
|
||||
var intersection = existing.Intersect(hashes).ToList();
|
||||
int overlapCount = intersection.Count;
|
||||
|
||||
if (overlapCount == hashes.Count)
|
||||
{
|
||||
showDialog(new RemoveFilteredResultsDialog(
|
||||
onRemove: () => runHashRemoval(collection, intersection)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (overlapCount > 0)
|
||||
{
|
||||
var toAdd = hashes.Except(existing).ToList();
|
||||
var toRemove = intersection;
|
||||
|
||||
showDialog(new PartialOverlapFilteredResultsDialog(
|
||||
overlapCount,
|
||||
onAddDifference: () => runHashAddition(collection, toAdd),
|
||||
onRemoveIntersection: () => runHashRemoval(collection, toRemove)));
|
||||
return;
|
||||
}
|
||||
|
||||
string collectionName = collection.PerformRead(c => c.Name);
|
||||
|
||||
showDialog(new AddFilteredResultsDialog(
|
||||
collectionName,
|
||||
hashes.Count,
|
||||
onAddAll: () => runHashAddition(collection, hashes)));
|
||||
}
|
||||
|
||||
private static void runHashAddition(Live<BeatmapCollection> collection, IReadOnlyList<string> hashes)
|
||||
{
|
||||
if (hashes.Count == 0)
|
||||
return;
|
||||
|
||||
Task.Run(() => collection.PerformWrite(c =>
|
||||
{
|
||||
foreach (string hash in hashes)
|
||||
{
|
||||
if (!c.BeatmapMD5Hashes.Contains(hash))
|
||||
c.BeatmapMD5Hashes.Add(hash);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private static void runHashRemoval(Live<BeatmapCollection> collection, IReadOnlyList<string> hashes)
|
||||
{
|
||||
Task.Run(() => collection.PerformWrite(c =>
|
||||
{
|
||||
foreach (string hash in hashes)
|
||||
c.BeatmapMD5Hashes.Remove(hash);
|
||||
}));
|
||||
}
|
||||
|
||||
private partial class AddFilteredResultsDialog : DangerousActionDialog
|
||||
{
|
||||
public AddFilteredResultsDialog(string collectionName, int beatmapCount, Action onAddAll)
|
||||
{
|
||||
Icon = FontAwesome.Solid.Check;
|
||||
HeaderText = EzSongSelectStrings.SAVE_TO_COLLECTION;
|
||||
BodyText = $"Add {beatmapCount:#,0} beatmaps to \"{collectionName}\"?";
|
||||
DangerousAction = onAddAll;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class RemoveFilteredResultsDialog : DangerousActionDialog
|
||||
{
|
||||
public RemoveFilteredResultsDialog(Action onRemove)
|
||||
{
|
||||
Icon = FontAwesome.Solid.Trash;
|
||||
HeaderText = EzSongSelectStrings.REMOVE_FROM_COLLECTION;
|
||||
BodyText = EzSongSelectStrings.REMOVE_FROM_COLLECTION_TOOLTIP;
|
||||
DangerousAction = onRemove;
|
||||
}
|
||||
}
|
||||
|
||||
private partial class PartialOverlapFilteredResultsDialog : DangerousActionDialog
|
||||
{
|
||||
public PartialOverlapFilteredResultsDialog(int overlapCount, Action onAddDifference, Action onRemoveIntersection)
|
||||
{
|
||||
Icon = FontAwesome.Solid.Question;
|
||||
HeaderText = EzSongSelectStrings.PARTIALLY_OVERLAPPED;
|
||||
BodyText = $"{overlapCount} {EzSongSelectStrings.SELECT_ACTION_FOR_OVERLAP}";
|
||||
Buttons = new PopupDialogButton[]
|
||||
{
|
||||
new PopupDialogDangerousButton
|
||||
{
|
||||
Text = EzSongSelectStrings.ADD_DIFFERENCE,
|
||||
Action = onAddDifference,
|
||||
},
|
||||
new PopupDialogDangerousButton
|
||||
{
|
||||
Text = EzSongSelectStrings.REMOVE_FROM_COLLECTION,
|
||||
Action = onRemoveIntersection,
|
||||
},
|
||||
new PopupDialogCancelButton
|
||||
{
|
||||
Text = "Cancel"
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,10 +92,19 @@ namespace osu.Game.Collections
|
||||
IsTextBoxHovered = v => TextBox.ReceivePositionalInputAt(v)
|
||||
}
|
||||
: Empty(),
|
||||
collection.IsManaged
|
||||
? new BatchAddToCollectionButton(collection)
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
X = -button_width,
|
||||
IsTextBoxHovered = v => TextBox.ReceivePositionalInputAt(v),
|
||||
}
|
||||
: Empty(),
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Right = collection.IsManaged ? button_width : 0 },
|
||||
Padding = new MarginPadding { Right = collection.IsManaged ? button_width * 2 : 0 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
TextBox = new ItemTextBox(collection)
|
||||
@@ -201,7 +210,7 @@ namespace osu.Game.Collections
|
||||
this.collection = collection;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
||||
Width = button_width + item_height / 2; // add corner radius to cover with fill
|
||||
Width = button_width + item_height / 4; // add corner radius to cover with fill
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@@ -238,7 +247,14 @@ namespace osu.Game.Collections
|
||||
};
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos);
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos)
|
||||
{
|
||||
if (!base.ReceivePositionalInputAt(screenSpacePos) || IsTextBoxHovered(screenSpacePos))
|
||||
return false;
|
||||
|
||||
float localX = ToLocalSpace(screenSpacePos).X;
|
||||
return localX > item_height / 4;
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
@@ -261,6 +277,77 @@ namespace osu.Game.Collections
|
||||
private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c));
|
||||
}
|
||||
|
||||
public partial class BatchAddToCollectionButton : OsuClickableContainer
|
||||
{
|
||||
public Func<Vector2, bool> IsTextBoxHovered = null!;
|
||||
|
||||
private readonly Live<BeatmapCollection> collection;
|
||||
private Color4 darkenedColour;
|
||||
private Color4 normalColour;
|
||||
|
||||
private Drawable background = null!;
|
||||
|
||||
public BatchAddToCollectionButton(Live<BeatmapCollection> collection)
|
||||
{
|
||||
this.collection = collection;
|
||||
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Width = button_width + item_height / 4;
|
||||
TooltipText = "Add all visible beatmaps to collection";
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
normalColour = colours.Yellow;
|
||||
darkenedColour = normalColour.Darken(0.9f);
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 10,
|
||||
Children = new[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = darkenedColour,
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.Centre,
|
||||
X = -button_width * 0.6f,
|
||||
Size = new Vector2(10),
|
||||
Icon = FontAwesome.Solid.Plus,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Action = () => this.FindClosestParent<ManageCollectionsDialog>()?.RequestBatchAddToCollection(collection);
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos);
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
background.FadeColour(normalColour, 100, Easing.Out);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
background.FadeColour(darkenedColour, 100, Easing.Out);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
background.FlashColour(Color4.White, 150);
|
||||
return base.OnClick(e);
|
||||
}
|
||||
}
|
||||
|
||||
public IEnumerable<LocalisableString> FilterTerms => Model.PerformRead(m => m.IsValid ? new[] { (LocalisableString)m.Name } : []);
|
||||
|
||||
private bool matchingFilter = true;
|
||||
|
||||
@@ -3,30 +3,21 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.LAsEzExtensions.Localization;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osuTK;
|
||||
using Realms;
|
||||
using CommonStrings = osu.Game.Resources.Localisation.Web.CommonStrings;
|
||||
|
||||
namespace osu.Game.Collections
|
||||
{
|
||||
@@ -42,27 +33,8 @@ namespace osu.Game.Collections
|
||||
|
||||
private BasicSearchTextBox searchTextBox = null!;
|
||||
private DrawableCollectionList list = null!;
|
||||
private SaveToCollectionDropdown saveToCollectionDropdown = null!;
|
||||
private RoundedButton saveFilteredResultsButton = null!;
|
||||
|
||||
private readonly BindableList<CollectionFilterMenuItem> saveToCollectionItems = new BindableList<CollectionFilterMenuItem>();
|
||||
|
||||
private IDisposable? saveCollectionsSubscription;
|
||||
|
||||
private Func<IEnumerable<BeatmapInfo>>? filteredBeatmapsProvider;
|
||||
|
||||
public Func<IEnumerable<BeatmapInfo>>? FilteredBeatmapsProvider
|
||||
{
|
||||
get => filteredBeatmapsProvider;
|
||||
set
|
||||
{
|
||||
filteredBeatmapsProvider = value;
|
||||
updateSaveButtonState();
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
public Func<IEnumerable<BeatmapInfo>>? FilteredBeatmapsProvider { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private MusicController? musicController { get; set; }
|
||||
@@ -154,13 +126,12 @@ namespace osu.Game.Collections
|
||||
{
|
||||
list = new DrawableCollectionList
|
||||
{
|
||||
Padding = new MarginPadding { Top = 90, Bottom = 50 },
|
||||
Padding = new MarginPadding { Vertical = 50 },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
searchTextBox = new BasicSearchTextBox
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Y = 45,
|
||||
Height = 40,
|
||||
ReleaseFocusOnCommit = false,
|
||||
HoldFocus = true,
|
||||
@@ -177,45 +148,6 @@ namespace osu.Game.Collections
|
||||
new NewCollectionEntryItem()
|
||||
}
|
||||
},
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = EzSongSelectStrings.SAVE_TO_COLLECTION,
|
||||
Font = OsuFont.GetFont(size: 16),
|
||||
},
|
||||
Empty(),
|
||||
saveToCollectionDropdown = new SaveToCollectionDropdown
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
Empty(),
|
||||
saveFilteredResultsButton = new RoundedButton
|
||||
{
|
||||
Width = 90,
|
||||
Height = 40,
|
||||
Text = "Save",
|
||||
Action = saveFilteredResults,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
@@ -235,18 +167,12 @@ namespace osu.Game.Collections
|
||||
{
|
||||
list.SearchTerm = searchTextBox.Current.Value;
|
||||
});
|
||||
|
||||
saveToCollectionDropdown.ItemSource = saveToCollectionItems;
|
||||
saveToCollectionDropdown.Current.BindValueChanged(_ => updateSaveButtonState(), true);
|
||||
|
||||
saveCollectionsSubscription = realm.RegisterForNotifications(r => r.All<BeatmapCollection>().OrderBy(c => c.Name), saveCollectionsChanged);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
duckOperation?.Dispose();
|
||||
saveCollectionsSubscription?.Dispose();
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
@@ -300,145 +226,12 @@ namespace osu.Game.Collections
|
||||
}
|
||||
}
|
||||
|
||||
private void saveCollectionsChanged(IRealmCollection<BeatmapCollection> collections, ChangeSet? changes)
|
||||
internal void RequestBatchAddToCollection(Live<BeatmapCollection> collection)
|
||||
{
|
||||
var selectedId = saveToCollectionDropdown.Current.Value?.Collection?.ID;
|
||||
|
||||
saveToCollectionItems.Clear();
|
||||
saveToCollectionItems.AddRange(collections.Select(c => new CollectionFilterMenuItem(c.ToLive(realm))));
|
||||
|
||||
if (saveToCollectionItems.Count > 0)
|
||||
{
|
||||
var selectedItem = saveToCollectionItems.FirstOrDefault(i => i.Collection?.ID == selectedId) ?? saveToCollectionItems[0];
|
||||
saveToCollectionDropdown.Current.Value = selectedItem;
|
||||
}
|
||||
|
||||
updateSaveButtonState();
|
||||
}
|
||||
|
||||
private void updateSaveButtonState()
|
||||
{
|
||||
var currentId = saveToCollectionDropdown.Current.Value?.Collection?.ID;
|
||||
bool hasTarget = currentId != null && saveToCollectionItems.Any(item => item.Collection?.ID == currentId);
|
||||
bool hasProvider = FilteredBeatmapsProvider != null;
|
||||
saveFilteredResultsButton.Enabled.Value = hasTarget && hasProvider;
|
||||
}
|
||||
|
||||
private void saveFilteredResults()
|
||||
{
|
||||
var provider = FilteredBeatmapsProvider;
|
||||
var collection = saveToCollectionDropdown.Current.Value?.Collection;
|
||||
|
||||
if (provider == null || collection == null)
|
||||
return;
|
||||
|
||||
var hashes = provider().Select(b => b.MD5Hash)
|
||||
.Where(h => !string.IsNullOrEmpty(h))
|
||||
.Distinct()
|
||||
.ToList();
|
||||
|
||||
if (hashes.Count == 0)
|
||||
return;
|
||||
|
||||
// Read existing entries from the collection to determine overlap.
|
||||
var existing = collection.PerformRead(c => c.BeatmapMD5Hashes.ToList());
|
||||
var intersection = existing.Intersect(hashes).ToList();
|
||||
|
||||
// Case 1: collection already contains all filtered results -> offer bulk remove.
|
||||
if (intersection.Count == hashes.Count)
|
||||
{
|
||||
Schedule(() => dialogOverlay?.Push(new SimplePopupDialog(
|
||||
FontAwesome.Solid.Trash,
|
||||
EzSongSelectStrings.REMOVE_FROM_COLLECTION,
|
||||
EzSongSelectStrings.REMOVE_FROM_COLLECTION_TOOLTIP,
|
||||
new PopupDialogButton[]
|
||||
{
|
||||
new PopupDialogCancelButton { Text = CommonStrings.ButtonsCancel },
|
||||
new PopupDialogDangerousButton
|
||||
{
|
||||
Text = AccountsStrings.UserTotpButtonRemove,
|
||||
Action = () => Task.Run(() => collection.PerformWrite(c =>
|
||||
{
|
||||
foreach (string h in intersection)
|
||||
c.BeatmapMD5Hashes.Remove(h);
|
||||
}))
|
||||
}
|
||||
})));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Case 2: partial overlap -> let user choose to add remaining or remove overlapping parts.
|
||||
if (intersection.Count > 0)
|
||||
{
|
||||
var toAdd = hashes.Except(existing).ToList();
|
||||
var toRemove = intersection;
|
||||
|
||||
Schedule(() => dialogOverlay?.Push(new SimplePopupDialog(
|
||||
FontAwesome.Solid.Question,
|
||||
EzSongSelectStrings.PARTIALLY_OVERLAPPED,
|
||||
$"{intersection.Count}{EzSongSelectStrings.SELECT_ACTION_FOR_OVERLAP}",
|
||||
new PopupDialogButton[]
|
||||
{
|
||||
new PopupDialogCancelButton { Text = CommonStrings.ButtonsCancel },
|
||||
new PopupDialogOkButton
|
||||
{
|
||||
Text = EzSongSelectStrings.ADD_DIFFERENCE,
|
||||
Action = () =>
|
||||
{
|
||||
if (toAdd.Count == 0) return;
|
||||
|
||||
Task.Run(() => collection.PerformWrite(c =>
|
||||
{
|
||||
foreach (string h in toAdd)
|
||||
{
|
||||
if (!c.BeatmapMD5Hashes.Contains(h))
|
||||
c.BeatmapMD5Hashes.Add(h);
|
||||
}
|
||||
}));
|
||||
}
|
||||
},
|
||||
new PopupDialogDangerousButton
|
||||
{
|
||||
Text = EzSongSelectStrings.REMOVE_INTERSECTION,
|
||||
Action = () => Task.Run(() => collection.PerformWrite(c =>
|
||||
{
|
||||
foreach (string h in toRemove)
|
||||
c.BeatmapMD5Hashes.Remove(h);
|
||||
}))
|
||||
}
|
||||
})));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
Task.Run(() => collection.PerformWrite(c =>
|
||||
{
|
||||
foreach (string hash in hashes)
|
||||
{
|
||||
if (!c.BeatmapMD5Hashes.Contains(hash))
|
||||
c.BeatmapMD5Hashes.Add(hash);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private partial class SaveToCollectionDropdown : OsuDropdown<CollectionFilterMenuItem>
|
||||
{
|
||||
protected override LocalisableString GenerateItemText(CollectionFilterMenuItem item) => item.CollectionName;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A minimal concrete <see cref="PopupDialog"/> used for inline confirmation prompts in this dialog.
|
||||
/// </summary>
|
||||
private partial class SimplePopupDialog : PopupDialog
|
||||
{
|
||||
public SimplePopupDialog(IconUsage icon, LocalisableString header, LocalisableString body, PopupDialogButton[] buttons)
|
||||
{
|
||||
Icon = icon;
|
||||
HeaderText = header;
|
||||
BodyText = body;
|
||||
Buttons = buttons;
|
||||
}
|
||||
BatchAddToCollectionHandler.RequestSaveToCollection(
|
||||
collection,
|
||||
FilteredBeatmapsProvider,
|
||||
dialog => Schedule(() => dialogOverlay?.Push(dialog)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,8 @@ namespace osu.Game.LAsEzExtensions.Localization
|
||||
public static class EzSongSelectStrings
|
||||
{
|
||||
public static readonly EzLocalizationManager.EzLocalisableString SAVE_TO_COLLECTION = new EzLocalizationManager.EzLocalisableString(
|
||||
"将当前过滤结果保存到",
|
||||
"Save current filter result to");
|
||||
"将当前过滤结果保存到收藏夹",
|
||||
"Add all visible beatmaps to collection");
|
||||
|
||||
public static readonly EzLocalizationManager.EzLocalisableString REMOVE_FROM_COLLECTION = new EzLocalizationManager.EzLocalisableString(
|
||||
"从收藏夹移除当前过滤结果",
|
||||
@@ -21,7 +21,7 @@ namespace osu.Game.LAsEzExtensions.Localization
|
||||
|
||||
public static readonly EzLocalizationManager.EzLocalisableString PARTIALLY_OVERLAPPED = new EzLocalizationManager.EzLocalisableString(
|
||||
"筛选结果与收藏夹部分重",
|
||||
"The filter result is partially overlapped with the collection");
|
||||
"The visible beatmaps is partially overlapped with the collection");
|
||||
|
||||
public static readonly EzLocalizationManager.EzLocalisableString SELECT_ACTION_FOR_OVERLAP = new EzLocalizationManager.EzLocalisableString(
|
||||
" 个谱面已存在。请选择要执行的操作:",
|
||||
|
||||
@@ -7,8 +7,11 @@ using System.Collections.Immutable;
|
||||
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.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
@@ -16,6 +19,7 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
@@ -31,6 +35,7 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.Select;
|
||||
using osu.Game.Screens.Select.Filter;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Screens.SelectV2
|
||||
@@ -66,6 +71,9 @@ namespace osu.Game.Screens.SelectV2
|
||||
[Resolved]
|
||||
private ISongSelect? songSelect { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private ManageCollectionsDialog? manageCollectionsDialog { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; } = null!;
|
||||
|
||||
@@ -316,7 +324,19 @@ namespace osu.Game.Screens.SelectV2
|
||||
updateCriteria();
|
||||
});
|
||||
|
||||
searchTextBox.Current.BindValueChanged(_ => updateCriteria());
|
||||
searchTextBox.Current.BindValueChanged(_ =>
|
||||
{
|
||||
updateCriteria();
|
||||
updateVisibleResultsActionAvailability();
|
||||
});
|
||||
|
||||
searchTextBox.VisibleResultsAction = () =>
|
||||
{
|
||||
if (manageCollectionsDialog == null)
|
||||
return;
|
||||
|
||||
manageCollectionsDialog.Show();
|
||||
};
|
||||
difficultyRangeSlider.LowerBound.BindValueChanged(_ => updateCriteria());
|
||||
difficultyRangeSlider.UpperBound.BindValueChanged(_ => updateCriteria());
|
||||
showConvertedBeatmapsButton.Active.BindValueChanged(_ => updateCriteria());
|
||||
@@ -344,9 +364,16 @@ namespace osu.Game.Screens.SelectV2
|
||||
csSelector.Current.BindValueChanged(_ => updateCriteria());
|
||||
xxySrFilterButton.Active.BindValueChanged(_ => updateCriteria());
|
||||
|
||||
updateVisibleResultsActionAvailability();
|
||||
updateCriteria();
|
||||
}
|
||||
|
||||
private void updateVisibleResultsActionAvailability()
|
||||
{
|
||||
bool available = !string.IsNullOrWhiteSpace(searchTextBox.Current.Value);
|
||||
searchTextBox.UpdateVisibleResultsActionAvailability(available);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
@@ -460,6 +487,29 @@ namespace osu.Game.Screens.SelectV2
|
||||
{
|
||||
public IBindable<BeatmapSetInfo?> ScopedBeatmapSet { get; } = new Bindable<BeatmapSetInfo?>();
|
||||
|
||||
public Action? VisibleResultsAction { get; set; }
|
||||
|
||||
private readonly BatchAddToCollectionButton batchAddToCollectionButton;
|
||||
|
||||
public SongSelectSearchTextBox()
|
||||
{
|
||||
AddInternal(batchAddToCollectionButton = new BatchAddToCollectionButton
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
X = -50,
|
||||
Alpha = 0,
|
||||
TooltipText = "Add all visible beatmaps to collection",
|
||||
Action = () => VisibleResultsAction?.Invoke(),
|
||||
});
|
||||
}
|
||||
|
||||
public void UpdateVisibleResultsActionAvailability(bool available)
|
||||
{
|
||||
batchAddToCollectionButton.Enabled.Value = available;
|
||||
batchAddToCollectionButton.FadeTo(available ? 1 : 0, 200, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override InnerSearchTextBox CreateInnerTextBox() => new InnerTextBox
|
||||
{
|
||||
ScopedBeatmapSet = { BindTarget = ScopedBeatmapSet },
|
||||
@@ -492,6 +542,64 @@ namespace osu.Game.Screens.SelectV2
|
||||
return base.OnPressed(e);
|
||||
}
|
||||
}
|
||||
|
||||
private partial class BatchAddToCollectionButton : OsuClickableContainer
|
||||
{
|
||||
private readonly Box background;
|
||||
|
||||
private Color4 normalColour;
|
||||
private Color4 hoverColour;
|
||||
|
||||
public BatchAddToCollectionButton()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Width = 50;
|
||||
CornerRadius = 7;
|
||||
Masking = true;
|
||||
|
||||
Child = background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
normalColour = colours.Yellow.Darken(0.45f);
|
||||
hoverColour = colours.Yellow;
|
||||
|
||||
background.Colour = normalColour;
|
||||
|
||||
AddInternal(new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.Solid.Plus,
|
||||
Size = new Vector2(10),
|
||||
Shear = -OsuGame.SHEAR,
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
background.FadeColour(hoverColour, 100, Easing.Out);
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
background.FadeColour(normalColour, 100, Easing.Out);
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user