mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 07:33:55 +09:00
Merge branch 'master' into limit-match-settings-textbox
This commit is contained in:
@ -9,7 +9,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
{
|
||||
public abstract class DisableableTabControl<T> : TabControl<T>
|
||||
{
|
||||
public readonly BindableBool Enabled = new BindableBool();
|
||||
public readonly BindableBool Enabled = new BindableBool(true);
|
||||
|
||||
protected override void AddTabItem(TabItem<T> tab, bool addToDropdown = true)
|
||||
{
|
||||
|
@ -2,24 +2,28 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Components
|
||||
{
|
||||
public class DrawableGameType : CircularContainer, IHasTooltip
|
||||
{
|
||||
private readonly GameType type;
|
||||
private readonly MatchType type;
|
||||
|
||||
public LocalisableString TooltipText => type.Name;
|
||||
public LocalisableString TooltipText => type.GetLocalisableDescription();
|
||||
|
||||
public DrawableGameType(GameType type)
|
||||
public DrawableGameType(MatchType type)
|
||||
{
|
||||
this.type = type;
|
||||
Masking = true;
|
||||
@ -34,10 +38,138 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
};
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
private void load()
|
||||
{
|
||||
Add(type.GetIcon(colours, Height / 2));
|
||||
Add(getIconFor(type));
|
||||
}
|
||||
|
||||
private Drawable getIconFor(MatchType matchType)
|
||||
{
|
||||
float size = Height / 2;
|
||||
|
||||
switch (matchType)
|
||||
{
|
||||
default:
|
||||
case MatchType.Playlists:
|
||||
return new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(size),
|
||||
Icon = FontAwesome.Regular.Clock,
|
||||
Colour = colours.Blue,
|
||||
Shadow = false
|
||||
};
|
||||
|
||||
case MatchType.HeadToHead:
|
||||
return new VersusRow(colours.Blue, colours.Blue, size * 0.6f)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
|
||||
case MatchType.TeamVersus:
|
||||
return new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(2f),
|
||||
Children = new[]
|
||||
{
|
||||
new VersusRow(colours.Blue, colours.Pink, size * 0.5f),
|
||||
new VersusRow(colours.Blue, colours.Pink, size * 0.5f),
|
||||
},
|
||||
};
|
||||
|
||||
// case MatchType.TagCoop:
|
||||
// return new SpriteIcon
|
||||
// {
|
||||
// Anchor = Anchor.Centre,
|
||||
// Origin = Anchor.Centre,
|
||||
// Size = new Vector2(size),
|
||||
// Icon = FontAwesome.Solid.Sync,
|
||||
// Colour = colours.Blue,
|
||||
//
|
||||
// Shadow = false
|
||||
// };
|
||||
|
||||
// case MatchType.TagTeamCoop:
|
||||
// return new FillFlowContainer
|
||||
// {
|
||||
// Anchor = Anchor.Centre,
|
||||
// Origin = Anchor.Centre,
|
||||
// AutoSizeAxes = Axes.Both,
|
||||
// Direction = FillDirection.Horizontal,
|
||||
// Spacing = new Vector2(2f),
|
||||
// Children = new[]
|
||||
// {
|
||||
// new SpriteIcon
|
||||
// {
|
||||
// Icon = FontAwesome.Solid.Sync,
|
||||
// Size = new Vector2(size * 0.75f),
|
||||
// Colour = colours.Blue,
|
||||
// Shadow = false,
|
||||
// },
|
||||
// new SpriteIcon
|
||||
// {
|
||||
// Icon = FontAwesome.Solid.Sync,
|
||||
// Size = new Vector2(size * 0.75f),
|
||||
// Colour = colours.Pink,
|
||||
// Shadow = false,
|
||||
// },
|
||||
// },
|
||||
// };
|
||||
}
|
||||
}
|
||||
|
||||
private class VersusRow : FillFlowContainer
|
||||
{
|
||||
public VersusRow(Color4 first, Color4 second, float size)
|
||||
{
|
||||
var triangleSize = new Vector2(size);
|
||||
AutoSizeAxes = Axes.Both;
|
||||
Spacing = new Vector2(2f, 0f);
|
||||
|
||||
Children = new[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Size = triangleSize,
|
||||
Colour = first,
|
||||
Children = new[]
|
||||
{
|
||||
new EquilateralTriangle
|
||||
{
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Rotation = 90,
|
||||
EdgeSmoothness = new Vector2(1f),
|
||||
},
|
||||
},
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Size = triangleSize,
|
||||
Colour = second,
|
||||
Children = new[]
|
||||
{
|
||||
new EquilateralTriangle
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Rotation = -90,
|
||||
EdgeSmoothness = new Vector2(1f),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -14,8 +15,10 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
/// </summary>
|
||||
public class ListingPollingComponent : RoomPollingComponent
|
||||
{
|
||||
[Resolved]
|
||||
private Bindable<FilterCriteria> currentFilter { get; set; }
|
||||
public IBindable<bool> InitialRoomsReceived => initialRoomsReceived;
|
||||
private readonly Bindable<bool> initialRoomsReceived = new Bindable<bool>();
|
||||
|
||||
public readonly Bindable<FilterCriteria> Filter = new Bindable<FilterCriteria>();
|
||||
|
||||
[Resolved]
|
||||
private Bindable<Room> selectedRoom { get; set; }
|
||||
@ -23,9 +26,11 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
currentFilter.BindValueChanged(_ =>
|
||||
Filter.BindValueChanged(_ =>
|
||||
{
|
||||
NotifyRoomsReceived(null);
|
||||
RoomManager.ClearRooms();
|
||||
initialRoomsReceived.Value = false;
|
||||
|
||||
if (IsLoaded)
|
||||
PollImmediately();
|
||||
});
|
||||
@ -38,24 +43,26 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
if (!API.IsLoggedIn)
|
||||
return base.Poll();
|
||||
|
||||
if (Filter.Value == null)
|
||||
return base.Poll();
|
||||
|
||||
var tcs = new TaskCompletionSource<bool>();
|
||||
|
||||
pollReq?.Cancel();
|
||||
pollReq = new GetRoomsRequest(currentFilter.Value.Status, currentFilter.Value.Category);
|
||||
pollReq = new GetRoomsRequest(Filter.Value.Status, Filter.Value.Category);
|
||||
|
||||
pollReq.Success += result =>
|
||||
{
|
||||
for (int i = 0; i < result.Count; i++)
|
||||
foreach (var existing in RoomManager.Rooms.ToArray())
|
||||
{
|
||||
if (result[i].RoomID.Value == selectedRoom.Value?.RoomID.Value)
|
||||
{
|
||||
// The listing request always has less information than the opened room, so don't include it.
|
||||
result[i] = selectedRoom.Value;
|
||||
break;
|
||||
}
|
||||
if (result.All(r => r.RoomID.Value != existing.RoomID.Value))
|
||||
RoomManager.RemoveRoom(existing);
|
||||
}
|
||||
|
||||
NotifyRoomsReceived(result);
|
||||
foreach (var incoming in result)
|
||||
RoomManager.AddOrUpdateRoom(incoming);
|
||||
|
||||
initialRoomsReceived.Value = true;
|
||||
tcs.SetResult(true);
|
||||
};
|
||||
|
||||
|
@ -10,12 +10,12 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
{
|
||||
public class OnlinePlayBackgroundSprite : OnlinePlayComposite
|
||||
{
|
||||
private readonly BeatmapSetCoverType beatmapSetCoverType;
|
||||
protected readonly BeatmapSetCoverType BeatmapSetCoverType;
|
||||
private UpdateableBeatmapBackgroundSprite sprite;
|
||||
|
||||
public OnlinePlayBackgroundSprite(BeatmapSetCoverType beatmapSetCoverType = BeatmapSetCoverType.Cover)
|
||||
{
|
||||
this.beatmapSetCoverType = beatmapSetCoverType;
|
||||
BeatmapSetCoverType = beatmapSetCoverType;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -33,6 +33,6 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
sprite.Beatmap.Value = Playlist.FirstOrDefault()?.Beatmap.Value;
|
||||
}
|
||||
|
||||
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(beatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
|
||||
protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
@ -17,15 +16,12 @@ using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Components
|
||||
{
|
||||
public abstract class RoomManager : CompositeDrawable, IRoomManager
|
||||
public class RoomManager : Component, IRoomManager
|
||||
{
|
||||
public event Action RoomsUpdated;
|
||||
|
||||
private readonly BindableList<Room> rooms = new BindableList<Room>();
|
||||
|
||||
public IBindable<bool> InitialRoomsReceived => initialRoomsReceived;
|
||||
private readonly Bindable<bool> initialRoomsReceived = new Bindable<bool>();
|
||||
|
||||
public IBindableList<Room> Rooms => rooms;
|
||||
|
||||
protected IBindable<Room> JoinedRoom => joinedRoom;
|
||||
@ -40,15 +36,9 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
protected RoomManager()
|
||||
public RoomManager()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = CreatePollingComponents().Select(p =>
|
||||
{
|
||||
p.RoomsReceived = onRoomsReceived;
|
||||
return p;
|
||||
}).ToList();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
@ -67,10 +57,10 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
{
|
||||
joinedRoom.Value = room;
|
||||
|
||||
update(room, result);
|
||||
addRoom(room);
|
||||
AddOrUpdateRoom(result);
|
||||
room.CopyFrom(result); // Also copy back to the source model, since this is likely to have been stored elsewhere.
|
||||
|
||||
RoomsUpdated?.Invoke();
|
||||
// The server may not contain all properties (such as password), so invoke success with the given room.
|
||||
onSuccess?.Invoke(room);
|
||||
};
|
||||
|
||||
@ -118,84 +108,49 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
|
||||
private readonly HashSet<long> ignoredRooms = new HashSet<long>();
|
||||
|
||||
private void onRoomsReceived(List<Room> received)
|
||||
public void AddOrUpdateRoom(Room room)
|
||||
{
|
||||
if (received == null)
|
||||
{
|
||||
ClearRooms();
|
||||
Debug.Assert(room.RoomID.Value != null);
|
||||
|
||||
if (ignoredRooms.Contains(room.RoomID.Value.Value))
|
||||
return;
|
||||
}
|
||||
|
||||
// Remove past matches
|
||||
foreach (var r in rooms.ToList())
|
||||
room.Position.Value = -room.RoomID.Value.Value;
|
||||
|
||||
try
|
||||
{
|
||||
if (received.All(e => e.RoomID.Value != r.RoomID.Value))
|
||||
rooms.Remove(r);
|
||||
}
|
||||
foreach (var pi in room.Playlist)
|
||||
pi.MapObjects(beatmaps, rulesets);
|
||||
|
||||
for (int i = 0; i < received.Count; i++)
|
||||
var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value);
|
||||
if (existing == null)
|
||||
rooms.Add(room);
|
||||
else
|
||||
existing.CopyFrom(room);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
var room = received[i];
|
||||
Logger.Error(ex, $"Failed to update room: {room.Name.Value}.");
|
||||
|
||||
Debug.Assert(room.RoomID.Value != null);
|
||||
|
||||
if (ignoredRooms.Contains(room.RoomID.Value.Value))
|
||||
continue;
|
||||
|
||||
room.Position.Value = i;
|
||||
|
||||
try
|
||||
{
|
||||
update(room, room);
|
||||
addRoom(room);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Error(ex, $"Failed to update room: {room.Name.Value}.");
|
||||
|
||||
ignoredRooms.Add(room.RoomID.Value.Value);
|
||||
rooms.Remove(room);
|
||||
}
|
||||
ignoredRooms.Add(room.RoomID.Value.Value);
|
||||
rooms.Remove(room);
|
||||
}
|
||||
|
||||
RoomsUpdated?.Invoke();
|
||||
initialRoomsReceived.Value = true;
|
||||
notifyRoomsUpdated();
|
||||
}
|
||||
|
||||
protected void RemoveRoom(Room room) => rooms.Remove(room);
|
||||
public void RemoveRoom(Room room)
|
||||
{
|
||||
rooms.Remove(room);
|
||||
notifyRoomsUpdated();
|
||||
}
|
||||
|
||||
protected void ClearRooms()
|
||||
public void ClearRooms()
|
||||
{
|
||||
rooms.Clear();
|
||||
initialRoomsReceived.Value = false;
|
||||
notifyRoomsUpdated();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Updates a local <see cref="Room"/> with a remote copy.
|
||||
/// </summary>
|
||||
/// <param name="local">The local <see cref="Room"/> to update.</param>
|
||||
/// <param name="remote">The remote <see cref="Room"/> to update with.</param>
|
||||
private void update(Room local, Room remote)
|
||||
{
|
||||
foreach (var pi in remote.Playlist)
|
||||
pi.MapObjects(beatmaps, rulesets);
|
||||
|
||||
local.CopyFrom(remote);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="Room"/> to the list of available rooms.
|
||||
/// </summary>
|
||||
/// <param name="room">The <see cref="Room"/> to add.</param>
|
||||
private void addRoom(Room room)
|
||||
{
|
||||
var existing = rooms.FirstOrDefault(e => e.RoomID.Value == room.RoomID.Value);
|
||||
if (existing == null)
|
||||
rooms.Add(room);
|
||||
else
|
||||
existing.CopyFrom(room);
|
||||
}
|
||||
|
||||
protected abstract IEnumerable<RoomPollingComponent> CreatePollingComponents();
|
||||
private void notifyRoomsUpdated() => Scheduler.AddOnce(() => RoomsUpdated?.Invoke());
|
||||
}
|
||||
}
|
||||
|
@ -1,29 +1,18 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Components
|
||||
{
|
||||
public abstract class RoomPollingComponent : PollingComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// Invoked when any <see cref="Room"/>s have been received from the API.
|
||||
/// <para>
|
||||
/// Any <see cref="Room"/>s present locally but not returned by this event are to be removed from display.
|
||||
/// If null, the display of local rooms is reset to an initial state.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
public Action<List<Room>> RoomsReceived;
|
||||
|
||||
[Resolved]
|
||||
protected IAPIProvider API { get; private set; }
|
||||
|
||||
protected void NotifyRoomsReceived(List<Room> rooms) => RoomsReceived?.Invoke(rooms);
|
||||
[Resolved]
|
||||
protected IRoomManager RoomManager { get; private set; }
|
||||
}
|
||||
}
|
||||
|
@ -1,117 +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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Components
|
||||
{
|
||||
public class RoomStatusInfo : OnlinePlayComposite
|
||||
{
|
||||
public RoomStatusInfo()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
StatusPart statusPart;
|
||||
EndDatePart endDatePart;
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
statusPart = new StatusPart
|
||||
{
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 14)
|
||||
},
|
||||
endDatePart = new EndDatePart { Font = OsuFont.GetFont(size: 14) }
|
||||
}
|
||||
};
|
||||
|
||||
statusPart.EndDate.BindTo(EndDate);
|
||||
statusPart.Status.BindTo(Status);
|
||||
statusPart.Availability.BindTo(Availability);
|
||||
endDatePart.EndDate.BindTo(EndDate);
|
||||
}
|
||||
|
||||
private class EndDatePart : DrawableDate
|
||||
{
|
||||
public readonly IBindable<DateTimeOffset?> EndDate = new Bindable<DateTimeOffset?>();
|
||||
|
||||
public EndDatePart()
|
||||
: base(DateTimeOffset.UtcNow)
|
||||
{
|
||||
EndDate.BindValueChanged(date =>
|
||||
{
|
||||
// If null, set a very large future date to prevent unnecessary schedules.
|
||||
Date = date.NewValue ?? DateTimeOffset.Now.AddYears(1);
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override string Format()
|
||||
{
|
||||
if (EndDate.Value == null)
|
||||
return string.Empty;
|
||||
|
||||
var diffToNow = Date.Subtract(DateTimeOffset.Now);
|
||||
|
||||
if (diffToNow.TotalSeconds < -5)
|
||||
return $"Closed {base.Format()}";
|
||||
|
||||
if (diffToNow.TotalSeconds < 0)
|
||||
return "Closed";
|
||||
|
||||
if (diffToNow.TotalSeconds < 5)
|
||||
return "Closing soon";
|
||||
|
||||
return $"Closing {base.Format()}";
|
||||
}
|
||||
}
|
||||
|
||||
private class StatusPart : EndDatePart
|
||||
{
|
||||
public readonly IBindable<RoomStatus> Status = new Bindable<RoomStatus>();
|
||||
public readonly IBindable<RoomAvailability> Availability = new Bindable<RoomAvailability>();
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
public StatusPart()
|
||||
{
|
||||
EndDate.BindValueChanged(_ => Format());
|
||||
Status.BindValueChanged(_ => Format());
|
||||
Availability.BindValueChanged(_ => Format());
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Text = Format();
|
||||
}
|
||||
|
||||
protected override string Format()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
return string.Empty;
|
||||
|
||||
RoomStatus status = Date < DateTimeOffset.Now ? new RoomStatusEnded() : Status.Value ?? new RoomStatusOpen();
|
||||
|
||||
this.FadeColour(status.GetAppropriateColour(colours), 100);
|
||||
return $"{Availability.Value.GetDescription()}, {status.Message}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
@ -48,17 +46,7 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
|
||||
pollReq.Success += result =>
|
||||
{
|
||||
// existing rooms need to be ordered by their position because the received of NotifyRoomsReceives expects to be able to sort them based on this order.
|
||||
var rooms = new List<Room>(roomManager.Rooms.OrderBy(r => r.Position.Value));
|
||||
|
||||
int index = rooms.FindIndex(r => r.RoomID.Value == result.RoomID.Value);
|
||||
|
||||
if (index < 0)
|
||||
return;
|
||||
|
||||
rooms[index] = result;
|
||||
|
||||
NotifyRoomsReceived(rooms);
|
||||
RoomManager.AddOrUpdateRoom(result);
|
||||
tcs.SetResult(true);
|
||||
};
|
||||
|
||||
|
@ -1,15 +1,17 @@
|
||||
// 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.Specialized;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Screens.Ranking.Expanded;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Components
|
||||
@ -62,8 +64,8 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
minDisplay = new StarRatingDisplay(default),
|
||||
maxDisplay = new StarRatingDisplay(default)
|
||||
minDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Range),
|
||||
maxDisplay = new StarRatingDisplay(default, StarRatingDisplaySize.Range)
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -85,9 +87,10 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
|
||||
minDisplay.Current.Value = minDifficulty;
|
||||
maxDisplay.Current.Value = maxDifficulty;
|
||||
maxDisplay.Alpha = Precision.AlmostEquals(Math.Round(minDifficulty.Stars, 2), Math.Round(maxDifficulty.Stars, 2)) ? 0 : 1;
|
||||
|
||||
minBackground.Colour = colours.ForDifficultyRating(minDifficulty.DifficultyRating, true);
|
||||
maxBackground.Colour = colours.ForDifficultyRating(maxDifficulty.DifficultyRating, true);
|
||||
minBackground.Colour = colours.ForStarDifficulty(minDifficulty.Stars);
|
||||
maxBackground.Colour = colours.ForStarDifficulty(maxDifficulty.Stars);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,19 +2,15 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using Humanizer;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
@ -22,52 +18,30 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
public const float HEIGHT = 80;
|
||||
|
||||
private readonly ScreenStack stack;
|
||||
private readonly MultiHeaderTitle title;
|
||||
|
||||
public Header(string mainTitle, ScreenStack stack)
|
||||
{
|
||||
this.stack = stack;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = HEIGHT;
|
||||
Padding = new MarginPadding { Left = WaveOverlayContainer.WIDTH_PADDING };
|
||||
|
||||
HeaderBreadcrumbControl breadcrumbs;
|
||||
MultiHeaderTitle title;
|
||||
|
||||
Children = new Drawable[]
|
||||
Child = title = new MultiHeaderTitle(mainTitle)
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex(@"#1f1921"),
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = WaveOverlayContainer.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
title = new MultiHeaderTitle(mainTitle)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
},
|
||||
breadcrumbs = new HeaderBreadcrumbControl(stack)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft
|
||||
}
|
||||
},
|
||||
},
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
};
|
||||
|
||||
breadcrumbs.Current.ValueChanged += screen =>
|
||||
{
|
||||
if (screen.NewValue is IOnlinePlaySubScreen onlineSubScreen)
|
||||
title.Screen = onlineSubScreen;
|
||||
};
|
||||
|
||||
breadcrumbs.Current.TriggerChange();
|
||||
// unnecessary to unbind these as this header has the same lifetime as the screen stack we are attaching to.
|
||||
stack.ScreenPushed += (_, __) => updateSubScreenTitle();
|
||||
stack.ScreenExited += (_, __) => updateSubScreenTitle();
|
||||
}
|
||||
|
||||
private void updateSubScreenTitle() => title.Screen = stack.CurrentScreen as IOnlinePlaySubScreen;
|
||||
|
||||
private class MultiHeaderTitle : CompositeDrawable
|
||||
{
|
||||
private const float spacing = 6;
|
||||
@ -75,9 +49,10 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
private readonly OsuSpriteText dot;
|
||||
private readonly OsuSpriteText pageTitle;
|
||||
|
||||
[CanBeNull]
|
||||
public IOnlinePlaySubScreen Screen
|
||||
{
|
||||
set => pageTitle.Text = value.ShortTitle.Titleize();
|
||||
set => pageTitle.Text = value?.ShortTitle.Titleize() ?? string.Empty;
|
||||
}
|
||||
|
||||
public MultiHeaderTitle(string mainTitle)
|
||||
@ -125,35 +100,5 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
pageTitle.Colour = dot.Colour = colours.Yellow;
|
||||
}
|
||||
}
|
||||
|
||||
private class HeaderBreadcrumbControl : ScreenBreadcrumbControl
|
||||
{
|
||||
public HeaderBreadcrumbControl(ScreenStack stack)
|
||||
: base(stack)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
StripColour = Color4.Transparent;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
AccentColour = Color4Extensions.FromHex("#e35c99");
|
||||
}
|
||||
|
||||
protected override TabItem<IScreen> CreateTabItem(IScreen value) => new HeaderBreadcrumbTabItem(value)
|
||||
{
|
||||
AccentColour = AccentColour
|
||||
};
|
||||
|
||||
private class HeaderBreadcrumbTabItem : BreadcrumbTabItem
|
||||
{
|
||||
public HeaderBreadcrumbTabItem(IScreen value)
|
||||
: base(value)
|
||||
{
|
||||
Bar.Colour = Color4.Transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,16 +18,29 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
/// </summary>
|
||||
event Action RoomsUpdated;
|
||||
|
||||
/// <summary>
|
||||
/// Whether an initial listing of rooms has been received.
|
||||
/// </summary>
|
||||
IBindable<bool> InitialRoomsReceived { get; }
|
||||
|
||||
/// <summary>
|
||||
/// All the active <see cref="Room"/>s.
|
||||
/// </summary>
|
||||
IBindableList<Room> Rooms { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <see cref="Room"/> to this <see cref="IRoomManager"/>.
|
||||
/// If already existing, the local room will be updated with the given one.
|
||||
/// </summary>
|
||||
/// <param name="room">The incoming <see cref="Room"/>.</param>
|
||||
void AddOrUpdateRoom(Room room);
|
||||
|
||||
/// <summary>
|
||||
/// Removes a <see cref="Room"/> from this <see cref="IRoomManager"/>.
|
||||
/// </summary>
|
||||
/// <param name="room">The <see cref="Room"/> to remove.</param>
|
||||
void RemoveRoom(Room room);
|
||||
|
||||
/// <summary>
|
||||
/// Removes all <see cref="Room"/>s from this <see cref="IRoomManager"/>.
|
||||
/// </summary>
|
||||
void ClearRooms();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="Room"/>.
|
||||
/// </summary>
|
||||
|
@ -5,10 +5,13 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
@ -18,7 +21,6 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
@ -26,6 +28,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -35,19 +38,18 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
public class DrawableRoom : OsuClickableContainer, IStateful<SelectionState>, IFilterable, IHasContextMenu, IHasPopover, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
public const float SELECTION_BORDER_WIDTH = 4;
|
||||
private const float corner_radius = 5;
|
||||
private const float corner_radius = 10;
|
||||
private const float transition_duration = 60;
|
||||
private const float content_padding = 10;
|
||||
private const float height = 110;
|
||||
private const float side_strip_width = 5;
|
||||
private const float cover_width = 145;
|
||||
private const float height = 100;
|
||||
|
||||
public event Action<SelectionState> StateChanged;
|
||||
|
||||
private readonly Box selectionBox;
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
||||
|
||||
private Drawable selectionBox;
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private OnlinePlayScreen parentScreen { get; set; }
|
||||
private LoungeSubScreen loungeScreen { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
@ -62,19 +64,26 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
private SelectionState state;
|
||||
|
||||
private Sample sampleSelect;
|
||||
private Sample sampleJoin;
|
||||
|
||||
public SelectionState State
|
||||
{
|
||||
get => state;
|
||||
set
|
||||
{
|
||||
if (value == state) return;
|
||||
if (value == state)
|
||||
return;
|
||||
|
||||
state = value;
|
||||
|
||||
if (state == SelectionState.Selected)
|
||||
selectionBox.FadeIn(transition_duration);
|
||||
else
|
||||
selectionBox.FadeOut(transition_duration);
|
||||
if (selectionBox != null)
|
||||
{
|
||||
if (state == SelectionState.Selected)
|
||||
selectionBox.FadeIn(transition_duration);
|
||||
else
|
||||
selectionBox.FadeOut(transition_duration);
|
||||
}
|
||||
|
||||
StateChanged?.Invoke(State);
|
||||
}
|
||||
@ -101,6 +110,25 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
}
|
||||
}
|
||||
|
||||
private int numberOfAvatars = 7;
|
||||
|
||||
public int NumberOfAvatars
|
||||
{
|
||||
get => numberOfAvatars;
|
||||
set
|
||||
{
|
||||
numberOfAvatars = value;
|
||||
|
||||
if (recentParticipantsList != null)
|
||||
recentParticipantsList.NumberOfCircles = value;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Bindable<RoomCategory> roomCategory = new Bindable<RoomCategory>();
|
||||
|
||||
private RecentParticipantsList recentParticipantsList;
|
||||
private RoomSpecialCategoryPill specialCategoryPill;
|
||||
|
||||
public bool FilteringActive { get; set; }
|
||||
|
||||
private PasswordProtectedIcon passwordIcon;
|
||||
@ -112,115 +140,197 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
Room = room;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = height + SELECTION_BORDER_WIDTH * 2;
|
||||
CornerRadius = corner_radius + SELECTION_BORDER_WIDTH / 2;
|
||||
Masking = true;
|
||||
Height = height;
|
||||
|
||||
// create selectionBox here so State can be set before being loaded
|
||||
selectionBox = new Box
|
||||
Masking = true;
|
||||
CornerRadius = corner_radius + SELECTION_BORDER_WIDTH / 2;
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0f,
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = Color4.Black.Opacity(40),
|
||||
Radius = 5,
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
private void load(OverlayColourProvider colours, AudioManager audio)
|
||||
{
|
||||
float stripWidth = side_strip_width * (Room.Category.Value == RoomCategory.Spotlight ? 2 : 1);
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new StatusColouredContainer(transition_duration)
|
||||
// This resolves internal 1px gaps due to applying the (parenting) corner radius and masking across multiple filling background sprites.
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = selectionBox
|
||||
Colour = colours.Background5,
|
||||
},
|
||||
new OnlinePlayBackgroundSprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Name = @"Room content",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(SELECTION_BORDER_WIDTH),
|
||||
// This negative padding resolves 1px gaps between this background and the background above.
|
||||
Padding = new MarginPadding { Left = 20, Vertical = -0.5f },
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = corner_radius,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = Color4.Black.Opacity(40),
|
||||
Radius = 5,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex(@"212121"),
|
||||
},
|
||||
new StatusColouredContainer(transition_duration)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = stripWidth,
|
||||
Child = new Box { RelativeSizeAxes = Axes.Both }
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = cover_width,
|
||||
Masking = true,
|
||||
Margin = new MarginPadding { Left = stripWidth },
|
||||
Child = new OnlinePlayBackgroundSprite(BeatmapSetCoverType.List) { RelativeSizeAxes = Axes.Both }
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.Relative, 0.2f)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Background5,
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientHorizontal(colours.Background5, colours.Background5.Opacity(0.3f))
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Name = @"Left details",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = content_padding,
|
||||
Left = stripWidth + cover_width + content_padding,
|
||||
Right = content_padding,
|
||||
Left = 20,
|
||||
Vertical = 5
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new RoomName { Font = OsuFont.GetFont(size: 18) },
|
||||
new ParticipantInfo(),
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new RoomStatusPill
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
},
|
||||
specialCategoryPill = new RoomSpecialCategoryPill
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
},
|
||||
new EndDateInfo
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
},
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = 3 },
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new RoomNameText(),
|
||||
new RoomHostText(),
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 5),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new RoomStatusInfo(),
|
||||
new BeatmapTitle { TextSize = 14 },
|
||||
},
|
||||
},
|
||||
new ModeTypeInfo
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
},
|
||||
new PlaylistCountPill
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
new StarRatingRangeDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Scale = new Vector2(0.8f)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Name = "Right content",
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Right = 10,
|
||||
Vertical = 5
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
recentParticipantsList = new RecentParticipantsList
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
NumberOfCircles = NumberOfAvatars
|
||||
}
|
||||
}
|
||||
},
|
||||
passwordIcon = new PasswordProtectedIcon { Alpha = 0 }
|
||||
},
|
||||
},
|
||||
},
|
||||
new StatusColouredContainer(transition_duration)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = selectionBox = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = state == SelectionState.Selected ? 1 : 0,
|
||||
Masking = true,
|
||||
CornerRadius = corner_radius,
|
||||
BorderThickness = SELECTION_BORDER_WIDTH,
|
||||
BorderColour = Color4.White,
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
sampleSelect = audio.Samples.Get($@"UI/{HoverSampleSet.Default.GetDescription()}-select");
|
||||
sampleJoin = audio.Samples.Get($@"UI/{HoverSampleSet.Submit.GetDescription()}-select");
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
@ -240,6 +350,15 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
else
|
||||
Alpha = 0;
|
||||
|
||||
roomCategory.BindTo(Room.Category);
|
||||
roomCategory.BindValueChanged(c =>
|
||||
{
|
||||
if (c.NewValue == RoomCategory.Spotlight)
|
||||
specialCategoryPill.Show();
|
||||
else
|
||||
specialCategoryPill.Hide();
|
||||
}, true);
|
||||
|
||||
hasPassword.BindTo(Room.HasPassword);
|
||||
hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
|
||||
}
|
||||
@ -250,7 +369,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
|
||||
{
|
||||
parentScreen?.OpenNewRoom(Room.DeepClone());
|
||||
lounge?.Open(Room.DeepClone());
|
||||
})
|
||||
};
|
||||
|
||||
@ -262,7 +381,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
switch (action)
|
||||
{
|
||||
case GlobalAction.Select:
|
||||
Click();
|
||||
TriggerClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -273,32 +392,40 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool ShouldBeConsideredForInput(Drawable child) => state == SelectionState.Selected;
|
||||
protected override bool ShouldBeConsideredForInput(Drawable child) => state == SelectionState.Selected || child is HoverSounds;
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (Room != selectedRoom.Value)
|
||||
{
|
||||
sampleSelect?.Play();
|
||||
selectedRoom.Value = Room;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Room.HasPassword.Value)
|
||||
{
|
||||
sampleJoin?.Play();
|
||||
this.ShowPopover();
|
||||
return true;
|
||||
}
|
||||
|
||||
sampleJoin?.Play();
|
||||
lounge?.Join(Room, null);
|
||||
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
private class RoomName : OsuSpriteText
|
||||
private class RoomNameText : OsuSpriteText
|
||||
{
|
||||
[Resolved(typeof(Room), nameof(Online.Rooms.Room.Name))]
|
||||
private Bindable<string> name { get; set; }
|
||||
|
||||
public RoomNameText()
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 28);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -306,6 +433,41 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
}
|
||||
}
|
||||
|
||||
private class RoomHostText : OnlinePlayComposite
|
||||
{
|
||||
private LinkFlowContainer hostText;
|
||||
|
||||
public RoomHostText()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = hostText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 16))
|
||||
{
|
||||
AutoSizeAxes = Axes.Both
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Host.BindValueChanged(host =>
|
||||
{
|
||||
hostText.Clear();
|
||||
|
||||
if (host.NewValue != null)
|
||||
{
|
||||
hostText.AddText("hosted by ");
|
||||
hostText.AddUserLink(host.NewValue);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
public class PasswordProtectedIcon : CompositeDrawable
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
@ -353,7 +515,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
private OsuPasswordTextBox passwordTextbox;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
private void load()
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
|
65
osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs
Normal file
65
osu.Game/Screens/OnlinePlay/Lounge/Components/EndDateInfo.cs
Normal file
@ -0,0 +1,65 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public class EndDateInfo : OnlinePlayComposite
|
||||
{
|
||||
public EndDateInfo()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = new EndDatePart
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
|
||||
EndDate = { BindTarget = EndDate }
|
||||
};
|
||||
}
|
||||
|
||||
private class EndDatePart : DrawableDate
|
||||
{
|
||||
public readonly IBindable<DateTimeOffset?> EndDate = new Bindable<DateTimeOffset?>();
|
||||
|
||||
public EndDatePart()
|
||||
: base(DateTimeOffset.UtcNow)
|
||||
{
|
||||
EndDate.BindValueChanged(date =>
|
||||
{
|
||||
// If null, set a very large future date to prevent unnecessary schedules.
|
||||
Date = date.NewValue ?? DateTimeOffset.Now.AddYears(1);
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override string Format()
|
||||
{
|
||||
if (EndDate.Value == null)
|
||||
return string.Empty;
|
||||
|
||||
var diffToNow = Date.Subtract(DateTimeOffset.Now);
|
||||
|
||||
if (diffToNow.TotalSeconds < -5)
|
||||
return $"Closed {base.Format()}";
|
||||
|
||||
if (diffToNow.TotalSeconds < 0)
|
||||
return "Closed";
|
||||
|
||||
if (diffToNow.TotalSeconds < 5)
|
||||
return "Closing soon";
|
||||
|
||||
return $"Closing {base.Format()}";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,135 +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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public abstract class FilterControl : CompositeDrawable
|
||||
{
|
||||
protected const float VERTICAL_PADDING = 10;
|
||||
protected const float HORIZONTAL_PADDING = 80;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private Bindable<FilterCriteria> filter { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; }
|
||||
|
||||
private readonly Box tabStrip;
|
||||
private readonly SearchTextBox search;
|
||||
private readonly PageTabControl<RoomStatusFilter> tabs;
|
||||
|
||||
protected FilterControl()
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.25f,
|
||||
},
|
||||
tabStrip = new Box
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 1,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Top = VERTICAL_PADDING,
|
||||
Horizontal = HORIZONTAL_PADDING
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
search = new FilterSearchTextBox
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
tabs = new PageTabControl<RoomStatusFilter>
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
tabs.Current.Value = RoomStatusFilter.Open;
|
||||
tabs.Current.TriggerChange();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
filter ??= new Bindable<FilterCriteria>();
|
||||
tabStrip.Colour = colours.Yellow;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
search.Current.BindValueChanged(_ => updateFilterDebounced());
|
||||
ruleset.BindValueChanged(_ => UpdateFilter());
|
||||
tabs.Current.BindValueChanged(_ => UpdateFilter(), true);
|
||||
}
|
||||
|
||||
private ScheduledDelegate scheduledFilterUpdate;
|
||||
|
||||
private void updateFilterDebounced()
|
||||
{
|
||||
scheduledFilterUpdate?.Cancel();
|
||||
scheduledFilterUpdate = Scheduler.AddDelayed(UpdateFilter, 200);
|
||||
}
|
||||
|
||||
protected void UpdateFilter() => Scheduler.AddOnce(updateFilter);
|
||||
|
||||
private void updateFilter()
|
||||
{
|
||||
scheduledFilterUpdate?.Cancel();
|
||||
|
||||
var criteria = CreateCriteria();
|
||||
criteria.SearchString = search.Current.Value;
|
||||
criteria.Status = tabs.Current.Value;
|
||||
criteria.Ruleset = ruleset.Value;
|
||||
|
||||
filter.Value = criteria;
|
||||
}
|
||||
|
||||
protected virtual FilterCriteria CreateCriteria() => new FilterCriteria();
|
||||
|
||||
public bool HoldFocus
|
||||
{
|
||||
get => search.HoldFocus;
|
||||
set => search.HoldFocus = value;
|
||||
}
|
||||
|
||||
public void TakeFocus() => search.TakeFocus();
|
||||
|
||||
private class FilterSearchTextBox : SearchTextBox
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
BackgroundUnfocused = OsuColour.Gray(0.06f);
|
||||
BackgroundFocused = OsuColour.Gray(0.12f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,88 +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 Humanizer;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public class ParticipantInfo : OnlinePlayComposite
|
||||
{
|
||||
public ParticipantInfo()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 15f;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
OsuSpriteText summary;
|
||||
Container flagContainer;
|
||||
LinkFlowContainer hostText;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5f, 0f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
flagContainer = new Container
|
||||
{
|
||||
Width = 22f,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
},
|
||||
hostText = new LinkFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
AutoSizeAxes = Axes.Both
|
||||
}
|
||||
},
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Colour = colours.Gray9,
|
||||
Children = new[]
|
||||
{
|
||||
summary = new OsuSpriteText
|
||||
{
|
||||
Text = "0 participants",
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
Host.BindValueChanged(host =>
|
||||
{
|
||||
hostText.Clear();
|
||||
flagContainer.Clear();
|
||||
|
||||
if (host.NewValue != null)
|
||||
{
|
||||
hostText.AddText("hosted by ");
|
||||
hostText.AddUserLink(host.NewValue, s => s.Font = s.Font.With(Typeface.Torus, weight: FontWeight.Bold, italics: true));
|
||||
|
||||
flagContainer.Child = new UpdateableFlag(host.NewValue.Country) { RelativeSizeAxes = Axes.Both };
|
||||
}
|
||||
}, true);
|
||||
|
||||
ParticipantCount.BindValueChanged(count => summary.Text = "participant".ToQuantity(count.NewValue), true);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// Displays contents in a "pill".
|
||||
/// </summary>
|
||||
public class PillContainer : Container
|
||||
{
|
||||
private const float padding = 8;
|
||||
|
||||
public readonly Drawable Background;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
private readonly Container content;
|
||||
|
||||
public PillContainer()
|
||||
{
|
||||
AutoSizeAxes = Axes.X;
|
||||
Height = 16;
|
||||
|
||||
InternalChild = new CircularContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Masking = true,
|
||||
Children = new[]
|
||||
{
|
||||
Background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.5f
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = padding },
|
||||
Child = new GridContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize, minSize: 80 - 2 * padding)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Padding = new MarginPadding { Bottom = 2 },
|
||||
Child = content = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
// 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.Collections.Specialized;
|
||||
using Humanizer;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A pill that displays the playlist item count.
|
||||
/// </summary>
|
||||
public class PlaylistCountPill : OnlinePlayComposite
|
||||
{
|
||||
private OsuTextFlowContainer count;
|
||||
|
||||
public PlaylistCountPill()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = new PillContainer
|
||||
{
|
||||
Child = count = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Playlist.BindCollectionChanged(updateCount, true);
|
||||
}
|
||||
|
||||
private void updateCount(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
count.Clear();
|
||||
count.AddText(Playlist.Count.ToString(), s => s.Font = s.Font.With(weight: FontWeight.Bold));
|
||||
count.AddText(" ");
|
||||
count.AddText("Beatmap".ToQuantity(Playlist.Count, ShowQuantityAs.None));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,59 +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 osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public class PlaylistsFilterControl : FilterControl
|
||||
{
|
||||
private readonly Dropdown<PlaylistsCategory> dropdown;
|
||||
|
||||
public PlaylistsFilterControl()
|
||||
{
|
||||
AddInternal(dropdown = new SlimEnumDropdown<PlaylistsCategory>
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Width = 160,
|
||||
X = -HORIZONTAL_PADDING,
|
||||
Y = -30
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
dropdown.Current.BindValueChanged(_ => UpdateFilter());
|
||||
}
|
||||
|
||||
protected override FilterCriteria CreateCriteria()
|
||||
{
|
||||
var criteria = base.CreateCriteria();
|
||||
|
||||
switch (dropdown.Current.Value)
|
||||
{
|
||||
case PlaylistsCategory.Normal:
|
||||
criteria.Category = "normal";
|
||||
break;
|
||||
|
||||
case PlaylistsCategory.Spotlight:
|
||||
criteria.Category = "spotlight";
|
||||
break;
|
||||
}
|
||||
|
||||
return criteria;
|
||||
}
|
||||
|
||||
private enum PlaylistsCategory
|
||||
{
|
||||
Any,
|
||||
Normal,
|
||||
Spotlight
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public class RankRangePill : MultiplayerRoomComposite
|
||||
{
|
||||
private OsuTextFlowContainer rankFlow;
|
||||
|
||||
public RankRangePill()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = new PillContainer
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(4),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(8),
|
||||
Icon = FontAwesome.Solid.User
|
||||
},
|
||||
rankFlow = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnRoomUpdated()
|
||||
{
|
||||
base.OnRoomUpdated();
|
||||
|
||||
rankFlow.Clear();
|
||||
|
||||
if (Room == null || Room.Users.All(u => u.User == null))
|
||||
{
|
||||
rankFlow.AddText("-");
|
||||
return;
|
||||
}
|
||||
|
||||
int minRank = Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Min();
|
||||
int maxRank = Room.Users.Select(u => u.User?.Statistics.GlobalRank ?? 0).DefaultIfEmpty(0).Max();
|
||||
|
||||
rankFlow.AddText("#");
|
||||
rankFlow.AddText(minRank.ToString("#,0"), s => s.Font = s.Font.With(weight: FontWeight.Bold));
|
||||
|
||||
rankFlow.AddText(" - ");
|
||||
|
||||
rankFlow.AddText("#");
|
||||
rankFlow.AddText(maxRank.ToString("#,0"), s => s.Font = s.Font.With(weight: FontWeight.Bold));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,278 @@
|
||||
// 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.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Users;
|
||||
using osu.Game.Users.Drawables;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public class RecentParticipantsList : OnlinePlayComposite
|
||||
{
|
||||
private const float avatar_size = 36;
|
||||
|
||||
private FillFlowContainer<CircularAvatar> avatarFlow;
|
||||
|
||||
private HiddenUserCount hiddenUsers;
|
||||
private OsuSpriteText totalCount;
|
||||
|
||||
public RecentParticipantsList()
|
||||
{
|
||||
AutoSizeAxes = Axes.X;
|
||||
Height = 60;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 10,
|
||||
Shear = new Vector2(0.2f, 0),
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Background4,
|
||||
}
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(4),
|
||||
Padding = new MarginPadding { Right = 16 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(16),
|
||||
Margin = new MarginPadding { Left = 8 },
|
||||
Icon = FontAwesome.Solid.User,
|
||||
},
|
||||
totalCount = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.Default.With(weight: FontWeight.Bold),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
avatarFlow = new FillFlowContainer<CircularAvatar>
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(4),
|
||||
Margin = new MarginPadding { Left = 4 },
|
||||
},
|
||||
hiddenUsers = new HiddenUserCount
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
RecentParticipants.BindCollectionChanged(onParticipantsChanged, true);
|
||||
ParticipantCount.BindValueChanged(_ =>
|
||||
{
|
||||
updateHiddenUsers();
|
||||
totalCount.Text = ParticipantCount.Value.ToString();
|
||||
}, true);
|
||||
}
|
||||
|
||||
private int numberOfCircles = 4;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of circles visible (including the "hidden count" circle in the overflow case).
|
||||
/// </summary>
|
||||
public int NumberOfCircles
|
||||
{
|
||||
get => numberOfCircles;
|
||||
set
|
||||
{
|
||||
numberOfCircles = value;
|
||||
|
||||
if (LoadState < LoadState.Loaded)
|
||||
return;
|
||||
|
||||
// Reinitialising the list looks janky, but this is unlikely to be used in a setting where it's visible.
|
||||
clearUsers();
|
||||
foreach (var u in RecentParticipants)
|
||||
addUser(u);
|
||||
|
||||
updateHiddenUsers();
|
||||
}
|
||||
}
|
||||
|
||||
private void onParticipantsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (var added in e.NewItems.OfType<User>())
|
||||
addUser(added);
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (var removed in e.OldItems.OfType<User>())
|
||||
removeUser(removed);
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Reset:
|
||||
clearUsers();
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Replace:
|
||||
case NotifyCollectionChangedAction.Move:
|
||||
// Easiest is to just reinitialise the whole list. These are unlikely to ever be use cases.
|
||||
clearUsers();
|
||||
foreach (var u in RecentParticipants)
|
||||
addUser(u);
|
||||
break;
|
||||
}
|
||||
|
||||
updateHiddenUsers();
|
||||
}
|
||||
|
||||
private int displayedCircles => avatarFlow.Count + (hiddenUsers.Count > 0 ? 1 : 0);
|
||||
|
||||
private void addUser(User user)
|
||||
{
|
||||
if (displayedCircles < NumberOfCircles)
|
||||
avatarFlow.Add(new CircularAvatar { User = user });
|
||||
}
|
||||
|
||||
private void removeUser(User user)
|
||||
{
|
||||
avatarFlow.RemoveAll(a => a.User == user);
|
||||
}
|
||||
|
||||
private void clearUsers()
|
||||
{
|
||||
avatarFlow.Clear();
|
||||
updateHiddenUsers();
|
||||
}
|
||||
|
||||
private void updateHiddenUsers()
|
||||
{
|
||||
int hiddenCount = 0;
|
||||
if (RecentParticipants.Count > NumberOfCircles)
|
||||
hiddenCount = ParticipantCount.Value - NumberOfCircles + 1;
|
||||
|
||||
hiddenUsers.Count = hiddenCount;
|
||||
|
||||
if (displayedCircles > NumberOfCircles)
|
||||
avatarFlow.Remove(avatarFlow.Last());
|
||||
else if (displayedCircles < NumberOfCircles)
|
||||
{
|
||||
var nextUser = RecentParticipants.FirstOrDefault(u => avatarFlow.All(a => a.User != u));
|
||||
if (nextUser != null) addUser(nextUser);
|
||||
}
|
||||
}
|
||||
|
||||
private class CircularAvatar : CompositeDrawable
|
||||
{
|
||||
public User User
|
||||
{
|
||||
get => avatar.User;
|
||||
set => avatar.User = value;
|
||||
}
|
||||
|
||||
private readonly UpdateableAvatar avatar = new UpdateableAvatar(showUsernameTooltip: true) { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
Size = new Vector2(avatar_size);
|
||||
|
||||
InternalChild = new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colours.Background5,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
avatar
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class HiddenUserCount : CompositeDrawable
|
||||
{
|
||||
public int Count
|
||||
{
|
||||
get => count;
|
||||
set
|
||||
{
|
||||
count = value;
|
||||
countText.Text = $"+{count}";
|
||||
|
||||
if (count > 0)
|
||||
Show();
|
||||
else
|
||||
Hide();
|
||||
}
|
||||
}
|
||||
|
||||
private int count;
|
||||
|
||||
private readonly SpriteText countText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.Default.With(weight: FontWeight.Bold),
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colours)
|
||||
{
|
||||
Size = new Vector2(avatar_size);
|
||||
Alpha = 0;
|
||||
|
||||
InternalChild = new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.Background5,
|
||||
},
|
||||
countText
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,86 +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.Collections.Generic;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public class RoomInfo : OnlinePlayComposite
|
||||
{
|
||||
private readonly List<Drawable> statusElements = new List<Drawable>();
|
||||
private readonly OsuTextFlowContainer roomName;
|
||||
|
||||
public RoomInfo()
|
||||
{
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
RoomLocalUserInfo localUserInfo;
|
||||
RoomStatusInfo statusInfo;
|
||||
ModeTypeInfo typeInfo;
|
||||
ParticipantInfo participantInfo;
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Spacing = new Vector2(0, 10),
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
roomName = new OsuTextFlowContainer(t => t.Font = OsuFont.GetFont(size: 30))
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
},
|
||||
participantInfo = new ParticipantInfo(),
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
statusInfo = new RoomStatusInfo(),
|
||||
typeInfo = new ModeTypeInfo
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight
|
||||
}
|
||||
}
|
||||
},
|
||||
localUserInfo = new RoomLocalUserInfo(),
|
||||
}
|
||||
};
|
||||
|
||||
statusElements.AddRange(new Drawable[]
|
||||
{
|
||||
statusInfo, typeInfo, participantInfo, localUserInfo
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
if (RoomID.Value == null)
|
||||
statusElements.ForEach(e => e.FadeOut());
|
||||
RoomID.BindValueChanged(id =>
|
||||
{
|
||||
if (id.NewValue == null)
|
||||
statusElements.ForEach(e => e.FadeOut(100));
|
||||
else
|
||||
statusElements.ForEach(e => e.FadeIn(100));
|
||||
}, true);
|
||||
RoomName.BindValueChanged(name =>
|
||||
{
|
||||
roomName.Text = name.NewValue ?? "No room selected";
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,91 +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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public class RoomInspector : OnlinePlayComposite
|
||||
{
|
||||
private const float transition_duration = 100;
|
||||
|
||||
private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 };
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
OverlinedHeader participantsHeader;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
Alpha = 0.25f
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = 30 },
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new RoomInfo
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding { Vertical = 60 },
|
||||
},
|
||||
participantsHeader = new OverlinedHeader("Recent Participants"),
|
||||
new ParticipantsDisplay(Direction.Vertical)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = ParticipantsList.TILE_SIZE * 3,
|
||||
Details = { BindTarget = participantsHeader.Details }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new Drawable[] { new OverlinedPlaylistHeader(), },
|
||||
new Drawable[]
|
||||
{
|
||||
new DrawableRoomPlaylist(false, false)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Items = { BindTarget = Playlist }
|
||||
},
|
||||
},
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public class RoomSpecialCategoryPill : OnlinePlayComposite
|
||||
{
|
||||
private SpriteText text;
|
||||
|
||||
public RoomSpecialCategoryPill()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
InternalChild = new PillContainer
|
||||
{
|
||||
Background =
|
||||
{
|
||||
Colour = colours.Pink,
|
||||
Alpha = 1
|
||||
},
|
||||
Child = text = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
|
||||
Colour = Color4.Black
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Category.BindValueChanged(c => text.Text = c.NewValue.ToString(), true);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// A pill that displays the room's current status.
|
||||
/// </summary>
|
||||
public class RoomStatusPill : OnlinePlayComposite
|
||||
{
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
private PillContainer pill;
|
||||
private SpriteText statusText;
|
||||
|
||||
public RoomStatusPill()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
InternalChild = pill = new PillContainer
|
||||
{
|
||||
Child = statusText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.SemiBold, size: 12),
|
||||
Colour = Color4.Black
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
EndDate.BindValueChanged(_ => updateDisplay());
|
||||
Status.BindValueChanged(_ => updateDisplay(), true);
|
||||
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
RoomStatus status = getDisplayStatus();
|
||||
|
||||
pill.Background.Alpha = 1;
|
||||
pill.Background.FadeColour(status.GetAppropriateColour(colours), 100);
|
||||
statusText.Text = status.Message;
|
||||
}
|
||||
|
||||
private RoomStatus getDisplayStatus()
|
||||
{
|
||||
if (EndDate.Value < DateTimeOffset.Now)
|
||||
return new RoomStatusEnded();
|
||||
|
||||
return Status.Value;
|
||||
}
|
||||
}
|
||||
}
|
@ -30,8 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
public IReadOnlyList<DrawableRoom> Rooms => roomFlow.FlowingChildren.Cast<DrawableRoom>().ToArray();
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private Bindable<FilterCriteria> filter { get; set; }
|
||||
public readonly Bindable<FilterCriteria> Filter = new Bindable<FilterCriteria>();
|
||||
|
||||
[Resolved]
|
||||
private Bindable<Room> selectedRoom { get; set; }
|
||||
@ -50,6 +49,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
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,
|
||||
@ -59,7 +61,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(2),
|
||||
Spacing = new Vector2(10),
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -71,7 +73,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
rooms.BindTo(roomManager.Rooms);
|
||||
|
||||
filter?.BindValueChanged(criteria => Filter(criteria.NewValue));
|
||||
Filter?.BindValueChanged(criteria => applyFilterCriteria(criteria.NewValue), true);
|
||||
|
||||
selectedRoom.BindValueChanged(selection =>
|
||||
{
|
||||
@ -82,7 +84,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
private void updateSelection() =>
|
||||
roomFlow.Children.ForEach(r => r.State = r.Room == selectedRoom.Value ? SelectionState.Selected : SelectionState.NotSelected);
|
||||
|
||||
public void Filter(FilterCriteria criteria)
|
||||
private void applyFilterCriteria(FilterCriteria criteria)
|
||||
{
|
||||
roomFlow.Children.ForEach(r =>
|
||||
{
|
||||
@ -123,7 +125,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
roomFlow.Add(new DrawableRoom(room));
|
||||
}
|
||||
|
||||
Filter(filter?.Value);
|
||||
applyFilterCriteria(Filter?.Value);
|
||||
|
||||
updateSelection();
|
||||
}
|
||||
@ -137,7 +139,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
roomFlow.Remove(toRemove);
|
||||
|
||||
selectedRoom.Value = null;
|
||||
// selection may have a lease due to being in a sub screen.
|
||||
if (!selectedRoom.Disabled)
|
||||
selectedRoom.Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -149,7 +153,8 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
selectedRoom.Value = null;
|
||||
if (!selectedRoom.Disabled)
|
||||
selectedRoom.Value = null;
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
@ -211,6 +216,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
private void selectNext(int direction)
|
||||
{
|
||||
if (selectedRoom.Disabled)
|
||||
return;
|
||||
|
||||
var visibleRooms = Rooms.AsEnumerable().Where(r => r.IsPresent);
|
||||
|
||||
Room room;
|
||||
|
@ -2,6 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
@ -9,15 +11,23 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
{
|
||||
@ -28,12 +38,14 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
|
||||
protected override UserActivity InitialActivity => new UserActivity.SearchingForLobby();
|
||||
|
||||
private readonly IBindable<bool> initialRoomsReceived = new Bindable<bool>();
|
||||
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
|
||||
protected Container<OsuButton> Buttons { get; } = new Container<OsuButton>
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
AutoSizeAxes = Axes.Both
|
||||
};
|
||||
|
||||
private FilterControl filter;
|
||||
private Container content;
|
||||
private LoadingLayer loadingLayer;
|
||||
protected ListingPollingComponent ListingPollingComponent { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
private Bindable<Room> selectedRoom { get; set; }
|
||||
@ -44,53 +56,118 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
[Resolved(CanBeNull = true)]
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IBindable<RulesetInfo> ruleset { get; set; }
|
||||
|
||||
[CanBeNull]
|
||||
private IDisposable joiningRoomOperation { get; set; }
|
||||
|
||||
private RoomsContainer roomsContainer;
|
||||
[CanBeNull]
|
||||
private LeasedBindable<Room> selectionLease;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private readonly Bindable<FilterCriteria> filter = new Bindable<FilterCriteria>(new FilterCriteria());
|
||||
private readonly IBindable<bool> operationInProgress = new Bindable<bool>();
|
||||
private readonly IBindable<bool> isIdle = new BindableBool();
|
||||
private LoadingLayer loadingLayer;
|
||||
private RoomsContainer roomsContainer;
|
||||
private SearchTextBox searchTextBox;
|
||||
private Dropdown<RoomStatusFilter> statusDropdown;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load([CanBeNull] IdleTracker idleTracker)
|
||||
{
|
||||
if (idleTracker != null)
|
||||
isIdle.BindTo(idleTracker.IsIdle);
|
||||
|
||||
OsuScrollContainer scrollContainer;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
content = new Container
|
||||
ListingPollingComponent = CreatePollingComponent().With(c => c.Filter.BindTarget = filter),
|
||||
loadingLayer = new LoadingLayer(true),
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
new Container
|
||||
Left = WaveOverlayContainer.WIDTH_PADDING,
|
||||
Right = WaveOverlayContainer.WIDTH_PADDING,
|
||||
},
|
||||
Child = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0.55f,
|
||||
Children = new Drawable[]
|
||||
new Dimension(GridSizeMode.Absolute, Header.HEIGHT),
|
||||
new Dimension(GridSizeMode.Absolute, 25),
|
||||
new Dimension(GridSizeMode.Absolute, 20)
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
scrollContainer = new OsuScrollContainer
|
||||
searchTextBox = new LoungeSearchTextBox
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.6f,
|
||||
},
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ScrollbarOverlapsContent = false,
|
||||
Padding = new MarginPadding(10),
|
||||
Child = roomsContainer = new RoomsContainer()
|
||||
Depth = float.MinValue, // Contained filters should appear over the top of rooms.
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Buttons.WithChild(CreateNewRoomButton().With(d =>
|
||||
{
|
||||
d.Anchor = Anchor.BottomLeft;
|
||||
d.Origin = Anchor.BottomLeft;
|
||||
d.Size = new Vector2(150, 37.5f);
|
||||
d.Action = () => Open();
|
||||
})),
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(10),
|
||||
ChildrenEnumerable = CreateFilterControls().Select(f => f.With(d =>
|
||||
{
|
||||
d.Anchor = Anchor.TopRight;
|
||||
d.Origin = Anchor.TopRight;
|
||||
}))
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
null,
|
||||
new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
scrollContainer = new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ScrollbarOverlapsContent = false,
|
||||
Child = roomsContainer = new RoomsContainer
|
||||
{
|
||||
Filter = { BindTarget = filter }
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
loadingLayer = new LoadingLayer(true),
|
||||
}
|
||||
},
|
||||
new RoomInspector
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0.45f,
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
filter = CreateFilterControl().With(d =>
|
||||
{
|
||||
d.RelativeSizeAxes = Axes.X;
|
||||
d.Height = 80;
|
||||
})
|
||||
};
|
||||
|
||||
// scroll selected room into view on selection.
|
||||
@ -106,37 +183,65 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
initialRoomsReceived.BindTo(RoomManager.InitialRoomsReceived);
|
||||
initialRoomsReceived.BindValueChanged(_ => updateLoadingLayer());
|
||||
searchTextBox.Current.BindValueChanged(_ => updateFilterDebounced());
|
||||
ruleset.BindValueChanged(_ => UpdateFilter());
|
||||
|
||||
isIdle.BindValueChanged(_ => updatePollingRate(this.IsCurrentScreen()), true);
|
||||
|
||||
if (ongoingOperationTracker != null)
|
||||
{
|
||||
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
|
||||
operationInProgress.BindValueChanged(_ => updateLoadingLayer(), true);
|
||||
operationInProgress.BindValueChanged(_ => updateLoadingLayer());
|
||||
}
|
||||
|
||||
ListingPollingComponent.InitialRoomsReceived.BindValueChanged(_ => updateLoadingLayer(), true);
|
||||
|
||||
updateFilter();
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
#region Filtering
|
||||
|
||||
content.Padding = new MarginPadding
|
||||
public void UpdateFilter() => Scheduler.AddOnce(updateFilter);
|
||||
|
||||
private ScheduledDelegate scheduledFilterUpdate;
|
||||
|
||||
private void updateFilterDebounced()
|
||||
{
|
||||
scheduledFilterUpdate?.Cancel();
|
||||
scheduledFilterUpdate = Scheduler.AddDelayed(UpdateFilter, 200);
|
||||
}
|
||||
|
||||
private void updateFilter()
|
||||
{
|
||||
scheduledFilterUpdate?.Cancel();
|
||||
filter.Value = CreateFilterCriteria();
|
||||
}
|
||||
|
||||
protected virtual FilterCriteria CreateFilterCriteria() => new FilterCriteria
|
||||
{
|
||||
SearchString = searchTextBox.Current.Value,
|
||||
Ruleset = ruleset.Value,
|
||||
Status = statusDropdown.Current.Value
|
||||
};
|
||||
|
||||
protected virtual IEnumerable<Drawable> CreateFilterControls()
|
||||
{
|
||||
statusDropdown = new SlimEnumDropdown<RoomStatusFilter>
|
||||
{
|
||||
Top = filter.DrawHeight,
|
||||
Left = WaveOverlayContainer.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH + HORIZONTAL_OVERFLOW_PADDING,
|
||||
Right = WaveOverlayContainer.WIDTH_PADDING + HORIZONTAL_OVERFLOW_PADDING,
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Width = 160,
|
||||
};
|
||||
|
||||
statusDropdown.Current.BindValueChanged(_ => UpdateFilter());
|
||||
|
||||
yield return statusDropdown;
|
||||
}
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
filter.TakeFocus();
|
||||
}
|
||||
#endregion
|
||||
|
||||
public override void OnEntering(IScreen last)
|
||||
{
|
||||
base.OnEntering(last);
|
||||
|
||||
onReturning();
|
||||
}
|
||||
|
||||
@ -144,6 +249,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
{
|
||||
base.OnResuming(last);
|
||||
|
||||
Debug.Assert(selectionLease != null);
|
||||
|
||||
selectionLease.Return();
|
||||
selectionLease = null;
|
||||
|
||||
if (selectedRoom.Value?.RoomID.Value == null)
|
||||
selectedRoom.Value = new Room();
|
||||
|
||||
@ -164,14 +274,21 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
base.OnSuspending(next);
|
||||
}
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
searchTextBox.TakeFocus();
|
||||
}
|
||||
|
||||
private void onReturning()
|
||||
{
|
||||
filter.HoldFocus = true;
|
||||
updatePollingRate(true);
|
||||
searchTextBox.HoldFocus = true;
|
||||
}
|
||||
|
||||
private void onLeaving()
|
||||
{
|
||||
filter.HoldFocus = false;
|
||||
updatePollingRate(false);
|
||||
searchTextBox.HoldFocus = false;
|
||||
|
||||
// ensure any password prompt is dismissed.
|
||||
this.HidePopover();
|
||||
@ -199,32 +316,63 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
/// <summary>
|
||||
/// Push a room as a new subscreen.
|
||||
/// </summary>
|
||||
public void Open(Room room) => Schedule(() =>
|
||||
/// <param name="room">An optional template to use when creating the room.</param>
|
||||
public void Open(Room room = null) => Schedule(() =>
|
||||
{
|
||||
// Handles the case where a room is clicked 3 times in quick succession
|
||||
if (!this.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
OpenNewRoom(room);
|
||||
OpenNewRoom(room ?? CreateNewRoom());
|
||||
});
|
||||
|
||||
protected virtual void OpenNewRoom(Room room)
|
||||
{
|
||||
selectedRoom.Value = room;
|
||||
selectionLease = selectedRoom.BeginLease(false);
|
||||
Debug.Assert(selectionLease != null);
|
||||
selectionLease.Value = room;
|
||||
|
||||
this.Push(CreateRoomSubScreen(room));
|
||||
}
|
||||
|
||||
protected abstract FilterControl CreateFilterControl();
|
||||
|
||||
protected abstract RoomSubScreen CreateRoomSubScreen(Room room);
|
||||
|
||||
private void updateLoadingLayer()
|
||||
{
|
||||
if (operationInProgress.Value || !initialRoomsReceived.Value)
|
||||
if (operationInProgress.Value || !ListingPollingComponent.InitialRoomsReceived.Value)
|
||||
loadingLayer.Show();
|
||||
else
|
||||
loadingLayer.Hide();
|
||||
}
|
||||
|
||||
private void updatePollingRate(bool isCurrentScreen)
|
||||
{
|
||||
if (!isCurrentScreen)
|
||||
ListingPollingComponent.TimeBetweenPolls.Value = 0;
|
||||
else
|
||||
ListingPollingComponent.TimeBetweenPolls.Value = isIdle.Value ? 120000 : 15000;
|
||||
|
||||
Logger.Log($"Polling adjusted (listing: {ListingPollingComponent.TimeBetweenPolls.Value})");
|
||||
}
|
||||
|
||||
protected abstract OsuButton CreateNewRoomButton();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new room.
|
||||
/// </summary>
|
||||
/// <returns>The created <see cref="Room"/>.</returns>
|
||||
protected abstract Room CreateNewRoom();
|
||||
|
||||
protected abstract RoomSubScreen CreateRoomSubScreen(Room room);
|
||||
|
||||
protected abstract ListingPollingComponent CreatePollingComponent();
|
||||
|
||||
private class LoungeSearchTextBox : SearchTextBox
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
BackgroundUnfocused = OsuColour.Gray(0.06f);
|
||||
BackgroundFocused = OsuColour.Gray(0.12f);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,40 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Match.Components
|
||||
{
|
||||
public abstract class CreateRoomButton : PurpleTriangleButton, IKeyBindingHandler<PlatformAction>
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
SpriteText.Font = SpriteText.Font.With(size: 14);
|
||||
Triangles.TriangleScale = 1.5f;
|
||||
}
|
||||
|
||||
public bool OnPressed(PlatformAction action)
|
||||
{
|
||||
if (!Enabled.Value)
|
||||
return false;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case PlatformAction.DocumentNew:
|
||||
// might as well also handle new tab. it's a bit of an undefined flow on this screen.
|
||||
case PlatformAction.TabNew:
|
||||
TriggerClick();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(PlatformAction action)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -4,15 +4,17 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Match.Components
|
||||
{
|
||||
public abstract class MatchSettingsOverlay : FocusedOverlayContainer
|
||||
public abstract class MatchSettingsOverlay : FocusedOverlayContainer, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
protected const float TRANSITION_DURATION = 350;
|
||||
protected const float FIELD_PADDING = 45;
|
||||
@ -21,6 +23,10 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
|
||||
|
||||
protected override bool BlockScrollInput => false;
|
||||
|
||||
protected abstract OsuButton SubmitButton { get; }
|
||||
|
||||
protected abstract bool IsLoading { get; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -29,18 +35,49 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
|
||||
Add(Settings = CreateSettings());
|
||||
}
|
||||
|
||||
protected abstract void SelectBeatmap();
|
||||
|
||||
protected abstract OnlinePlayComposite CreateSettings();
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
base.PopIn();
|
||||
Settings.MoveToY(0, TRANSITION_DURATION, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
base.PopOut();
|
||||
Settings.MoveToY(-1, TRANSITION_DURATION, Easing.InSine);
|
||||
}
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case GlobalAction.Select:
|
||||
if (IsLoading)
|
||||
return true;
|
||||
|
||||
if (SubmitButton.Enabled.Value)
|
||||
{
|
||||
SubmitButton.TriggerClick();
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
SelectBeatmap();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(GlobalAction action)
|
||||
{
|
||||
}
|
||||
|
||||
protected class SettingsTextBox : OsuTextBox
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -9,31 +9,27 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.GameTypes;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Match.Components
|
||||
{
|
||||
public class GameTypePicker : DisableableTabControl<GameType>
|
||||
public class MatchTypePicker : DisableableTabControl<MatchType>
|
||||
{
|
||||
private const float height = 40;
|
||||
private const float selection_width = 3;
|
||||
|
||||
protected override TabItem<GameType> CreateTabItem(GameType value) => new GameTypePickerItem(value);
|
||||
protected override TabItem<MatchType> CreateTabItem(MatchType value) => new GameTypePickerItem(value);
|
||||
|
||||
protected override Dropdown<GameType> CreateDropdown() => null;
|
||||
protected override Dropdown<MatchType> CreateDropdown() => null;
|
||||
|
||||
public GameTypePicker()
|
||||
public MatchTypePicker()
|
||||
{
|
||||
Height = height + selection_width * 2;
|
||||
TabContainer.Spacing = new Vector2(10 - selection_width * 2);
|
||||
|
||||
AddItem(new GameTypeTag());
|
||||
AddItem(new GameTypeVersus());
|
||||
AddItem(new GameTypeTagTeam());
|
||||
AddItem(new GameTypeTeamVersus());
|
||||
AddItem(new GameTypePlaylists());
|
||||
AddItem(MatchType.HeadToHead);
|
||||
AddItem(MatchType.TeamVersus);
|
||||
}
|
||||
|
||||
private class GameTypePickerItem : DisableableTabItem
|
||||
@ -42,7 +38,7 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
|
||||
|
||||
private readonly CircularContainer hover, selection;
|
||||
|
||||
public GameTypePickerItem(GameType value)
|
||||
public GameTypePickerItem(MatchType value)
|
||||
: base(value)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
@ -8,8 +8,10 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
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.Screens;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -17,6 +19,7 @@ using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Match
|
||||
{
|
||||
@ -61,8 +64,15 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
|
||||
protected RoomSubScreen()
|
||||
{
|
||||
Padding = new MarginPadding { Top = Header.HEIGHT };
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4Extensions.FromHex(@"3e3a44") // This is super temporary.
|
||||
},
|
||||
BeatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker
|
||||
{
|
||||
SelectedItem = { BindTarget = SelectedItem }
|
||||
@ -250,5 +260,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
private class UserModSelectOverlay : LocalPlayerModSelectOverlay
|
||||
{
|
||||
}
|
||||
|
||||
public class UserModSelectButton : PurpleTriangleButton
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
public class CreateMultiplayerMatchButton : PurpleTriangleButton
|
||||
public class CreateMultiplayerMatchButton : CreateRoomButton
|
||||
{
|
||||
private IBindable<bool> isConnected;
|
||||
private IBindable<bool> operationInProgress;
|
||||
@ -22,8 +22,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Triangles.TriangleScale = 1.5f;
|
||||
|
||||
Text = "Create room";
|
||||
|
||||
isConnected = multiplayerClient.IsConnected.GetBoundCopy();
|
||||
|
@ -0,0 +1,40 @@
|
||||
// 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 osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
public class GameplayMatchScoreDisplay : MatchScoreDisplay
|
||||
{
|
||||
public Bindable<bool> Expanded = new Bindable<bool>();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Scale = new Vector2(0.5f);
|
||||
|
||||
Expanded.BindValueChanged(expandedChanged, true);
|
||||
}
|
||||
|
||||
private void expandedChanged(ValueChangedEvent<bool> expanded)
|
||||
{
|
||||
if (expanded.NewValue)
|
||||
{
|
||||
Score1Text.FadeIn(500, Easing.OutQuint);
|
||||
Score2Text.FadeIn(500, Easing.OutQuint);
|
||||
this.ResizeWidthTo(2, 500, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
Score1Text.FadeOut(500, Easing.OutQuint);
|
||||
Score2Text.FadeOut(500, Easing.OutQuint);
|
||||
this.ResizeWidthTo(1, 500, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -47,7 +47,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Text = "Select beatmap",
|
||||
Action = () => matchSubScreen.Push(new MultiplayerMatchSongSelect()),
|
||||
Action = () =>
|
||||
{
|
||||
if (matchSubScreen.IsCurrentScreen())
|
||||
matchSubScreen.Push(new MultiplayerMatchSongSelect());
|
||||
},
|
||||
Alpha = 0
|
||||
}
|
||||
}
|
||||
@ -68,6 +72,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
}, true);
|
||||
}
|
||||
|
||||
public void BeginSelection() => selectButton.TriggerClick();
|
||||
|
||||
private void updateBeatmap()
|
||||
{
|
||||
if (SelectedItem.Value == null)
|
||||
|
@ -6,6 +6,7 @@ using System.Diagnostics;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.ExceptionExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
@ -27,8 +28,19 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
public class MultiplayerMatchSettingsOverlay : MatchSettingsOverlay
|
||||
{
|
||||
private MatchSettings settings;
|
||||
|
||||
protected override OsuButton SubmitButton => settings.ApplyButton;
|
||||
|
||||
[Resolved]
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||
|
||||
protected override bool IsLoading => ongoingOperationTracker.InProgress.Value;
|
||||
|
||||
protected override void SelectBeatmap() => settings.SelectBeatmap();
|
||||
|
||||
protected override OnlinePlayComposite CreateSettings()
|
||||
=> new MatchSettings
|
||||
=> settings = new MatchSettings
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativePositionAxes = Axes.Y,
|
||||
@ -43,7 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
|
||||
public OsuTextBox NameField, MaxParticipantsField;
|
||||
public RoomAvailabilityPicker AvailabilityPicker;
|
||||
public GameTypePicker TypePicker;
|
||||
public MatchTypePicker TypePicker;
|
||||
public OsuTextBox PasswordTextBox;
|
||||
public TriangleButton ApplyButton;
|
||||
|
||||
@ -53,6 +65,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
private LoadingLayer loadingLayer;
|
||||
private BeatmapSelectionControl initialBeatmapControl;
|
||||
|
||||
public void SelectBeatmap() => initialBeatmapControl.BeginSelection();
|
||||
|
||||
[Resolved]
|
||||
private IRoomManager manager { get; set; }
|
||||
|
||||
@ -149,7 +163,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
},
|
||||
new Section("Game type")
|
||||
{
|
||||
Alpha = disabled_alpha,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
@ -158,10 +171,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
Spacing = new Vector2(7),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
TypePicker = new GameTypePicker
|
||||
TypePicker = new MatchTypePicker
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Enabled = { Value = false }
|
||||
},
|
||||
typeLabel = new OsuSpriteText
|
||||
{
|
||||
@ -267,7 +279,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
loadingLayer = new LoadingLayer(true)
|
||||
};
|
||||
|
||||
TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue?.Name ?? string.Empty, true);
|
||||
TypePicker.Current.BindValueChanged(type => typeLabel.Text = type.NewValue.GetLocalisableDescription(), true);
|
||||
RoomName.BindValueChanged(name => NameField.Text = name.NewValue, true);
|
||||
Availability.BindValueChanged(availability => AvailabilityPicker.Current.Value = availability.NewValue, true);
|
||||
Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true);
|
||||
@ -306,7 +318,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
// Otherwise, update the room directly in preparation for it to be submitted to the API on match creation.
|
||||
if (client.Room != null)
|
||||
{
|
||||
client.ChangeSettings(name: NameField.Text, password: PasswordTextBox.Text).ContinueWith(t => Schedule(() =>
|
||||
client.ChangeSettings(name: NameField.Text, password: PasswordTextBox.Text, matchType: TypePicker.Current.Value).ContinueWith(t => Schedule(() =>
|
||||
{
|
||||
if (t.IsCompletedSuccessfully)
|
||||
onSuccess(currentRoom.Value);
|
||||
|
@ -99,7 +99,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
break;
|
||||
}
|
||||
|
||||
bool enableButton = Client.Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value;
|
||||
bool enableButton = Room?.State == MultiplayerRoomState.Open && !operationInProgress.Value;
|
||||
|
||||
// When the local user is the host and spectating the match, the "start match" state should be enabled if any users are ready.
|
||||
if (localUser?.State == MultiplayerUserState.Spectating)
|
||||
|
@ -2,11 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
|
||||
@ -25,48 +22,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
client.ChangeState(MultiplayerUserState.Idle);
|
||||
}
|
||||
|
||||
protected override void UpdatePollingRate(bool isIdle)
|
||||
{
|
||||
var multiplayerRoomManager = (MultiplayerRoomManager)RoomManager;
|
||||
|
||||
if (!this.IsCurrentScreen())
|
||||
{
|
||||
multiplayerRoomManager.TimeBetweenListingPolls.Value = 0;
|
||||
multiplayerRoomManager.TimeBetweenSelectionPolls.Value = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (CurrentSubScreen)
|
||||
{
|
||||
case LoungeSubScreen _:
|
||||
multiplayerRoomManager.TimeBetweenListingPolls.Value = isIdle ? 120000 : 15000;
|
||||
multiplayerRoomManager.TimeBetweenSelectionPolls.Value = isIdle ? 120000 : 15000;
|
||||
break;
|
||||
|
||||
// Don't poll inside the match or anywhere else.
|
||||
default:
|
||||
multiplayerRoomManager.TimeBetweenListingPolls.Value = 0;
|
||||
multiplayerRoomManager.TimeBetweenSelectionPolls.Value = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Log($"Polling adjusted (listing: {multiplayerRoomManager.TimeBetweenListingPolls.Value}, selection: {multiplayerRoomManager.TimeBetweenSelectionPolls.Value})");
|
||||
}
|
||||
|
||||
protected override Room CreateNewRoom() =>
|
||||
new Room
|
||||
{
|
||||
Name = { Value = $"{API.LocalUser}'s awesome room" },
|
||||
Category = { Value = RoomCategory.Realtime }
|
||||
};
|
||||
|
||||
protected override string ScreenTitle => "Multiplayer";
|
||||
|
||||
protected override RoomManager CreateRoomManager() => new MultiplayerRoomManager();
|
||||
|
||||
protected override LoungeSubScreen CreateLounge() => new MultiplayerLoungeSubScreen();
|
||||
|
||||
protected override OsuButton CreateNewMultiplayerGameButton() => new CreateMultiplayerMatchButton();
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +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 osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
public class MultiplayerFilterControl : FilterControl
|
||||
{
|
||||
protected override FilterCriteria CreateCriteria()
|
||||
{
|
||||
var criteria = base.CreateCriteria();
|
||||
criteria.Category = "realtime";
|
||||
return criteria;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,10 +1,16 @@
|
||||
// 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.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
@ -13,13 +19,45 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
public class MultiplayerLoungeSubScreen : LoungeSubScreen
|
||||
{
|
||||
protected override FilterControl CreateFilterControl() => new MultiplayerFilterControl();
|
||||
|
||||
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room);
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; }
|
||||
|
||||
public override void OnResuming(IScreen last)
|
||||
{
|
||||
base.OnResuming(last);
|
||||
|
||||
// Upon having left a room, we don't know whether we were the only participant, and whether the room is now closed as a result of leaving it.
|
||||
// To work around this, temporarily remove the room and trigger an immediate listing poll.
|
||||
if (last is MultiplayerMatchSubScreen match)
|
||||
{
|
||||
RoomManager.RemoveRoom(match.Room);
|
||||
ListingPollingComponent.PollImmediately();
|
||||
}
|
||||
}
|
||||
|
||||
protected override FilterCriteria CreateFilterCriteria()
|
||||
{
|
||||
var criteria = base.CreateFilterCriteria();
|
||||
criteria.Category = @"realtime";
|
||||
return criteria;
|
||||
}
|
||||
|
||||
protected override OsuButton CreateNewRoomButton() => new CreateMultiplayerMatchButton();
|
||||
|
||||
protected override Room CreateNewRoom() => new Room
|
||||
{
|
||||
Name = { Value = $"{api.LocalUser}'s awesome room" },
|
||||
Category = { Value = RoomCategory.Realtime },
|
||||
Type = { Value = MatchType.HeadToHead },
|
||||
};
|
||||
|
||||
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new MultiplayerMatchSubScreen(room);
|
||||
|
||||
protected override ListingPollingComponent CreatePollingComponent() => new MultiplayerListingPollingComponent();
|
||||
|
||||
protected override void OpenNewRoom(Room room)
|
||||
{
|
||||
if (client?.IsConnected.Value != true)
|
||||
@ -30,5 +68,32 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
base.OpenNewRoom(room);
|
||||
}
|
||||
|
||||
private class MultiplayerListingPollingComponent : ListingPollingComponent
|
||||
{
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; }
|
||||
|
||||
private readonly IBindable<bool> isConnected = new Bindable<bool>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
isConnected.BindTo(client.IsConnected);
|
||||
isConnected.BindValueChanged(c => Scheduler.AddOnce(() =>
|
||||
{
|
||||
if (isConnected.Value && IsLoaded)
|
||||
PollImmediately();
|
||||
}), true);
|
||||
}
|
||||
|
||||
protected override Task Poll()
|
||||
{
|
||||
if (!isConnected.Value)
|
||||
return Task.CompletedTask;
|
||||
|
||||
return base.Poll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -42,6 +42,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
public override string ShortTitle => "room";
|
||||
|
||||
public readonly Room Room;
|
||||
|
||||
[Resolved]
|
||||
private MultiplayerClient client { get; set; }
|
||||
|
||||
@ -49,9 +51,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
private OngoingOperationTracker ongoingOperationTracker { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private Bindable<Room> currentRoom { get; set; }
|
||||
|
||||
private MultiplayerMatchSettingsOverlay settingsOverlay;
|
||||
private Bindable<Room> currentRoom { get; set; } // Todo: This should not exist.
|
||||
|
||||
private readonly IBindable<bool> isConnected = new Bindable<bool>();
|
||||
|
||||
@ -59,9 +59,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
private IDisposable readyClickOperation;
|
||||
|
||||
private GridContainer mainContent;
|
||||
private MultiplayerMatchSettingsOverlay settingsOverlay;
|
||||
|
||||
public MultiplayerMatchSubScreen(Room room)
|
||||
{
|
||||
Room = room;
|
||||
|
||||
Title = room.RoomID.Value == null ? "New room" : room.Name.Value;
|
||||
Activity.Value = new UserActivity.InLobby(room);
|
||||
}
|
||||
@ -176,7 +179,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PurpleTriangleButton
|
||||
new UserModSelectButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
@ -274,7 +277,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
isConnected.BindValueChanged(connected =>
|
||||
{
|
||||
if (!connected.NewValue)
|
||||
Schedule(this.Exit);
|
||||
handleRoomLost();
|
||||
}, true);
|
||||
|
||||
currentRoom.BindValueChanged(room =>
|
||||
@ -284,7 +287,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
// the room has gone away.
|
||||
// this could mean something happened during the join process, or an external connection issue occurred.
|
||||
// one specific scenario is where the underlying room is created, but the signalr server returns an error during the join process. this triggers a PartRoom operation (see https://github.com/ppy/osu/blob/7654df94f6f37b8382be7dfcb4f674e03bd35427/osu.Game/Screens/OnlinePlay/Multiplayer/MultiplayerRoomManager.cs#L97)
|
||||
Schedule(this.Exit);
|
||||
handleRoomLost();
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
@ -448,9 +451,24 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
private void onRoomUpdated()
|
||||
{
|
||||
// may happen if the client is kicked or otherwise removed from the room.
|
||||
if (client.Room == null)
|
||||
{
|
||||
handleRoomLost();
|
||||
return;
|
||||
}
|
||||
|
||||
Scheduler.AddOnce(UpdateMods);
|
||||
}
|
||||
|
||||
private void handleRoomLost() => Schedule(() =>
|
||||
{
|
||||
if (this.IsCurrentScreen())
|
||||
this.Exit();
|
||||
else
|
||||
ValidForResume = false;
|
||||
});
|
||||
|
||||
private void onLoadRequested()
|
||||
{
|
||||
if (BeatmapAvailability.Value.State != DownloadState.LocallyAvailable)
|
||||
@ -475,16 +493,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
protected override Screen CreateGameplayScreen()
|
||||
{
|
||||
Debug.Assert(client.LocalUser != null);
|
||||
Debug.Assert(client.Room != null);
|
||||
|
||||
int[] userIds = client.CurrentMatchPlayingUserIds.ToArray();
|
||||
MultiplayerRoomUser[] users = userIds.Select(id => client.Room.Users.First(u => u.UserID == id)).ToArray();
|
||||
|
||||
switch (client.LocalUser.State)
|
||||
{
|
||||
case MultiplayerUserState.Spectating:
|
||||
return new MultiSpectatorScreen(userIds);
|
||||
return new MultiSpectatorScreen(users.Take(PlayerGrid.MAX_PLAYERS).ToArray());
|
||||
|
||||
default:
|
||||
return new PlayerLoader(() => new MultiplayerPlayer(SelectedItem.Value, userIds));
|
||||
return new PlayerLoader(() => new MultiplayerPlayer(SelectedItem.Value, users));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3,9 +3,12 @@
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
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.Logging;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
@ -34,16 +37,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
private MultiplayerGameplayLeaderboard leaderboard;
|
||||
|
||||
private readonly int[] userIds;
|
||||
private readonly MultiplayerRoomUser[] users;
|
||||
|
||||
private LoadingLayer loadingDisplay;
|
||||
private FillFlowContainer leaderboardFlow;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a multiplayer player.
|
||||
/// </summary>
|
||||
/// <param name="playlistItem">The playlist item to be played.</param>
|
||||
/// <param name="userIds">The users which are participating in this game.</param>
|
||||
public MultiplayerPlayer(PlaylistItem playlistItem, int[] userIds)
|
||||
/// <param name="users">The users which are participating in this game.</param>
|
||||
public MultiplayerPlayer(PlaylistItem playlistItem, MultiplayerRoomUser[] users)
|
||||
: base(playlistItem, new PlayerConfiguration
|
||||
{
|
||||
AllowPause = false,
|
||||
@ -51,14 +55,41 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
AllowSkipping = false,
|
||||
})
|
||||
{
|
||||
this.userIds = userIds;
|
||||
this.users = users;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
if (!LoadedBeatmapSuccessfully)
|
||||
return;
|
||||
|
||||
HUDOverlay.Add(leaderboardFlow = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
});
|
||||
|
||||
// todo: this should be implemented via a custom HUD implementation, and correctly masked to the main content area.
|
||||
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, userIds), HUDOverlay.Add);
|
||||
LoadComponentAsync(leaderboard = new MultiplayerGameplayLeaderboard(ScoreProcessor, users), l =>
|
||||
{
|
||||
if (!LoadedBeatmapSuccessfully)
|
||||
return;
|
||||
|
||||
((IBindable<bool>)leaderboard.Expanded).BindTo(HUDOverlay.ShowHud);
|
||||
|
||||
leaderboardFlow.Add(l);
|
||||
|
||||
if (leaderboard.TeamScores.Count >= 2)
|
||||
{
|
||||
LoadComponentAsync(new GameplayMatchScoreDisplay
|
||||
{
|
||||
Team1Score = { BindTarget = leaderboard.TeamScores.First().Value },
|
||||
Team2Score = { BindTarget = leaderboard.TeamScores.Last().Value },
|
||||
Expanded = { BindTarget = HUDOverlay.ShowHud },
|
||||
}, leaderboardFlow.Add);
|
||||
}
|
||||
});
|
||||
|
||||
HUDOverlay.Add(loadingDisplay = new LoadingLayer(true) { Depth = float.MaxValue });
|
||||
}
|
||||
@ -67,6 +98,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
base.LoadAsyncComplete();
|
||||
|
||||
if (!LoadedBeatmapSuccessfully)
|
||||
return;
|
||||
|
||||
if (!ValidForResume)
|
||||
return; // token retrieval may have failed.
|
||||
|
||||
@ -92,13 +126,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
Debug.Assert(client.Room != null);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
((IBindable<bool>)leaderboard.Expanded).BindTo(IsBreakTime);
|
||||
}
|
||||
|
||||
protected override void StartGameplay()
|
||||
{
|
||||
// block base call, but let the server know we are ready to start.
|
||||
@ -118,6 +145,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (!LoadedBeatmapSuccessfully)
|
||||
return;
|
||||
|
||||
adjustLeaderboardPosition();
|
||||
}
|
||||
|
||||
@ -125,7 +156,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
const float padding = 44; // enough margin to avoid the hit error display.
|
||||
|
||||
leaderboard.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
|
||||
leaderboardFlow.Position = new Vector2(padding, padding + HUDOverlay.TopScoringElementsHeight);
|
||||
}
|
||||
|
||||
private void onMatchStarted() => Scheduler.Add(() =>
|
||||
@ -150,7 +181,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
protected override ResultsScreen CreateResults(ScoreInfo score)
|
||||
{
|
||||
Debug.Assert(RoomId.Value != null);
|
||||
return new MultiplayerResultsScreen(score, RoomId.Value.Value, PlaylistItem);
|
||||
return leaderboard.TeamScores.Count == 2
|
||||
? new MultiplayerTeamResultsScreen(score, RoomId.Value.Value, PlaylistItem, leaderboard.TeamScores)
|
||||
: new MultiplayerResultsScreen(score, RoomId.Value.Value, PlaylistItem);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -2,11 +2,8 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ExceptionExtensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
@ -21,22 +18,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
[Resolved]
|
||||
private MultiplayerClient multiplayerClient { get; set; }
|
||||
|
||||
public readonly Bindable<double> TimeBetweenListingPolls = new Bindable<double>();
|
||||
public readonly Bindable<double> TimeBetweenSelectionPolls = new Bindable<double>();
|
||||
private readonly IBindable<bool> isConnected = new Bindable<bool>();
|
||||
private readonly Bindable<bool> allowPolling = new Bindable<bool>();
|
||||
|
||||
private ListingPollingComponent listingPollingComponent;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
isConnected.BindTo(multiplayerClient.IsConnected);
|
||||
isConnected.BindValueChanged(_ => Scheduler.AddOnce(updatePolling));
|
||||
JoinedRoom.BindValueChanged(_ => Scheduler.AddOnce(updatePolling), true);
|
||||
}
|
||||
|
||||
public override void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
=> base.CreateRoom(room, r => joinMultiplayerRoom(r, r.Password.Value, onSuccess, onError), onError);
|
||||
|
||||
@ -64,19 +45,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
if (JoinedRoom.Value == null)
|
||||
return;
|
||||
|
||||
var joinedRoom = JoinedRoom.Value;
|
||||
|
||||
base.PartRoom();
|
||||
|
||||
multiplayerClient.LeaveRoom();
|
||||
|
||||
// Todo: This is not the way to do this. Basically when we're the only participant and the room closes, there's no way to know if this is actually the case.
|
||||
// This is delayed one frame because upon exiting the match subscreen, multiplayer updates the polling rate and messes with polling.
|
||||
Schedule(() =>
|
||||
{
|
||||
RemoveRoom(joinedRoom);
|
||||
listingPollingComponent.PollImmediately();
|
||||
});
|
||||
}
|
||||
|
||||
private void joinMultiplayerRoom(Room room, string password, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
@ -99,70 +69,5 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void updatePolling()
|
||||
{
|
||||
if (!isConnected.Value)
|
||||
ClearRooms();
|
||||
|
||||
// Don't poll when not connected or when a room has been joined.
|
||||
allowPolling.Value = isConnected.Value && JoinedRoom.Value == null;
|
||||
}
|
||||
|
||||
protected override IEnumerable<RoomPollingComponent> CreatePollingComponents() => new RoomPollingComponent[]
|
||||
{
|
||||
listingPollingComponent = new MultiplayerListingPollingComponent
|
||||
{
|
||||
TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls },
|
||||
AllowPolling = { BindTarget = allowPolling }
|
||||
},
|
||||
new MultiplayerSelectionPollingComponent
|
||||
{
|
||||
TimeBetweenPolls = { BindTarget = TimeBetweenSelectionPolls },
|
||||
AllowPolling = { BindTarget = allowPolling }
|
||||
}
|
||||
};
|
||||
|
||||
private class MultiplayerListingPollingComponent : ListingPollingComponent
|
||||
{
|
||||
public readonly IBindable<bool> AllowPolling = new Bindable<bool>();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AllowPolling.BindValueChanged(allowPolling =>
|
||||
{
|
||||
if (!allowPolling.NewValue)
|
||||
return;
|
||||
|
||||
if (IsLoaded)
|
||||
PollImmediately();
|
||||
});
|
||||
}
|
||||
|
||||
protected override Task Poll() => !AllowPolling.Value ? Task.CompletedTask : base.Poll();
|
||||
}
|
||||
|
||||
private class MultiplayerSelectionPollingComponent : SelectionPollingComponent
|
||||
{
|
||||
public readonly IBindable<bool> AllowPolling = new Bindable<bool>();
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
AllowPolling.BindValueChanged(allowPolling =>
|
||||
{
|
||||
if (!allowPolling.NewValue)
|
||||
return;
|
||||
|
||||
if (IsLoaded)
|
||||
PollImmediately();
|
||||
});
|
||||
}
|
||||
|
||||
protected override Task Poll() => !AllowPolling.Value ? Task.CompletedTask : base.Poll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,152 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
{
|
||||
public class MultiplayerTeamResultsScreen : MultiplayerResultsScreen
|
||||
{
|
||||
private readonly SortedDictionary<int, BindableInt> teamScores;
|
||||
|
||||
private Container winnerBackground;
|
||||
private Drawable winnerText;
|
||||
|
||||
public MultiplayerTeamResultsScreen(ScoreInfo score, long roomId, PlaylistItem playlistItem, SortedDictionary<int, BindableInt> teamScores)
|
||||
: base(score, roomId, playlistItem)
|
||||
{
|
||||
if (teamScores.Count != 2)
|
||||
throw new NotSupportedException(@"This screen currently only supports 2 teams");
|
||||
|
||||
this.teamScores = teamScores;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
const float winner_background_half_height = 250;
|
||||
|
||||
VerticalScrollContent.Anchor = VerticalScrollContent.Origin = Anchor.TopCentre;
|
||||
VerticalScrollContent.Scale = new Vector2(0.9f);
|
||||
VerticalScrollContent.Y = 75;
|
||||
|
||||
var redScore = teamScores.First().Value;
|
||||
var blueScore = teamScores.Last().Value;
|
||||
|
||||
LocalisableString winner;
|
||||
Colour4 winnerColour;
|
||||
|
||||
int comparison = redScore.Value.CompareTo(blueScore.Value);
|
||||
|
||||
if (comparison < 0)
|
||||
{
|
||||
// team name should eventually be coming from the multiplayer match state.
|
||||
winner = MultiplayerTeamResultsScreenStrings.TeamWins(@"Blue");
|
||||
winnerColour = colours.TeamColourBlue;
|
||||
}
|
||||
else if (comparison > 0)
|
||||
{
|
||||
// team name should eventually be coming from the multiplayer match state.
|
||||
winner = MultiplayerTeamResultsScreenStrings.TeamWins(@"Red");
|
||||
winnerColour = colours.TeamColourRed;
|
||||
}
|
||||
else
|
||||
{
|
||||
winner = MultiplayerTeamResultsScreenStrings.TheTeamsAreTied;
|
||||
winnerColour = Colour4.White.Opacity(0.5f);
|
||||
}
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new MatchScoreDisplay
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Team1Score = { BindTarget = redScore },
|
||||
Team2Score = { BindTarget = blueScore },
|
||||
},
|
||||
winnerBackground = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0,
|
||||
Children = new[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = winner_background_half_height,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Colour = ColourInfo.GradientVertical(Colour4.Black.Opacity(0), Colour4.Black.Opacity(0.4f))
|
||||
},
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = winner_background_half_height,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Colour = ColourInfo.GradientVertical(Colour4.Black.Opacity(0.4f), Colour4.Black.Opacity(0))
|
||||
}
|
||||
}
|
||||
},
|
||||
(winnerText = new OsuSpriteText
|
||||
{
|
||||
Alpha = 0,
|
||||
Font = OsuFont.Torus.With(size: 80, weight: FontWeight.Bold),
|
||||
Text = winner,
|
||||
Blending = BlendingParameters.Additive
|
||||
}).WithEffect(new GlowEffect
|
||||
{
|
||||
Colour = winnerColour,
|
||||
}).With(e =>
|
||||
{
|
||||
e.Anchor = Anchor.Centre;
|
||||
e.Origin = Anchor.Centre;
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
using (BeginDelayedSequence(300))
|
||||
{
|
||||
const double fade_in_duration = 600;
|
||||
|
||||
winnerText.FadeInFromZero(fade_in_duration, Easing.InQuint);
|
||||
winnerBackground.FadeInFromZero(fade_in_duration, Easing.InQuint);
|
||||
|
||||
winnerText
|
||||
.ScaleTo(10)
|
||||
.ScaleTo(1, 600, Easing.InQuad)
|
||||
.Then()
|
||||
.ScaleTo(1.02f, 1600, Easing.OutQuint)
|
||||
.FadeOut(5000, Easing.InQuad);
|
||||
winnerBackground.Delay(2200).FadeOut(2000);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -37,10 +37,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
private SpriteIcon crown;
|
||||
|
||||
private OsuSpriteText userRankText;
|
||||
private ModDisplay userModsDisplay;
|
||||
private StateDisplay userStateDisplay;
|
||||
|
||||
private IconButton kickButton;
|
||||
|
||||
public ParticipantPanel(MultiplayerRoomUser user)
|
||||
{
|
||||
User = user;
|
||||
@ -56,99 +59,117 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
|
||||
var backgroundColour = Color4Extensions.FromHex("#33413C");
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChild = new GridContainer
|
||||
{
|
||||
crown = new SpriteIcon
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Icon = FontAwesome.Solid.Crown,
|
||||
Size = new Vector2(14),
|
||||
Colour = Color4Extensions.FromHex("#F7E65D"),
|
||||
Alpha = 0
|
||||
new Dimension(GridSizeMode.Absolute, 18),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
new Container
|
||||
Content = new[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Left = 24 },
|
||||
Child = new Container
|
||||
new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
Children = new Drawable[]
|
||||
crown = new SpriteIcon
|
||||
{
|
||||
new Box
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Icon = FontAwesome.Solid.Crown,
|
||||
Size = new Vector2(14),
|
||||
Colour = Color4Extensions.FromHex("#F7E65D"),
|
||||
Alpha = 0
|
||||
},
|
||||
new TeamDisplay(User),
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = backgroundColour
|
||||
},
|
||||
new UserCoverBackground
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0.75f,
|
||||
User = user,
|
||||
Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White.Opacity(0.25f))
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(10),
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
new Box
|
||||
{
|
||||
new UpdateableAvatar
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = backgroundColour
|
||||
},
|
||||
new UserCoverBackground
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0.75f,
|
||||
User = user,
|
||||
Colour = ColourInfo.GradientHorizontal(Color4.White.Opacity(0), Color4.White.Opacity(0.25f))
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(10),
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fit,
|
||||
User = user
|
||||
},
|
||||
new UpdateableFlag
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(30, 20),
|
||||
Country = user?.Country
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18),
|
||||
Text = user?.Username
|
||||
},
|
||||
userRankText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Font = OsuFont.GetFont(size: 14),
|
||||
new UpdateableAvatar
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fit,
|
||||
User = user
|
||||
},
|
||||
new UpdateableFlag
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(30, 20),
|
||||
Country = user?.Country
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 18),
|
||||
Text = user?.Username
|
||||
},
|
||||
userRankText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Font = OsuFont.GetFont(size: 14),
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Right = 70 },
|
||||
Child = userModsDisplay = new ModDisplay
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Scale = new Vector2(0.5f),
|
||||
ExpansionMode = ExpansionMode.AlwaysContracted,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Margin = new MarginPadding { Right = 70 },
|
||||
Child = userModsDisplay = new ModDisplay
|
||||
{
|
||||
Scale = new Vector2(0.5f),
|
||||
ExpansionMode = ExpansionMode.AlwaysContracted,
|
||||
}
|
||||
},
|
||||
userStateDisplay = new StateDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Margin = new MarginPadding { Right = 10 },
|
||||
}
|
||||
},
|
||||
userStateDisplay = new StateDisplay
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Margin = new MarginPadding { Right = 10 },
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
kickButton = new KickButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding(4),
|
||||
Action = () => Client.KickUser(User.UserID),
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -157,7 +178,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
{
|
||||
base.OnRoomUpdated();
|
||||
|
||||
if (Room == null)
|
||||
if (Room == null || Client.LocalUser == null)
|
||||
return;
|
||||
|
||||
const double fade_time = 50;
|
||||
@ -169,6 +190,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
|
||||
userStateDisplay.UpdateStatus(User.State, User.BeatmapAvailability);
|
||||
|
||||
if (Client.IsHost && !User.Equals(Client.LocalUser))
|
||||
kickButton.FadeIn(fade_time);
|
||||
else
|
||||
kickButton.FadeOut(fade_time);
|
||||
|
||||
if (Room.Host?.Equals(User) == true)
|
||||
crown.FadeIn(fade_time);
|
||||
else
|
||||
@ -201,13 +227,36 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
new OsuMenuItem("Give host", MenuItemType.Standard, () =>
|
||||
{
|
||||
// Ensure the local user is still host.
|
||||
if (Room.Host?.UserID != api.LocalUser.Value.Id)
|
||||
if (!Client.IsHost)
|
||||
return;
|
||||
|
||||
Client.TransferHost(targetUser);
|
||||
}),
|
||||
new OsuMenuItem("Kick", MenuItemType.Destructive, () =>
|
||||
{
|
||||
// Ensure the local user is still host.
|
||||
if (!Client.IsHost)
|
||||
return;
|
||||
|
||||
Client.KickUser(targetUser);
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class KickButton : IconButton
|
||||
{
|
||||
public KickButton()
|
||||
{
|
||||
Icon = FontAwesome.Solid.UserTimes;
|
||||
TooltipText = "Kick";
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
IconHoverColour = colours.Red;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,131 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Participants
|
||||
{
|
||||
internal class TeamDisplay : MultiplayerRoomComposite
|
||||
{
|
||||
private readonly MultiplayerRoomUser user;
|
||||
|
||||
private Drawable box;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
|
||||
public TeamDisplay(MultiplayerRoomUser user)
|
||||
{
|
||||
this.user = user;
|
||||
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Width = 15;
|
||||
|
||||
Margin = new MarginPadding { Horizontal = 3 };
|
||||
|
||||
Alpha = 0;
|
||||
Scale = new Vector2(0, 1);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
box = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CornerRadius = 5,
|
||||
Masking = true,
|
||||
Scale = new Vector2(0, 1),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Child = new Box
|
||||
{
|
||||
Colour = Color4.White,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
}
|
||||
};
|
||||
|
||||
if (Client.LocalUser?.Equals(user) == true)
|
||||
{
|
||||
InternalChild = new OsuClickableContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
TooltipText = "Change team",
|
||||
Action = changeTeam,
|
||||
Child = box
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
InternalChild = box;
|
||||
}
|
||||
}
|
||||
|
||||
private void changeTeam()
|
||||
{
|
||||
Client.SendMatchRequest(new ChangeTeamRequest
|
||||
{
|
||||
TeamID = ((Client.LocalUser?.MatchState as TeamVersusUserState)?.TeamID + 1) % 2 ?? 0,
|
||||
});
|
||||
}
|
||||
|
||||
private int? displayedTeam;
|
||||
|
||||
protected override void OnRoomUpdated()
|
||||
{
|
||||
base.OnRoomUpdated();
|
||||
|
||||
// we don't have a way of knowing when an individual user's state has updated, so just handle on RoomUpdated for now.
|
||||
|
||||
var userRoomState = Room?.Users.FirstOrDefault(u => u.Equals(user))?.MatchState;
|
||||
|
||||
const double duration = 400;
|
||||
|
||||
int? newTeam = (userRoomState as TeamVersusUserState)?.TeamID;
|
||||
|
||||
if (newTeam == displayedTeam)
|
||||
return;
|
||||
|
||||
displayedTeam = newTeam;
|
||||
|
||||
if (displayedTeam != null)
|
||||
{
|
||||
box.FadeColour(getColourForTeam(displayedTeam.Value), duration, Easing.OutQuint);
|
||||
box.ScaleTo(new Vector2(box.Scale.X < 0 ? 1 : -1, 1), duration, Easing.OutQuint);
|
||||
|
||||
this.ScaleTo(Vector2.One, duration, Easing.OutQuint);
|
||||
this.FadeIn(duration);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.ScaleTo(new Vector2(0, 1), duration, Easing.OutQuint);
|
||||
this.FadeOut(duration);
|
||||
}
|
||||
}
|
||||
|
||||
private ColourInfo getColourForTeam(int id)
|
||||
{
|
||||
switch (id)
|
||||
{
|
||||
default:
|
||||
return colours.Red;
|
||||
|
||||
case 1:
|
||||
return colours.Blue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
|
||||
@ -11,8 +12,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
{
|
||||
public class MultiSpectatorLeaderboard : MultiplayerGameplayLeaderboard
|
||||
{
|
||||
public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, int[] userIds)
|
||||
: base(scoreProcessor, userIds)
|
||||
public MultiSpectatorLeaderboard([NotNull] ScoreProcessor scoreProcessor, MultiplayerRoomUser[] users)
|
||||
: base(scoreProcessor, users)
|
||||
{
|
||||
}
|
||||
|
||||
@ -32,7 +33,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
((SpectatingTrackedUserData)data).Clock = null;
|
||||
}
|
||||
|
||||
protected override TrackedUserData CreateUserData(int userId, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(userId, scoreProcessor);
|
||||
protected override TrackedUserData CreateUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor) => new SpectatingTrackedUserData(user, scoreProcessor);
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
@ -47,8 +48,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
[CanBeNull]
|
||||
public IClock Clock;
|
||||
|
||||
public SpectatingTrackedUserData(int userId, ScoreProcessor scoreProcessor)
|
||||
: base(userId, scoreProcessor)
|
||||
public SpectatingTrackedUserData(MultiplayerRoomUser user, ScoreProcessor scoreProcessor)
|
||||
: base(user, scoreProcessor)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
/// <param name="score">The score containing the player's replay.</param>
|
||||
/// <param name="spectatorPlayerClock">The clock controlling the gameplay running state.</param>
|
||||
public MultiSpectatorPlayer([NotNull] Score score, [NotNull] ISpectatorPlayerClock spectatorPlayerClock)
|
||||
: base(score)
|
||||
: base(score, new PlayerConfiguration { AllowUserInteraction = false })
|
||||
{
|
||||
this.spectatorPlayerClock = spectatorPlayerClock;
|
||||
}
|
||||
@ -34,6 +34,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
private void load()
|
||||
{
|
||||
spectatorPlayerClock.WaitingOnFrames.BindTo(waitingOnFrames);
|
||||
|
||||
HUDOverlay.PlayerSettingsOverlay.Expire();
|
||||
HUDOverlay.HoldToQuit.Expire();
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -19,6 +20,12 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
PlayerSettings.Expire();
|
||||
}
|
||||
|
||||
protected override void LogoArriving(OsuLogo logo, bool resuming)
|
||||
{
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Online.Spectator;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Spectate;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
@ -24,7 +25,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
// We are managing our own adjustments. For now, this happens inside the Player instances themselves.
|
||||
public override bool AllowRateAdjustments => false;
|
||||
public override bool AllowTrackAdjustments => false;
|
||||
|
||||
/// <summary>
|
||||
/// Whether all spectating players have finished loading.
|
||||
@ -45,20 +46,26 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
private PlayerArea currentAudioSource;
|
||||
private bool canStartMasterClock;
|
||||
|
||||
private readonly MultiplayerRoomUser[] users;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="MultiSpectatorScreen"/>.
|
||||
/// </summary>
|
||||
/// <param name="userIds">The players to spectate.</param>
|
||||
public MultiSpectatorScreen(int[] userIds)
|
||||
: base(userIds.Take(PlayerGrid.MAX_PLAYERS).ToArray())
|
||||
/// <param name="users">The players to spectate.</param>
|
||||
public MultiSpectatorScreen(MultiplayerRoomUser[] users)
|
||||
: base(users.Select(u => u.UserID).ToArray())
|
||||
{
|
||||
instances = new PlayerArea[UserIds.Count];
|
||||
this.users = users;
|
||||
|
||||
instances = new PlayerArea[Users.Count];
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Container leaderboardContainer;
|
||||
Container scoreDisplayContainer;
|
||||
|
||||
masterClockContainer = new MasterGameplayClockContainer(Beatmap.Value, 0);
|
||||
|
||||
InternalChildren = new[]
|
||||
@ -67,28 +74,44 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
masterClockContainer.WithChild(new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize)
|
||||
},
|
||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
leaderboardContainer = new Container
|
||||
scoreDisplayContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
},
|
||||
grid = new PlayerGrid { RelativeSizeAxes = Axes.Both }
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
leaderboardContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X
|
||||
},
|
||||
grid = new PlayerGrid { RelativeSizeAxes = Axes.Both }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
for (int i = 0; i < UserIds.Count; i++)
|
||||
for (int i = 0; i < Users.Count; i++)
|
||||
{
|
||||
grid.Add(instances[i] = new PlayerArea(UserIds[i], masterClockContainer.GameplayClock));
|
||||
grid.Add(instances[i] = new PlayerArea(Users[i], masterClockContainer.GameplayClock));
|
||||
syncManager.AddPlayerClock(instances[i].GameplayClock);
|
||||
}
|
||||
|
||||
@ -97,7 +120,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
var scoreProcessor = Ruleset.Value.CreateInstance().CreateScoreProcessor();
|
||||
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
||||
|
||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, UserIds.ToArray())
|
||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(scoreProcessor, users)
|
||||
{
|
||||
Expanded = { Value = true },
|
||||
Anchor = Anchor.CentreLeft,
|
||||
@ -108,6 +131,15 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
leaderboard.AddClock(instance.UserId, instance.GameplayClock);
|
||||
|
||||
leaderboardContainer.Add(leaderboard);
|
||||
|
||||
if (leaderboard.TeamScores.Count == 2)
|
||||
{
|
||||
LoadComponentAsync(new MatchScoreDisplay
|
||||
{
|
||||
Team1Score = { BindTarget = leaderboard.TeamScores.First().Value },
|
||||
Team2Score = { BindTarget = leaderboard.TeamScores.Last().Value },
|
||||
}, scoreDisplayContainer.Add);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -30,11 +30,14 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
protected Bindable<RoomStatus> Status { get; private set; }
|
||||
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<GameType> Type { get; private set; }
|
||||
protected Bindable<MatchType> Type { get; private set; }
|
||||
|
||||
[Resolved(typeof(Room))]
|
||||
protected BindableList<PlaylistItem> Playlist { get; private set; }
|
||||
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<RoomCategory> Category { get; private set; }
|
||||
|
||||
[Resolved(typeof(Room))]
|
||||
protected BindableList<User> RecentParticipants { get; private set; }
|
||||
|
||||
|
@ -11,26 +11,27 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
[Cached]
|
||||
public abstract class OnlinePlayScreen : OsuScreen, IHasSubScreenStack
|
||||
{
|
||||
[Cached]
|
||||
protected readonly OverlayColourProvider ColourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||
|
||||
public override bool CursorVisible => (screenStack?.CurrentScreen as IOnlinePlaySubScreen)?.CursorVisible ?? true;
|
||||
|
||||
// this is required due to PlayerLoader eventually being pushed to the main stack
|
||||
@ -38,14 +39,8 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
private MultiplayerWaveContainer waves;
|
||||
|
||||
private OsuButton createButton;
|
||||
|
||||
private ScreenStack screenStack;
|
||||
|
||||
private LoungeSubScreen loungeSubScreen;
|
||||
|
||||
private readonly IBindable<bool> isIdle = new BindableBool();
|
||||
private ScreenStack screenStack;
|
||||
|
||||
[Cached(Type = typeof(IRoomManager))]
|
||||
protected RoomManager RoomManager { get; private set; }
|
||||
@ -53,9 +48,6 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[Cached]
|
||||
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
|
||||
|
||||
[Cached]
|
||||
private readonly Bindable<FilterCriteria> currentFilter = new Bindable<FilterCriteria>(new FilterCriteria());
|
||||
|
||||
[Cached]
|
||||
private readonly OngoingOperationTracker ongoingOperationTracker = new OngoingOperationTracker();
|
||||
|
||||
@ -68,15 +60,9 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[Resolved]
|
||||
protected IAPIProvider API { get; private set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IdleTracker idleTracker { get; set; }
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private OsuLogo logo { get; set; }
|
||||
|
||||
private Drawable header;
|
||||
private Drawable headerBackground;
|
||||
|
||||
protected OnlinePlayScreen()
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
@ -107,59 +93,26 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = Header.HEIGHT },
|
||||
Children = new[]
|
||||
Children = new Drawable[]
|
||||
{
|
||||
header = new Container
|
||||
new BeatmapBackgroundSprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 400,
|
||||
Children = new[]
|
||||
{
|
||||
headerBackground = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 1.25f,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new HeaderBackgroundSprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 400 // Keep a static height so the header doesn't change as it's resized between subscreens
|
||||
},
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Bottom = -1 }, // 1px padding to avoid a 1px gap due to masking
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientVertical(backgroundColour.Opacity(0.5f), backgroundColour)
|
||||
},
|
||||
}
|
||||
}
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
screenStack = new OnlinePlaySubScreenStack { RelativeSizeAxes = Axes.Both }
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.9f), Color4.Black.Opacity(0.6f))
|
||||
},
|
||||
screenStack = new OnlinePlaySubScreenStack
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
}
|
||||
}
|
||||
},
|
||||
new Header(ScreenTitle, screenStack),
|
||||
createButton = CreateNewMultiplayerGameButton().With(button =>
|
||||
{
|
||||
button.Anchor = Anchor.TopRight;
|
||||
button.Origin = Anchor.TopRight;
|
||||
button.Size = new Vector2(150, Header.HEIGHT - 20);
|
||||
button.Margin = new MarginPadding
|
||||
{
|
||||
Top = 10,
|
||||
Right = 10 + HORIZONTAL_OVERFLOW_PADDING,
|
||||
};
|
||||
button.Action = () => OpenNewRoom();
|
||||
}),
|
||||
RoomManager,
|
||||
ongoingOperationTracker,
|
||||
ongoingOperationTracker
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -184,12 +137,6 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
apiState.BindTo(API.State);
|
||||
apiState.BindValueChanged(onlineStateChanged, true);
|
||||
|
||||
if (idleTracker != null)
|
||||
{
|
||||
isIdle.BindTo(idleTracker.IsIdle);
|
||||
isIdle.BindValueChanged(idle => UpdatePollingRate(idle.NewValue), true);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||
@ -199,8 +146,6 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
protected abstract void UpdatePollingRate(bool isIdle);
|
||||
|
||||
private void forcefullyExit()
|
||||
{
|
||||
// This is temporary since we don't currently have a way to force screens to be exited
|
||||
@ -236,8 +181,6 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
screenStack.CurrentScreen.OnResuming(last);
|
||||
|
||||
base.OnResuming(last);
|
||||
|
||||
UpdatePollingRate(isIdle.Value);
|
||||
}
|
||||
|
||||
public override void OnSuspending(IScreen next)
|
||||
@ -247,8 +190,6 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
Debug.Assert(screenStack.CurrentScreen != null);
|
||||
screenStack.CurrentScreen.OnSuspending(next);
|
||||
|
||||
UpdatePollingRate(isIdle.Value);
|
||||
}
|
||||
|
||||
public override bool OnExiting(IScreen next)
|
||||
@ -292,18 +233,6 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
logo.Delay(WaveContainer.DISAPPEAR_DURATION / 2).FadeOut();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates and opens the newly-created room.
|
||||
/// </summary>
|
||||
/// <param name="room">An optional template to use when creating the room.</param>
|
||||
public void OpenNewRoom(Room room = null) => loungeSubScreen.Open(room ?? CreateNewRoom());
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new room.
|
||||
/// </summary>
|
||||
/// <returns>The created <see cref="Room"/>.</returns>
|
||||
protected abstract Room CreateNewRoom();
|
||||
|
||||
private void screenPushed(IScreen lastScreen, IScreen newScreen)
|
||||
{
|
||||
subScreenChanged(lastScreen, newScreen);
|
||||
@ -319,39 +248,21 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
|
||||
private void subScreenChanged(IScreen lastScreen, IScreen newScreen)
|
||||
{
|
||||
switch (newScreen)
|
||||
{
|
||||
case LoungeSubScreen _:
|
||||
header.Delay(OnlinePlaySubScreen.RESUME_TRANSITION_DELAY).ResizeHeightTo(400, OnlinePlaySubScreen.APPEAR_DURATION, Easing.OutQuint);
|
||||
headerBackground.MoveToX(0, OnlinePlaySubScreen.X_MOVE_DURATION, Easing.OutQuint);
|
||||
break;
|
||||
|
||||
case RoomSubScreen _:
|
||||
header.ResizeHeightTo(135, OnlinePlaySubScreen.APPEAR_DURATION, Easing.OutQuint);
|
||||
headerBackground.MoveToX(-OnlinePlaySubScreen.X_SHIFT, OnlinePlaySubScreen.X_MOVE_DURATION, Easing.OutQuint);
|
||||
break;
|
||||
}
|
||||
|
||||
if (lastScreen is IOsuScreen lastOsuScreen)
|
||||
Activity.UnbindFrom(lastOsuScreen.Activity);
|
||||
|
||||
if (newScreen is IOsuScreen newOsuScreen)
|
||||
((IBindable<UserActivity>)Activity).BindTo(newOsuScreen.Activity);
|
||||
|
||||
UpdatePollingRate(isIdle.Value);
|
||||
createButton.FadeTo(newScreen is LoungeSubScreen ? 1 : 0, 200);
|
||||
}
|
||||
|
||||
protected IScreen CurrentSubScreen => screenStack.CurrentScreen;
|
||||
|
||||
protected abstract string ScreenTitle { get; }
|
||||
|
||||
protected abstract RoomManager CreateRoomManager();
|
||||
protected virtual RoomManager CreateRoomManager() => new RoomManager();
|
||||
|
||||
protected abstract LoungeSubScreen CreateLounge();
|
||||
|
||||
protected abstract OsuButton CreateNewMultiplayerGameButton();
|
||||
|
||||
private class MultiplayerWaveContainer : WaveContainer
|
||||
{
|
||||
protected override bool StartHidden => true;
|
||||
@ -365,13 +276,48 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
}
|
||||
}
|
||||
|
||||
private class HeaderBackgroundSprite : OnlinePlayBackgroundSprite
|
||||
private class BeatmapBackgroundSprite : OnlinePlayBackgroundSprite
|
||||
{
|
||||
protected override UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new BackgroundSprite { RelativeSizeAxes = Axes.Both };
|
||||
protected override UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new BlurredBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both };
|
||||
|
||||
private class BackgroundSprite : UpdateableBeatmapBackgroundSprite
|
||||
public class BlurredBackgroundSprite : UpdateableBeatmapBackgroundSprite
|
||||
{
|
||||
protected override double TransformDuration => 200;
|
||||
public BlurredBackgroundSprite(BeatmapSetCoverType type)
|
||||
: base(type)
|
||||
{
|
||||
}
|
||||
|
||||
protected override double LoadDelay => 200;
|
||||
|
||||
protected override Drawable CreateDrawable(BeatmapInfo model) =>
|
||||
new BufferedLoader(base.CreateDrawable(model));
|
||||
}
|
||||
|
||||
// This class is an unfortunate requirement due to `LongRunningLoad` requiring direct async loading.
|
||||
// It means that if the web request fetching the beatmap background takes too long, it will suddenly appear.
|
||||
internal class BufferedLoader : BufferedContainer
|
||||
{
|
||||
private readonly Drawable drawable;
|
||||
|
||||
public BufferedLoader(Drawable drawable)
|
||||
{
|
||||
this.drawable = drawable;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
BlurSigma = new Vector2(10);
|
||||
FrameBufferScale = new Vector2(0.5f);
|
||||
CacheDrawnFrameBuffer = true;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
LoadComponentAsync(drawable, d =>
|
||||
{
|
||||
Add(d);
|
||||
ForceRedraw();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -59,6 +59,8 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
LeftArea.Padding = new MarginPadding { Top = Header.HEIGHT };
|
||||
|
||||
initialBeatmap = Beatmap.Value;
|
||||
initialRuleset = Ruleset.Value;
|
||||
initialMods = Mods.Value.ToList();
|
||||
|
@ -6,13 +6,11 @@ using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
public class CreatePlaylistsRoomButton : PurpleTriangleButton
|
||||
public class CreatePlaylistsRoomButton : CreateRoomButton
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Triangles.TriangleScale = 1.5f;
|
||||
|
||||
Text = "Create playlist";
|
||||
}
|
||||
}
|
||||
|
@ -1,62 +1,14 @@
|
||||
// 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 osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
public class Playlists : OnlinePlayScreen
|
||||
{
|
||||
protected override void UpdatePollingRate(bool isIdle)
|
||||
{
|
||||
var playlistsManager = (PlaylistsRoomManager)RoomManager;
|
||||
|
||||
if (!this.IsCurrentScreen())
|
||||
{
|
||||
playlistsManager.TimeBetweenListingPolls.Value = 0;
|
||||
playlistsManager.TimeBetweenSelectionPolls.Value = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (CurrentSubScreen)
|
||||
{
|
||||
case LoungeSubScreen _:
|
||||
playlistsManager.TimeBetweenListingPolls.Value = isIdle ? 120000 : 15000;
|
||||
playlistsManager.TimeBetweenSelectionPolls.Value = isIdle ? 120000 : 15000;
|
||||
break;
|
||||
|
||||
case RoomSubScreen _:
|
||||
playlistsManager.TimeBetweenListingPolls.Value = 0;
|
||||
playlistsManager.TimeBetweenSelectionPolls.Value = isIdle ? 30000 : 5000;
|
||||
break;
|
||||
|
||||
default:
|
||||
playlistsManager.TimeBetweenListingPolls.Value = 0;
|
||||
playlistsManager.TimeBetweenSelectionPolls.Value = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Logger.Log($"Polling adjusted (listing: {playlistsManager.TimeBetweenListingPolls.Value}, selection: {playlistsManager.TimeBetweenSelectionPolls.Value})");
|
||||
}
|
||||
|
||||
protected override Room CreateNewRoom()
|
||||
{
|
||||
return new Room { Name = { Value = $"{API.LocalUser}'s awesome playlist" } };
|
||||
}
|
||||
|
||||
protected override string ScreenTitle => "Playlists";
|
||||
|
||||
protected override RoomManager CreateRoomManager() => new PlaylistsRoomManager();
|
||||
|
||||
protected override LoungeSubScreen CreateLounge() => new PlaylistsLoungeSubScreen();
|
||||
|
||||
protected override OsuButton CreateNewMultiplayerGameButton() => new CreatePlaylistsRoomButton();
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,15 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
@ -10,8 +18,62 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
public class PlaylistsLoungeSubScreen : LoungeSubScreen
|
||||
{
|
||||
protected override FilterControl CreateFilterControl() => new PlaylistsFilterControl();
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private Dropdown<PlaylistsCategory> categoryDropdown;
|
||||
|
||||
protected override IEnumerable<Drawable> CreateFilterControls()
|
||||
{
|
||||
categoryDropdown = new SlimEnumDropdown<PlaylistsCategory>
|
||||
{
|
||||
RelativeSizeAxes = Axes.None,
|
||||
Width = 160,
|
||||
};
|
||||
|
||||
categoryDropdown.Current.BindValueChanged(_ => UpdateFilter());
|
||||
|
||||
return base.CreateFilterControls().Append(categoryDropdown);
|
||||
}
|
||||
|
||||
protected override FilterCriteria CreateFilterCriteria()
|
||||
{
|
||||
var criteria = base.CreateFilterCriteria();
|
||||
|
||||
switch (categoryDropdown.Current.Value)
|
||||
{
|
||||
case PlaylistsCategory.Normal:
|
||||
criteria.Category = @"normal";
|
||||
break;
|
||||
|
||||
case PlaylistsCategory.Spotlight:
|
||||
criteria.Category = @"spotlight";
|
||||
break;
|
||||
}
|
||||
|
||||
return criteria;
|
||||
}
|
||||
|
||||
protected override OsuButton CreateNewRoomButton() => new CreatePlaylistsRoomButton();
|
||||
|
||||
protected override Room CreateNewRoom()
|
||||
{
|
||||
return new Room
|
||||
{
|
||||
Name = { Value = $"{api.LocalUser}'s awesome playlist" },
|
||||
Type = { Value = MatchType.Playlists }
|
||||
};
|
||||
}
|
||||
|
||||
protected override RoomSubScreen CreateRoomSubScreen(Room room) => new PlaylistsRoomSubScreen(room);
|
||||
|
||||
protected override ListingPollingComponent CreatePollingComponent() => new ListingPollingComponent();
|
||||
|
||||
private enum PlaylistsCategory
|
||||
{
|
||||
Any,
|
||||
Normal,
|
||||
Spotlight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,8 +26,16 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
public Action EditPlaylist;
|
||||
|
||||
private MatchSettings settings;
|
||||
|
||||
protected override OsuButton SubmitButton => settings.ApplyButton;
|
||||
|
||||
protected override bool IsLoading => settings.IsLoading; // should probably be replaced with an OngoingOperationTracker.
|
||||
|
||||
protected override void SelectBeatmap() => settings.SelectBeatmap();
|
||||
|
||||
protected override OnlinePlayComposite CreateSettings()
|
||||
=> new MatchSettings
|
||||
=> settings = new MatchSettings
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativePositionAxes = Axes.Y,
|
||||
@ -45,12 +53,16 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
public RoomAvailabilityPicker AvailabilityPicker;
|
||||
public TriangleButton ApplyButton;
|
||||
|
||||
public bool IsLoading => loadingLayer.State.Value == Visibility.Visible;
|
||||
|
||||
public OsuSpriteText ErrorText;
|
||||
|
||||
private LoadingLayer loadingLayer;
|
||||
private DrawableRoomPlaylist playlist;
|
||||
private OsuSpriteText playlistLength;
|
||||
|
||||
private PurpleTriangleButton editPlaylistButton;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
private IRoomManager manager { get; set; }
|
||||
|
||||
@ -199,7 +211,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
},
|
||||
new Drawable[]
|
||||
{
|
||||
new PurpleTriangleButton
|
||||
editPlaylistButton = new PurpleTriangleButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
@ -292,6 +304,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
ApplyButton.Enabled.Value = hasValidSettings;
|
||||
}
|
||||
|
||||
public void SelectBeatmap() => editPlaylistButton.TriggerClick();
|
||||
|
||||
private void onPlaylistChanged(object sender, NotifyCollectionChangedEventArgs e) =>
|
||||
playlistLength.Text = $"Length: {Playlist.GetTotalDuration()}";
|
||||
|
||||
|
@ -1,21 +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.Collections.Generic;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
public class PlaylistsRoomManager : RoomManager
|
||||
{
|
||||
public readonly Bindable<double> TimeBetweenListingPolls = new Bindable<double>();
|
||||
public readonly Bindable<double> TimeBetweenSelectionPolls = new Bindable<double>();
|
||||
|
||||
protected override IEnumerable<RoomPollingComponent> CreatePollingComponents() => new RoomPollingComponent[]
|
||||
{
|
||||
new ListingPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenListingPolls } },
|
||||
new SelectionPollingComponent { TimeBetweenPolls = { BindTarget = TimeBetweenSelectionPolls } }
|
||||
};
|
||||
}
|
||||
}
|
@ -3,11 +3,14 @@
|
||||
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Input;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
@ -33,12 +36,13 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
[Resolved(typeof(Room), nameof(Room.Playlist))]
|
||||
private BindableList<PlaylistItem> playlist { get; set; }
|
||||
|
||||
private readonly IBindable<bool> isIdle = new BindableBool();
|
||||
|
||||
private MatchSettingsOverlay settingsOverlay;
|
||||
private MatchLeaderboard leaderboard;
|
||||
|
||||
private OverlinedHeader participantsHeader;
|
||||
|
||||
private GridContainer mainContent;
|
||||
private SelectionPollingComponent selectionPollingComponent;
|
||||
|
||||
public PlaylistsRoomSubScreen(Room room)
|
||||
{
|
||||
@ -46,11 +50,15 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
Activity.Value = new UserActivity.InLobby(room);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load([CanBeNull] IdleTracker idleTracker)
|
||||
{
|
||||
if (idleTracker != null)
|
||||
isIdle.BindTo(idleTracker.IsIdle);
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
selectionPollingComponent = new SelectionPollingComponent(),
|
||||
mainContent = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -163,7 +171,7 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
Spacing = new Vector2(10, 0),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new PurpleTriangleButton
|
||||
new UserModSelectButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
@ -230,7 +238,11 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
settingsOverlay = new PlaylistsMatchSettingsOverlay
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
EditPlaylist = () => this.Push(new PlaylistsSongSelect()),
|
||||
EditPlaylist = () =>
|
||||
{
|
||||
if (this.IsCurrentScreen())
|
||||
this.Push(new PlaylistsSongSelect());
|
||||
},
|
||||
State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden }
|
||||
}
|
||||
});
|
||||
@ -256,6 +268,8 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
isIdle.BindValueChanged(_ => updatePollingRate(), true);
|
||||
|
||||
roomId.BindValueChanged(id =>
|
||||
{
|
||||
if (id.NewValue == null)
|
||||
@ -271,6 +285,12 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
}, true);
|
||||
}
|
||||
|
||||
private void updatePollingRate()
|
||||
{
|
||||
selectionPollingComponent.TimeBetweenPolls.Value = isIdle.Value ? 30000 : 5000;
|
||||
Logger.Log($"Polling adjusted (selection: {selectionPollingComponent.TimeBetweenPolls.Value})");
|
||||
}
|
||||
|
||||
protected override Screen CreateGameplayScreen() => new PlayerLoader(() => new PlaylistsPlayer(SelectedItem.Value)
|
||||
{
|
||||
Exited = () => leaderboard.RefreshScores()
|
||||
|
Reference in New Issue
Block a user