mirror of
https://github.com/ppy/osu.git
synced 2024-12-16 05:02:56 +08:00
247 lines
7.8 KiB
C#
247 lines
7.8 KiB
C#
// 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.Collections.Specialized;
|
|
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.Bindings;
|
|
using osu.Framework.Input.Events;
|
|
using osu.Framework.Threading;
|
|
using osu.Game.Extensions;
|
|
using osu.Game.Graphics.Cursor;
|
|
using osu.Game.Graphics.UserInterface;
|
|
using osu.Game.Input.Bindings;
|
|
using osu.Game.Online.Rooms;
|
|
using osuTK;
|
|
|
|
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
|
{
|
|
public class RoomsContainer : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
|
{
|
|
private readonly IBindableList<Room> rooms = new BindableList<Room>();
|
|
|
|
private readonly FillFlowContainer<DrawableRoom> roomFlow;
|
|
|
|
public IReadOnlyList<DrawableRoom> Rooms => roomFlow.FlowingChildren.Cast<DrawableRoom>().ToArray();
|
|
|
|
[Resolved(CanBeNull = true)]
|
|
private Bindable<FilterCriteria> filter { get; set; }
|
|
|
|
[Resolved]
|
|
private Bindable<Room> selectedRoom { get; set; }
|
|
|
|
[Resolved]
|
|
private IRoomManager roomManager { get; set; }
|
|
|
|
[Resolved(CanBeNull = true)]
|
|
private LoungeSubScreen loungeSubScreen { get; set; }
|
|
|
|
// handle deselection
|
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
|
|
|
public RoomsContainer()
|
|
{
|
|
RelativeSizeAxes = Axes.X;
|
|
AutoSizeAxes = Axes.Y;
|
|
|
|
// account for the fact we are in a scroll container and want a bit of spacing from the scroll bar.
|
|
Padding = new MarginPadding { Right = 5 };
|
|
|
|
InternalChild = new OsuContextMenuContainer
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
AutoSizeAxes = Axes.Y,
|
|
Child = roomFlow = new FillFlowContainer<DrawableRoom>
|
|
{
|
|
RelativeSizeAxes = Axes.X,
|
|
AutoSizeAxes = Axes.Y,
|
|
Direction = FillDirection.Vertical,
|
|
Spacing = new Vector2(10),
|
|
}
|
|
};
|
|
}
|
|
|
|
protected override void LoadComplete()
|
|
{
|
|
rooms.CollectionChanged += roomsChanged;
|
|
roomManager.RoomsUpdated += updateSorting;
|
|
|
|
rooms.BindTo(roomManager.Rooms);
|
|
|
|
filter?.BindValueChanged(criteria => Filter(criteria.NewValue));
|
|
|
|
selectedRoom.BindValueChanged(selection =>
|
|
{
|
|
updateSelection();
|
|
}, true);
|
|
}
|
|
|
|
private void updateSelection() =>
|
|
roomFlow.Children.ForEach(r => r.State = r.Room == selectedRoom.Value ? SelectionState.Selected : SelectionState.NotSelected);
|
|
|
|
public void Filter(FilterCriteria criteria)
|
|
{
|
|
roomFlow.Children.ForEach(r =>
|
|
{
|
|
if (criteria == null)
|
|
r.MatchingFilter = true;
|
|
else
|
|
{
|
|
bool matchingFilter = true;
|
|
|
|
matchingFilter &= r.Room.Playlist.Count == 0 || criteria.Ruleset == null || r.Room.Playlist.Any(i => i.Ruleset.Value.Equals(criteria.Ruleset));
|
|
|
|
if (!string.IsNullOrEmpty(criteria.SearchString))
|
|
matchingFilter &= r.FilterTerms.Any(term => term.Contains(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase));
|
|
|
|
r.MatchingFilter = matchingFilter;
|
|
}
|
|
});
|
|
}
|
|
|
|
private void roomsChanged(object sender, NotifyCollectionChangedEventArgs args)
|
|
{
|
|
switch (args.Action)
|
|
{
|
|
case NotifyCollectionChangedAction.Add:
|
|
addRooms(args.NewItems.Cast<Room>());
|
|
break;
|
|
|
|
case NotifyCollectionChangedAction.Remove:
|
|
removeRooms(args.OldItems.Cast<Room>());
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void addRooms(IEnumerable<Room> rooms)
|
|
{
|
|
foreach (var room in rooms)
|
|
{
|
|
roomFlow.Add(new DrawableRoom(room));
|
|
}
|
|
|
|
Filter(filter?.Value);
|
|
|
|
updateSelection();
|
|
}
|
|
|
|
private void removeRooms(IEnumerable<Room> rooms)
|
|
{
|
|
foreach (var r in rooms)
|
|
{
|
|
var toRemove = roomFlow.Single(d => d.Room == r);
|
|
toRemove.Action = null;
|
|
|
|
roomFlow.Remove(toRemove);
|
|
|
|
selectedRoom.Value = null;
|
|
}
|
|
}
|
|
|
|
private void updateSorting()
|
|
{
|
|
foreach (var room in roomFlow)
|
|
roomFlow.SetLayoutPosition(room, room.Room.Position.Value);
|
|
}
|
|
|
|
protected override bool OnClick(ClickEvent e)
|
|
{
|
|
selectedRoom.Value = null;
|
|
return base.OnClick(e);
|
|
}
|
|
|
|
#region Key selection logic (shared with BeatmapCarousel)
|
|
|
|
public bool OnPressed(GlobalAction action)
|
|
{
|
|
switch (action)
|
|
{
|
|
case GlobalAction.SelectNext:
|
|
beginRepeatSelection(() => selectNext(1), action);
|
|
return true;
|
|
|
|
case GlobalAction.SelectPrevious:
|
|
beginRepeatSelection(() => selectNext(-1), action);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public void OnReleased(GlobalAction action)
|
|
{
|
|
switch (action)
|
|
{
|
|
case GlobalAction.SelectNext:
|
|
case GlobalAction.SelectPrevious:
|
|
endRepeatSelection(action);
|
|
break;
|
|
}
|
|
}
|
|
|
|
private ScheduledDelegate repeatDelegate;
|
|
private object lastRepeatSource;
|
|
|
|
/// <summary>
|
|
/// Begin repeating the specified selection action.
|
|
/// </summary>
|
|
/// <param name="action">The action to perform.</param>
|
|
/// <param name="source">The source of the action. Used in conjunction with <see cref="endRepeatSelection"/> to only cancel the correct action (most recently pressed key).</param>
|
|
private void beginRepeatSelection(Action action, object source)
|
|
{
|
|
endRepeatSelection();
|
|
|
|
lastRepeatSource = source;
|
|
repeatDelegate = this.BeginKeyRepeat(Scheduler, action);
|
|
}
|
|
|
|
private void endRepeatSelection(object source = null)
|
|
{
|
|
// only the most recent source should be able to cancel the current action.
|
|
if (source != null && !EqualityComparer<object>.Default.Equals(lastRepeatSource, source))
|
|
return;
|
|
|
|
repeatDelegate?.Cancel();
|
|
repeatDelegate = null;
|
|
lastRepeatSource = null;
|
|
}
|
|
|
|
private void selectNext(int direction)
|
|
{
|
|
var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent);
|
|
|
|
Room room;
|
|
|
|
if (selectedRoom.Value == null)
|
|
room = visibleRooms.FirstOrDefault()?.Room;
|
|
else
|
|
{
|
|
if (direction < 0)
|
|
visibleRooms = visibleRooms.Reverse();
|
|
|
|
room = visibleRooms.SkipWhile(r => r.Room != selectedRoom.Value).Skip(1).FirstOrDefault()?.Room;
|
|
}
|
|
|
|
// we already have a valid selection only change selection if we still have a room to switch to.
|
|
if (room != null)
|
|
selectedRoom.Value = room;
|
|
}
|
|
|
|
#endregion
|
|
|
|
protected override void Dispose(bool isDisposing)
|
|
{
|
|
base.Dispose(isDisposing);
|
|
|
|
if (roomManager != null)
|
|
roomManager.RoomsUpdated -= updateSorting;
|
|
}
|
|
}
|
|
}
|