Merge pull request #7839 from smoogipoo/match-subscreen-redesign

Redesign match subscreen to add playlist support
This commit is contained in:
Dean Herbert
2020-02-15 21:11:39 +09:00
committed by GitHub
31 changed files with 984 additions and 1078 deletions

View File

@ -2,8 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Multi.Components;
@ -14,6 +17,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchBeatmapDetailArea : MultiplayerTestScene
{
[Resolved]
private BeatmapManager beatmapManager { get; set; }
[Resolved]
private RulesetStore rulesetStore { get; set; }
[SetUp]
public void Setup() => Schedule(() =>
{

View File

@ -1,41 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Match.Components;
using osu.Framework.Graphics;
using osu.Game.Audio;
using osu.Framework.Allocation;
namespace osu.Game.Tests.Visual.Multiplayer
{
[Cached(typeof(IPreviewTrackOwner))]
public class TestSceneMatchBeatmapPanel : MultiplayerTestScene, IPreviewTrackOwner
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(MatchBeatmapPanel)
};
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; }
public TestSceneMatchBeatmapPanel()
{
Add(new MatchBeatmapPanel
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
});
Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 1763072 } } });
Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 2101557 } } });
Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 1973466 } } });
Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 2109801 } } });
Room.Playlist.Add(new PlaylistItem { Beatmap = { Value = new BeatmapInfo { OnlineBeatmapID = 1922035 } } });
}
}
}

View File

@ -5,10 +5,10 @@ using System;
using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.GameTypes;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
@ -45,7 +45,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
}
});
Room.Type.Value = new GameTypeTimeshift();
Room.Name.Value = "A very awesome room";
Room.Host.Value = new User { Id = 2, Username = "peppy" };
Child = new Header();
}

View File

@ -1,35 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchHostInfo : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(HostInfo)
};
private readonly Bindable<User> host = new Bindable<User>(new User { Username = "SomeHost" });
public TestSceneMatchHostInfo()
{
HostInfo hostInfo;
Child = hostInfo = new HostInfo
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
};
hostInfo.Host.BindTo(host);
}
}
}

View File

@ -1,84 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.RoomStatuses;
using osu.Game.Rulesets;
using osu.Game.Screens.Multi.Match.Components;
namespace osu.Game.Tests.Visual.Multiplayer
{
[TestFixture]
public class TestSceneMatchInfo : MultiplayerTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Info),
typeof(HeaderButton),
typeof(ReadyButton),
typeof(MatchBeatmapPanel)
};
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
Add(new Info());
AddStep(@"set name", () => Room.Name.Value = @"Room Name?");
AddStep(@"set availability", () => Room.Availability.Value = RoomAvailability.FriendsOnly);
AddStep(@"set status", () => Room.Status.Value = new RoomStatusPlaying());
AddStep(@"set beatmap", () =>
{
Room.Playlist.Clear();
Room.Playlist.Add(new PlaylistItem
{
Beatmap =
{
Value = new BeatmapInfo
{
StarDifficulty = 2.4,
Ruleset = rulesets.GetRuleset(0),
Metadata = new BeatmapMetadata
{
Title = @"My Song",
Artist = @"VisualTests",
AuthorString = @"osu!lazer",
},
}
}
});
});
AddStep(@"change name", () => Room.Name.Value = @"Room Name!");
AddStep(@"change availability", () => Room.Availability.Value = RoomAvailability.InviteOnly);
AddStep(@"change status", () => Room.Status.Value = new RoomStatusOpen());
AddStep(@"null beatmap", () => Room.Playlist.Clear());
AddStep(@"change beatmap", () =>
{
Room.Playlist.Clear();
Room.Playlist.Add(new PlaylistItem
{
Beatmap =
{
Value = new BeatmapInfo
{
StarDifficulty = 4.2,
Ruleset = rulesets.GetRuleset(3),
Metadata = new BeatmapMetadata
{
Title = @"Your Song",
Artist = @"Tester",
AuthorString = @"Someone",
},
}
}
});
});
}
}
}

View File

@ -0,0 +1,39 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Multi.Match.Components;
using osuTK;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchLeaderboardChatDisplay : MultiplayerTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(LeaderboardChatDisplay)
};
protected override bool UseOnlineAPI => true;
public TestSceneMatchLeaderboardChatDisplay()
{
Room.RoomID.Value = 7;
Add(new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500),
Child = new LeaderboardChatDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
}
});
}
}
}

View File

@ -1,52 +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 NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual.Multiplayer
{
[TestFixture]
public class TestSceneMatchParticipants : MultiplayerTestScene
{
public TestSceneMatchParticipants()
{
Add(new Participants { RelativeSizeAxes = Axes.Both });
AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
AddStep(@"set users", () => Room.Participants.AddRange(new[]
{
new User
{
Username = @"Feppla",
Id = 4271601,
Country = new Country { FlagName = @"SE" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
IsSupporter = true,
},
new User
{
Username = @"Xilver",
Id = 3099689,
Country = new Country { FlagName = @"IL" },
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
IsSupporter = true,
},
new User
{
Username = @"Wucki",
Id = 5287410,
Country = new Country { FlagName = @"FI" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5287410/5cfeaa9dd41cbce038ecdc9d781396ed4b0108089170bf7f50492ef8eadeb368.jpeg",
IsSupporter = true,
},
}));
AddStep(@"set max", () => Room.MaxParticipants.Value = 10);
AddStep(@"clear users", () => Room.Participants.Clear());
AddStep(@"set max to null", () => Room.MaxParticipants.Value = null);
}
}
}

View File

@ -0,0 +1,121 @@
// 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 NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Multi;
using osu.Game.Screens.Multi.Match;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Tests.Beatmaps;
using osu.Game.Users;
using osuTK.Input;
using Header = osu.Game.Screens.Multi.Match.Components.Header;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneMatchSubScreen : MultiplayerTestScene
{
protected override bool UseOnlineAPI => true;
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Screens.Multi.Multiplayer),
typeof(MatchSubScreen),
typeof(Header),
typeof(Footer)
};
[Cached(typeof(IRoomManager))]
private readonly TestRoomManager roomManager = new TestRoomManager();
[Resolved]
private BeatmapManager beatmaps { get; set; }
[Resolved]
private RulesetStore rulesets { get; set; }
private TestMatchSubScreen match;
[SetUp]
public void Setup() => Schedule(() =>
{
Room.CopyFrom(new Room());
});
[SetUpSteps]
public void SetupSteps()
{
AddStep("load match", () => LoadScreen(match = new TestMatchSubScreen(Room)));
AddUntilStep("wait for load", () => match.IsCurrentScreen());
}
[Test]
public void TestPlaylistItemSelectedOnCreate()
{
AddStep("set room properties", () =>
{
Room.Name.Value = "my awesome room";
Room.Host.Value = new User { Id = 2, Username = "peppy" };
Room.Playlist.Add(new PlaylistItem
{
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo }
});
});
AddStep("move mouse to create button", () =>
{
var footer = match.ChildrenOfType<Footer>().Single();
InputManager.MoveMouseTo(footer.ChildrenOfType<OsuButton>().Single());
});
AddStep("click", () => InputManager.Click(MouseButton.Left));
AddAssert("first playlist item selected", () => match.SelectedItem.Value == Room.Playlist[0]);
}
private class TestMatchSubScreen : MatchSubScreen
{
public new Bindable<PlaylistItem> SelectedItem => base.SelectedItem;
public TestMatchSubScreen(Room room)
: base(room)
{
}
}
private class TestRoomManager : IRoomManager
{
public event Action RoomsUpdated
{
add => throw new NotImplementedException();
remove => throw new NotImplementedException();
}
public IBindableList<Room> Rooms { get; } = new BindableList<Room>();
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
{
room.RoomID.Value = 1;
onSuccess?.Invoke(room);
}
public void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null) => onSuccess?.Invoke(room);
public void PartRoom()
{
}
}
}
}

View File

@ -0,0 +1,28 @@
// 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.Game.Screens.Multi.Match.Components;
using osuTK;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneOverlinedParticipants : MultiplayerTestScene
{
protected override bool UseOnlineAPI => true;
public TestSceneOverlinedParticipants()
{
Room.RoomID.Value = 7;
Add(new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500),
Child = new OverlinedParticipants()
});
}
}
}

View File

@ -0,0 +1,39 @@
// 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.Game.Online.Multiplayer;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Tests.Beatmaps;
using osuTK;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneOverlinedPlaylist : MultiplayerTestScene
{
protected override bool UseOnlineAPI => true;
public TestSceneOverlinedPlaylist()
{
for (int i = 0; i < 10; i++)
{
Room.Playlist.Add(new PlaylistItem
{
ID = i,
Beatmap = { Value = new TestBeatmap(new OsuRuleset().RulesetInfo).BeatmapInfo },
Ruleset = { Value = new OsuRuleset().RulesetInfo }
});
}
Add(new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(500),
Child = new OverlinedPlaylist(false)
});
}
}
}

View File

@ -0,0 +1,20 @@
// 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.Game.Screens.Multi.Components;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneParticipantsList : MultiplayerTestScene
{
protected override bool UseOnlineAPI => true;
public TestSceneParticipantsList()
{
Room.RoomID.Value = 7;
Add(new ParticipantsList { RelativeSizeAxes = Axes.Both });
}
}
}

View File

@ -118,8 +118,20 @@ namespace osu.Game.Online.Multiplayer
if (DateTimeOffset.Now >= EndDate.Value)
Status.Value = new RoomStatusEnded();
foreach (var removedItem in Playlist.Except(other.Playlist).ToArray())
Playlist.Remove(removedItem);
// transfer local beatmaps across to ensure we have Metadata available (CreateRoomRequest does not give us metadata as expected)
foreach (var item in other.Playlist)
{
var localItem = Playlist.FirstOrDefault(i => i.BeatmapID == item.BeatmapID);
if (localItem != null)
{
item.Beatmap.Value.Metadata = localItem.Beatmap.Value.Metadata;
}
}
foreach (var removeableItem in Playlist.Except(other.Playlist).ToArray())
Playlist.Remove(removeableItem);
Playlist.AddRange(other.Playlist.Except(Playlist).ToArray());
foreach (var removedItem in Participants.Except(other.Participants).ToArray())

View File

@ -0,0 +1,119 @@
// 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.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Users;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Screens.Multi.Components
{
public class ParticipantsList : MultiplayerComposite
{
private readonly FillFlowContainer fill;
public ParticipantsList()
{
InternalChild = new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = fill = new FillFlowContainer
{
Spacing = new Vector2(10),
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Full,
}
};
}
[BackgroundDependencyLoader]
private void load()
{
RoomID.BindValueChanged(_ => updateParticipants(), true);
}
[Resolved]
private IAPIProvider api { get; set; }
private GetRoomScoresRequest request;
private void updateParticipants()
{
var roomId = RoomID.Value ?? 0;
request?.Cancel();
// nice little progressive fade
int time = 500;
foreach (var c in fill.Children)
{
c.Delay(500 - time).FadeOut(time, Easing.Out);
time = Math.Max(20, time - 20);
c.Expire();
}
if (roomId == 0) return;
request = new GetRoomScoresRequest(roomId);
request.Success += scores => Schedule(() =>
{
if (roomId != RoomID.Value)
return;
fill.Clear();
foreach (var s in scores)
fill.Add(new UserTile(s.User));
fill.FadeInFromZero(1000, Easing.OutQuint);
});
api.Queue(request);
}
protected override void Dispose(bool isDisposing)
{
request?.Cancel();
base.Dispose(isDisposing);
}
private class UserTile : CompositeDrawable, IHasTooltip
{
private readonly User user;
public string TooltipText => user.Username;
public UserTile(User user)
{
this.user = user;
Size = new Vector2(70f);
CornerRadius = 5f;
Masking = true;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"27252d"),
},
new UpdateableAvatar
{
RelativeSizeAxes = Axes.Both,
User = user,
},
};
}
}
}
}

View File

@ -18,6 +18,7 @@ using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online;
using osu.Game.Online.Chat;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays.Direct;
@ -51,6 +52,8 @@ namespace osu.Game.Screens.Multi
private readonly bool allowEdit;
private readonly bool allowSelection;
protected override bool ShouldBeConsideredForInput(Drawable child) => allowEdit || SelectedItem.Value == Model;
public DrawableRoomPlaylistItem(PlaylistItem item, bool allowEdit, bool allowSelection)
: base(item)
{
@ -186,17 +189,16 @@ namespace osu.Game.Screens.Multi
X = -18,
Children = new Drawable[]
{
new PlaylistDownloadButton(item.Beatmap.Value.BeatmapSet)
{
Size = new Vector2(50, 30)
},
new IconButton
{
Icon = FontAwesome.Solid.MinusSquare,
Alpha = allowEdit ? 1 : 0,
Action = () => RequestDeletion?.Invoke(Model),
},
new PanelDownloadButton(item.Beatmap.Value.BeatmapSet)
{
Size = new Vector2(50, 30),
Alpha = allowEdit ? 0 : 1
}
}
}
}
@ -209,6 +211,27 @@ namespace osu.Game.Screens.Multi
return true;
}
private class PlaylistDownloadButton : PanelDownloadButton
{
public PlaylistDownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false)
: base(beatmapSet, noVideo)
{
Alpha = 0;
}
protected override void LoadComplete()
{
base.LoadComplete();
State.BindValueChanged(stateChanged, true);
}
private void stateChanged(ValueChangedEvent<DownloadState> state)
{
this.FadeTo(state.NewValue == DownloadState.LocallyAvailable ? 0 : 1, 500);
}
}
// For now, this is the same implementation as in PanelBackground, but supports a beatmap info rather than a working beatmap
private class PanelBackground : Container // todo: should be a buffered container (https://github.com/ppy/osu-framework/issues/3222)
{

View File

@ -1,25 +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 osu.Framework.Allocation;
using osu.Framework.Bindables;
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.Shapes;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Components;
using osu.Game.Users;
using osu.Game.Users.Drawables;
using osuTK;
using osuTK.Graphics;
@ -160,9 +153,11 @@ namespace osu.Game.Screens.Multi.Lounge.Components
},
new Drawable[]
{
new MatchParticipants
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 10 },
Child = new ParticipantsList { RelativeSizeAxes = Axes.Both }
}
}
}
@ -219,107 +214,5 @@ namespace osu.Game.Screens.Multi.Lounge.Components
status.BindValueChanged(s => Text = s.NewValue.Message, true);
}
}
private class MatchParticipants : MultiplayerComposite
{
private readonly FillFlowContainer fill;
public MatchParticipants()
{
Padding = new MarginPadding { Horizontal = 10 };
InternalChild = new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
Child = fill = new FillFlowContainer
{
Spacing = new Vector2(10),
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Full,
}
};
}
[BackgroundDependencyLoader]
private void load()
{
RoomID.BindValueChanged(_ => updateParticipants(), true);
}
[Resolved]
private IAPIProvider api { get; set; }
private GetRoomScoresRequest request;
private void updateParticipants()
{
var roomId = RoomID.Value ?? 0;
request?.Cancel();
// nice little progressive fade
int time = 500;
foreach (var c in fill.Children)
{
c.Delay(500 - time).FadeOut(time, Easing.Out);
time = Math.Max(20, time - 20);
c.Expire();
}
if (roomId == 0) return;
request = new GetRoomScoresRequest(roomId);
request.Success += scores =>
{
if (roomId != RoomID.Value)
return;
fill.Clear();
foreach (var s in scores)
fill.Add(new UserTile(s.User));
fill.FadeInFromZero(1000, Easing.OutQuint);
};
api.Queue(request);
}
protected override void Dispose(bool isDisposing)
{
request?.Cancel();
base.Dispose(isDisposing);
}
private class UserTile : CompositeDrawable, IHasTooltip
{
private readonly User user;
public string TooltipText => user.Username;
public UserTile(User user)
{
this.user = user;
Size = new Vector2(70f);
CornerRadius = 5f;
Masking = true;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"27252d"),
},
new UpdateableAvatar
{
RelativeSizeAxes = Axes.Both,
User = user,
},
};
}
}
}
}
}

View File

@ -0,0 +1,53 @@
// 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.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osuTK;
namespace osu.Game.Screens.Multi.Match.Components
{
public class Footer : CompositeDrawable
{
public const float HEIGHT = 100;
public Action OnStart;
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
private readonly Drawable background;
private readonly OsuButton startButton;
public Footer()
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
InternalChildren = new[]
{
background = new Box { RelativeSizeAxes = Axes.Both },
startButton = new ReadyButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(600, 50),
SelectedItem = { BindTarget = SelectedItem },
Action = () => OnStart?.Invoke()
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
background.Colour = OsuColour.FromHex(@"28242d");
startButton.BackgroundColour = colours.Green;
}
}
}

View File

@ -1,39 +1,23 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays.SearchableList;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Multi.Components;
using osu.Game.Screens.Play.HUD;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Users.Drawables;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Multi.Match.Components
{
public class Header : MultiplayerComposite
{
public const float HEIGHT = 200;
public const float HEIGHT = 50;
public readonly BindableBool ShowBeatmapPanel = new BindableBool();
public MatchTabControl Tabs { get; private set; }
public Action RequestBeatmapSelection;
private MatchBeatmapPanel beatmapPanel;
private ModDisplay modDisplay;
private UpdateableAvatar avatar;
private LinkFlowContainer hostText;
public Header()
{
@ -44,128 +28,52 @@ namespace osu.Game.Screens.Multi.Match.Components
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BeatmapSelectButton beatmapButton;
InternalChildren = new Drawable[]
InternalChild = new FillFlowContainer
{
new Container
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new Drawable[]
avatar = new UpdateableAvatar
{
new HeaderBackgroundSprite { RelativeSizeAxes = Axes.Both },
new Box
Size = new Vector2(50),
Masking = true,
CornerRadius = 10,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.7f), Color4.Black.Opacity(0.8f)),
},
beatmapPanel = new MatchBeatmapPanel
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Margin = new MarginPadding { Right = 100 },
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 30),
Current = { BindTarget = RoomName }
},
hostText = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold))
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
}
}
}
},
new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = 1,
Colour = colours.Yellow
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 20 },
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new BeatmapTypeInfo(),
modDisplay = new ModDisplay
{
Scale = new Vector2(0.75f),
DisplayUnrankedText = false
},
}
},
new Container
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = 200,
Padding = new MarginPadding { Vertical = 10 },
Child = beatmapButton = new BeatmapSelectButton
{
RelativeSizeAxes = Axes.Both,
Height = 1,
},
},
Tabs = new MatchTabControl
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X
},
},
},
}
};
beatmapButton.Action = () => RequestBeatmapSelection?.Invoke();
Playlist.ItemsAdded += _ => updateMods();
Playlist.ItemsRemoved += _ => updateMods();
updateMods();
}
protected override void LoadComplete()
{
base.LoadComplete();
ShowBeatmapPanel.BindValueChanged(value => beatmapPanel.FadeTo(value.NewValue ? 1 : 0, 200, Easing.OutQuint), true);
}
private void updateMods()
{
var item = Playlist.FirstOrDefault();
modDisplay.Current.Value = item?.RequiredMods?.ToArray() ?? Array.Empty<Mod>();
}
private class BeatmapSelectButton : HeaderButton
{
[Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable<int?> roomId { get; set; }
public BeatmapSelectButton()
Host.BindValueChanged(host =>
{
Text = "Select beatmap";
}
avatar.User = host.NewValue;
[BackgroundDependencyLoader]
private void load()
{
roomId.BindValueChanged(id => this.FadeTo(id.NewValue.HasValue ? 0 : 1), true);
}
}
hostText.Clear();
private class HeaderBackgroundSprite : MultiplayerBackgroundSprite
{
protected override UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new BackgroundSprite { RelativeSizeAxes = Axes.Both };
private class BackgroundSprite : UpdateableBeatmapBackgroundSprite
{
protected override double TransformDuration => 200;
}
if (host.NewValue != null)
{
hostText.AddText("hosted by ");
hostText.AddUserLink(host.NewValue);
}
}, true);
}
}
}

View File

@ -1,47 +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.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Multi.Match.Components
{
public class HeaderButton : TriangleButton
{
[BackgroundDependencyLoader]
private void load()
{
BackgroundColour = OsuColour.FromHex(@"1187aa");
Triangles.ColourLight = OsuColour.FromHex(@"277b9c");
Triangles.ColourDark = OsuColour.FromHex(@"1f6682");
Triangles.TriangleScale = 1.5f;
Add(new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 1f,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.15f,
Blending = BlendingParameters.Additive,
},
});
}
protected override SpriteText CreateText() => new OsuSpriteText
{
Depth = -1,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Font = OsuFont.GetFont(weight: FontWeight.Light, size: 30),
};
}
}

View File

@ -1,61 +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.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Users;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Screens.Multi.Match.Components
{
public class HostInfo : CompositeDrawable
{
public readonly IBindable<User> Host = new Bindable<User>();
private readonly LinkFlowContainer linkContainer;
private readonly UpdateableAvatar avatar;
public HostInfo()
{
AutoSizeAxes = Axes.X;
Height = 50;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5, 0),
Children = new Drawable[]
{
avatar = new UpdateableAvatar { Size = new Vector2(50) },
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Child = linkContainer = new LinkFlowContainer { AutoSizeAxes = Axes.Both }
}
}
};
Host.BindValueChanged(host => updateHost(host.NewValue));
}
private void updateHost(User host)
{
avatar.User = host;
if (host != null)
{
linkContainer.AddText("hosted by");
linkContainer.NewLine();
linkContainer.AddUserLink(host, s => s.Font = s.Font.With(Typeface.Exo, weight: FontWeight.Bold, italics: true));
}
}
}
}

View File

@ -1,107 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays.SearchableList;
using osu.Game.Screens.Multi.Components;
using osuTK;
namespace osu.Game.Screens.Multi.Match.Components
{
public class Info : MultiplayerComposite
{
public Action OnStart;
private ReadyButton readyButton;
public Info()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
}
[BackgroundDependencyLoader]
private void load()
{
HostInfo hostInfo;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"28242d"),
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING + OsuScreen.HORIZONTAL_OVERFLOW_PADDING },
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Padding = new MarginPadding { Vertical = 20 },
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 30),
Current = RoomName
},
new RoomStatusInfo(),
}
},
hostInfo = new HostInfo(),
},
},
new FillFlowContainer
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.X,
Height = 70,
Spacing = new Vector2(10, 0),
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
readyButton = new ReadyButton
{
Action = () => OnStart?.Invoke()
}
}
}
},
},
};
hostInfo.Host.BindTo(Host);
Playlist.ItemsAdded += _ => updateBeatmap();
Playlist.ItemsRemoved += _ => updateBeatmap();
updateBeatmap();
}
private void updateBeatmap()
{
readyButton.Beatmap.Value = Playlist.FirstOrDefault()?.Beatmap.Value;
}
}
}

View File

@ -0,0 +1,100 @@
// 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.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Multi.Match.Components
{
public class LeaderboardChatDisplay : MultiplayerComposite
{
private const double fade_duration = 100;
private readonly OsuTabControl<DisplayMode> tabControl;
private readonly MatchLeaderboard leaderboard;
private readonly MatchChatDisplay chat;
public LeaderboardChatDisplay()
{
RelativeSizeAxes = Axes.Both;
InternalChild = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
tabControl = new DisplayModeTabControl
{
RelativeSizeAxes = Axes.X,
Height = 24,
}
},
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 10 },
Children = new Drawable[]
{
leaderboard = new MatchLeaderboard { RelativeSizeAxes = Axes.Both },
chat = new MatchChatDisplay
{
RelativeSizeAxes = Axes.Both,
Alpha = 0
}
}
}
},
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
tabControl.AccentColour = colours.Yellow;
}
protected override void LoadComplete()
{
base.LoadComplete();
tabControl.Current.BindValueChanged(changeTab);
}
public void RefreshScores() => leaderboard.RefreshScores();
private void changeTab(ValueChangedEvent<DisplayMode> mode)
{
chat.FadeTo(mode.NewValue == DisplayMode.Chat ? 1 : 0, fade_duration);
leaderboard.FadeTo(mode.NewValue == DisplayMode.Leaderboard ? 1 : 0, fade_duration);
}
private class DisplayModeTabControl : OsuTabControl<DisplayMode>
{
protected override TabItem<DisplayMode> CreateTabItem(DisplayMode value) => base.CreateTabItem(value).With(d =>
{
d.Anchor = Anchor.Centre;
d.Origin = Anchor.Centre;
});
}
private enum DisplayMode
{
Leaderboard,
Chat,
}
}
}

View File

@ -1,67 +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.Linq;
using System.Threading;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Overlays.Direct;
using osu.Game.Rulesets;
namespace osu.Game.Screens.Multi.Match.Components
{
public class MatchBeatmapPanel : MultiplayerComposite
{
[Resolved]
private IAPIProvider api { get; set; }
[Resolved]
private RulesetStore rulesets { get; set; }
private CancellationTokenSource loadCancellation;
private GetBeatmapSetRequest request;
private DirectGridPanel panel;
public MatchBeatmapPanel()
{
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load()
{
Playlist.ItemsAdded += _ => loadNewPanel();
Playlist.ItemsRemoved += _ => loadNewPanel();
loadNewPanel();
}
private void loadNewPanel()
{
loadCancellation?.Cancel();
request?.Cancel();
panel?.FadeOut(200);
panel?.Expire();
panel = null;
var beatmap = Playlist.FirstOrDefault()?.Beatmap.Value;
if (beatmap?.OnlineBeatmapID == null)
return;
loadCancellation = new CancellationTokenSource();
request = new GetBeatmapSetRequest(beatmap.OnlineBeatmapID.Value, BeatmapSetLookupType.BeatmapId);
request.Success += res => Schedule(() =>
{
panel = new DirectGridPanel(res.ToBeatmapSet(rulesets));
LoadComponentAsync(panel, AddInternal, loadCancellation.Token);
});
api.Queue(request);
}
}
}

View File

@ -1,28 +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.Bindables;
namespace osu.Game.Screens.Multi.Match.Components
{
public abstract class MatchPage
{
public abstract string Name { get; }
public readonly BindableBool Enabled = new BindableBool(true);
public override string ToString() => Name;
public override int GetHashCode() => GetType().GetHashCode();
public override bool Equals(object obj) => GetType() == obj?.GetType();
}
public class SettingsMatchPage : MatchPage
{
public override string Name => "Settings";
}
public class RoomMatchPage : MatchPage
{
public override string Name => "Room";
}
}

View File

@ -25,6 +25,8 @@ namespace osu.Game.Screens.Multi.Match.Components
private const float transition_duration = 350;
private const float field_padding = 45;
public Action EditPlaylist;
protected MatchSettings Settings { get; private set; }
[BackgroundDependencyLoader]
@ -35,7 +37,8 @@ namespace osu.Game.Screens.Multi.Match.Components
Child = Settings = new MatchSettings
{
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.Y
RelativePositionAxes = Axes.Y,
EditPlaylist = () => EditPlaylist?.Invoke()
};
}
@ -53,6 +56,8 @@ namespace osu.Game.Screens.Multi.Match.Components
{
private const float disabled_alpha = 0.2f;
public Action EditPlaylist;
public OsuTextBox NameField, MaxParticipantsField;
public OsuDropdown<TimeSpan> DurationField;
public RoomAvailabilityPicker AvailabilityPicker;
@ -63,6 +68,7 @@ namespace osu.Game.Screens.Multi.Match.Components
private OsuSpriteText typeLabel;
private ProcessingOverlay processingOverlay;
private DrawableRoomPlaylist playlist;
[Resolved(CanBeNull = true)]
private IRoomManager manager { get; set; }
@ -123,6 +129,26 @@ namespace osu.Game.Screens.Multi.Match.Components
OnCommit = (sender, text) => apply(),
},
},
new Section("Duration")
{
Child = DurationField = new DurationDropdown
{
RelativeSizeAxes = Axes.X,
Items = new[]
{
TimeSpan.FromMinutes(30),
TimeSpan.FromHours(1),
TimeSpan.FromHours(2),
TimeSpan.FromHours(4),
TimeSpan.FromHours(8),
TimeSpan.FromHours(12),
//TimeSpan.FromHours(16),
TimeSpan.FromHours(24),
TimeSpan.FromDays(3),
TimeSpan.FromDays(7)
}
}
},
new Section("Room visibility")
{
Alpha = disabled_alpha,
@ -155,15 +181,6 @@ namespace osu.Game.Screens.Multi.Match.Components
},
},
},
},
},
new SectionContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Padding = new MarginPadding { Left = field_padding / 2 },
Children = new[]
{
new Section("Max participants")
{
Alpha = disabled_alpha,
@ -175,26 +192,6 @@ namespace osu.Game.Screens.Multi.Match.Components
OnCommit = (sender, text) => apply()
},
},
new Section("Duration")
{
Child = DurationField = new DurationDropdown
{
RelativeSizeAxes = Axes.X,
Items = new[]
{
TimeSpan.FromMinutes(30),
TimeSpan.FromHours(1),
TimeSpan.FromHours(2),
TimeSpan.FromHours(4),
TimeSpan.FromHours(8),
TimeSpan.FromHours(12),
//TimeSpan.FromHours(16),
TimeSpan.FromHours(24),
TimeSpan.FromDays(3),
TimeSpan.FromDays(7)
}
}
},
new Section("Password (optional)")
{
Alpha = disabled_alpha,
@ -208,6 +205,45 @@ namespace osu.Game.Screens.Multi.Match.Components
},
},
},
new SectionContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Padding = new MarginPadding { Left = field_padding / 2 },
Children = new[]
{
new Section("Playlist")
{
Child = new GridContainer
{
RelativeSizeAxes = Axes.X,
Height = 300,
Content = new[]
{
new Drawable[]
{
playlist = new DrawableRoomPlaylist(true, true) { RelativeSizeAxes = Axes.Both }
},
new Drawable[]
{
new OsuButton
{
RelativeSizeAxes = Axes.X,
Height = 40,
Text = "Edit playlist",
Action = () => EditPlaylist?.Invoke()
}
}
},
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
}
}
},
},
},
},
}
},
@ -271,6 +307,8 @@ namespace osu.Game.Screens.Multi.Match.Components
Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true);
MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true);
Duration.BindValueChanged(duration => DurationField.Current.Value = duration.NewValue, true);
playlist.Items.BindTo(Playlist);
}
protected override void Update()

View File

@ -1,66 +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.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osuTK.Graphics;
namespace osu.Game.Screens.Multi.Match.Components
{
public class MatchTabControl : PageTabControl<MatchPage>
{
[Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable<int?> roomId { get; set; }
public MatchTabControl()
{
AddItem(new RoomMatchPage());
AddItem(new SettingsMatchPage());
}
[BackgroundDependencyLoader]
private void load()
{
roomId.BindValueChanged(id =>
{
if (id.NewValue.HasValue)
{
Items.ForEach(t => t.Enabled.Value = !(t is SettingsMatchPage));
Current.Value = new RoomMatchPage();
}
else
{
Items.ForEach(t => t.Enabled.Value = t is SettingsMatchPage);
Current.Value = new SettingsMatchPage();
}
}, true);
}
protected override TabItem<MatchPage> CreateTabItem(MatchPage value) => new TabItem(value);
private class TabItem : PageTabItem
{
private readonly IBindable<bool> enabled = new BindableBool();
public TabItem(MatchPage value)
: base(value)
{
enabled.BindTo(value.Enabled);
enabled.BindValueChanged(enabled => Colour = enabled.NewValue ? Color4.White : Color4.Gray, true);
}
protected override bool OnClick(ClickEvent e)
{
if (!enabled.Value)
return true;
return base.OnClick(e);
}
}
}
}

View File

@ -0,0 +1,87 @@
// 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.Graphics;
using osu.Game.Graphics.Sprites;
using osuTK;
namespace osu.Game.Screens.Multi.Match.Components
{
public abstract class OverlinedDisplay : MultiplayerComposite
{
protected readonly Container Content;
protected string Details
{
set => details.Text = value;
}
private readonly Circle line;
private readonly OsuSpriteText details;
protected OverlinedDisplay(string title)
{
RelativeSizeAxes = Axes.Both;
InternalChild = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
line = new Circle
{
RelativeSizeAxes = Axes.X,
Height = 2,
Margin = new MarginPadding { Bottom = 2 }
},
},
new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Margin = new MarginPadding { Top = 5 },
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
new OsuSpriteText
{
Text = title,
Font = OsuFont.GetFont(size: 14)
},
details = new OsuSpriteText { Font = OsuFont.GetFont(size: 14) },
}
},
},
new Drawable[]
{
Content = new Container
{
Margin = new MarginPadding { Top = 5 },
RelativeSizeAxes = Axes.Both
}
}
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
}
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
line.Colour = colours.Yellow;
details.Colour = colours.Yellow;
}
}
}

View File

@ -0,0 +1,29 @@
// 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.Game.Screens.Multi.Components;
namespace osu.Game.Screens.Multi.Match.Components
{
public class OverlinedParticipants : OverlinedDisplay
{
public OverlinedParticipants()
: base("Participants")
{
Content.Add(new ParticipantsList { RelativeSizeAxes = Axes.Both });
}
[BackgroundDependencyLoader]
private void load()
{
ParticipantCount.BindValueChanged(_ => setParticipantCount());
MaxParticipants.BindValueChanged(_ => setParticipantCount());
setParticipantCount();
}
private void setParticipantCount() => Details = MaxParticipants.Value != null ? $"{ParticipantCount.Value}/{MaxParticipants.Value}" : ParticipantCount.Value.ToString();
}
}

View File

@ -0,0 +1,33 @@
// 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.Game.Online.Multiplayer;
namespace osu.Game.Screens.Multi.Match.Components
{
public class OverlinedPlaylist : OverlinedDisplay
{
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
private readonly DrawableRoomPlaylist playlist;
public OverlinedPlaylist(bool allowSelection)
: base("Playlist")
{
Content.Add(playlist = new DrawableRoomPlaylist(false, allowSelection)
{
RelativeSizeAxes = Axes.Both,
SelectedItem = { BindTarget = SelectedItem }
});
}
[BackgroundDependencyLoader]
private void load()
{
playlist.Items.BindTo(Playlist);
}
}
}

View File

@ -1,77 +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.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Containers;
using osu.Game.Overlays.SearchableList;
using osu.Game.Screens.Multi.Components;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Screens.Multi.Match.Components
{
public class Participants : MultiplayerComposite
{
[BackgroundDependencyLoader]
private void load()
{
FillFlowContainer<UserPanel> usersFlow;
InternalChild = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new OsuScrollContainer
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 10 },
Children = new Drawable[]
{
new ParticipantCountDisplay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
},
usersFlow = new FillFlowContainer<UserPanel>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(5),
Padding = new MarginPadding { Top = 40 },
LayoutDuration = 200,
LayoutEasing = Easing.OutQuint,
},
},
},
},
};
Participants.ItemsAdded += users =>
{
usersFlow.AddRange(users.Select(u =>
{
var panel = new UserPanel(u)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 300,
};
panel.OnLoadComplete += d => d.FadeInFromZero(60);
return panel;
}).ToList());
};
Participants.ItemsRemoved += users =>
{
usersFlow.RemoveAll(p => users.Contains(p.User));
};
}
}
}

View File

@ -5,16 +5,15 @@ using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osuTK;
namespace osu.Game.Screens.Multi.Match.Components
{
public class ReadyButton : HeaderButton
public class ReadyButton : OsuButton
{
public readonly Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
[Resolved(typeof(Room), nameof(Room.EndDate))]
private Bindable<DateTimeOffset> endDate { get; set; }
@ -29,9 +28,6 @@ namespace osu.Game.Screens.Multi.Match.Components
public ReadyButton()
{
RelativeSizeAxes = Axes.Y;
Size = new Vector2(200, 1);
Text = "Start";
}
@ -41,31 +37,37 @@ namespace osu.Game.Screens.Multi.Match.Components
beatmaps.ItemAdded += beatmapAdded;
beatmaps.ItemRemoved += beatmapRemoved;
Beatmap.BindValueChanged(b => updateBeatmap(b.NewValue), true);
SelectedItem.BindValueChanged(item => updateSelectedItem(item.NewValue), true);
}
private void updateBeatmap(BeatmapInfo beatmap)
private void updateSelectedItem(PlaylistItem item)
{
hasBeatmap = false;
if (beatmap?.OnlineBeatmapID == null)
int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
if (beatmapId == null)
return;
hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID) != null;
hasBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == beatmapId) != null;
}
private void beatmapAdded(BeatmapSetInfo model)
{
if (model.Beatmaps.Any(b => b.OnlineBeatmapID == Beatmap.Value.OnlineBeatmapID))
int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
if (beatmapId == null)
return;
if (model.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId))
Schedule(() => hasBeatmap = true);
}
private void beatmapRemoved(BeatmapSetInfo model)
{
if (Beatmap.Value == null)
int? beatmapId = SelectedItem.Value?.Beatmap.Value?.OnlineBeatmapID;
if (beatmapId == null)
return;
if (model.OnlineBeatmapSetID == Beatmap.Value.BeatmapSet.OnlineBeatmapSetID)
if (model.Beatmaps.Any(b => b.OnlineBeatmapID == beatmapId))
Schedule(() => hasBeatmap = false);
}
@ -78,7 +80,7 @@ namespace osu.Game.Screens.Multi.Match.Components
private void updateEnabledState()
{
if (gameBeatmap.Value == null)
if (gameBeatmap.Value == null || SelectedItem.Value == null)
{
Enabled.Value = false;
return;
@ -94,7 +96,10 @@ namespace osu.Game.Screens.Multi.Match.Components
base.Dispose(isDisposing);
if (beatmaps != null)
{
beatmaps.ItemAdded -= beatmapAdded;
beatmaps.ItemRemoved -= beatmapRemoved;
}
}
}
}

View File

@ -5,18 +5,23 @@ using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.GameTypes;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Multi.Components;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Screens.Multi.Play;
using osu.Game.Screens.Select;
using PlaylistItem = osu.Game.Online.Multiplayer.PlaylistItem;
using osuTK.Graphics;
using Footer = osu.Game.Screens.Multi.Match.Components.Footer;
namespace osu.Game.Screens.Multi.Match
{
@ -32,26 +37,22 @@ namespace osu.Game.Screens.Multi.Match
[Resolved(typeof(Room), nameof(Room.RoomID))]
private Bindable<int?> roomId { get; set; }
[Resolved(typeof(Room), nameof(Room.Name))]
private Bindable<string> name { get; set; }
[Resolved(typeof(Room), nameof(Room.Type))]
private Bindable<GameType> type { get; set; }
[Resolved(typeof(Room))]
protected BindableList<PlaylistItem> Playlist { get; private set; }
[Resolved(typeof(Room), nameof(Room.Playlist))]
private BindableList<PlaylistItem> playlist { get; set; }
[Resolved]
private BeatmapManager beatmapManager { get; set; }
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; }
[Resolved(canBeNull: true)]
private Multiplayer multiplayer { get; set; }
[Resolved(CanBeNull = true)]
private OsuGame game { get; set; }
protected readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
private readonly Bindable<PlaylistItem> selectedItem = new Bindable<PlaylistItem>();
private MatchLeaderboard leaderboard;
private LeaderboardChatDisplay leaderboardChatDisplay;
private MatchSettingsOverlay settingsOverlay;
public MatchSubScreen(Room room)
{
@ -61,13 +62,14 @@ namespace osu.Game.Screens.Multi.Match
[BackgroundDependencyLoader]
private void load()
{
Components.Header header;
Info info;
GridContainer bottomRow;
MatchSettingsOverlay settings;
InternalChildren = new Drawable[]
{
new HeaderBackgroundSprite
{
RelativeSizeAxes = Axes.X,
Height = 200,
Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.4f), Color4.White.Opacity(0))
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
@ -75,167 +77,169 @@ namespace osu.Game.Screens.Multi.Match
{
new Drawable[]
{
header = new Components.Header
new Container
{
Depth = -1,
RequestBeatmapSelection = () =>
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding
{
this.Push(new MatchSongSelect
Horizontal = 105,
Vertical = 20
},
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
Selected = item =>
new Drawable[] { new Components.Header() },
new Drawable[]
{
Playlist.Clear();
Playlist.Add(item);
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 65 },
Child = new GridContainer
{
ColumnDimensions = new[]
{
new Dimension(minSize: 160),
new Dimension(minSize: 360),
new Dimension(minSize: 400),
},
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 5 },
Child = new OverlinedParticipants()
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 5 },
Child = new OverlinedPlaylist(true) // Temporarily always allow selection
{
SelectedItem = { BindTarget = SelectedItem }
}
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = 5 },
Child = leaderboardChatDisplay = new LeaderboardChatDisplay()
}
},
}
}
}
}
});
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
}
}
}
},
new Drawable[] { info = new Info { OnStart = onStart } },
new Drawable[]
{
bottomRow = new GridContainer
new Footer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
leaderboard = new MatchLeaderboard
{
Padding = new MarginPadding
{
Left = 10 + HORIZONTAL_OVERFLOW_PADDING,
Right = 10,
Vertical = 10,
},
RelativeSizeAxes = Axes.Both
},
new Container
{
Padding = new MarginPadding
{
Left = 10,
Right = 10 + HORIZONTAL_OVERFLOW_PADDING,
Vertical = 10,
},
RelativeSizeAxes = Axes.Both,
Child = new MatchChatDisplay
{
RelativeSizeAxes = Axes.Both
}
},
},
},
OnStart = onStart,
SelectedItem = { BindTarget = SelectedItem }
}
},
}
},
RowDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Distributed),
}
},
new Container
settingsOverlay = new MatchSettingsOverlay
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = Components.Header.HEIGHT },
Child = settings = new MatchSettingsOverlay { RelativeSizeAxes = Axes.Both },
},
EditPlaylist = () => this.Push(new MatchSongSelect()),
State = { Value = roomId.Value == null ? Visibility.Visible : Visibility.Hidden }
}
};
header.Tabs.Current.BindValueChanged(tab =>
{
const float fade_duration = 500;
var settingsDisplayed = tab.NewValue is SettingsMatchPage;
header.ShowBeatmapPanel.Value = !settingsDisplayed;
settings.State.Value = settingsDisplayed ? Visibility.Visible : Visibility.Hidden;
info.FadeTo(settingsDisplayed ? 0 : 1, fade_duration, Easing.OutQuint);
bottomRow.FadeTo(settingsDisplayed ? 0 : 1, fade_duration, Easing.OutQuint);
}, true);
beatmapManager.ItemAdded += beatmapAdded;
}
protected override void LoadComplete()
{
base.LoadComplete();
Playlist.ItemsAdded += _ => Scheduler.AddOnce(updateSelectedItem);
Playlist.ItemsRemoved += _ => Scheduler.AddOnce(updateSelectedItem);
roomId.BindValueChanged(id =>
{
if (id.NewValue == null)
settingsOverlay.Show();
else
{
settingsOverlay.Hide();
updateSelectedItem();
}
// Set the first playlist item.
// This is scheduled since updating the room and playlist may happen in an arbitrary order (via Room.CopyFrom()).
Schedule(() => SelectedItem.Value = playlist.FirstOrDefault());
}
}, true);
private void updateSelectedItem()
{
selectedItem.Value = Playlist.FirstOrDefault();
currentItemChanged();
SelectedItem.BindValueChanged(_ => Scheduler.AddOnce(selectedItemChanged));
SelectedItem.Value = playlist.FirstOrDefault();
beatmapManager.ItemAdded += beatmapAdded;
}
public override bool OnExiting(IScreen next)
{
RoomManager?.PartRoom();
Mods.Value = Array.Empty<Mod>();
previewTrackManager.StopAnyPlaying(this);
return base.OnExiting(next);
}
/// <summary>
/// Handles propagation of the current playlist item's content to game-wide mechanisms.
/// </summary>
private void currentItemChanged()
private void selectedItemChanged()
{
var item = selectedItem.Value;
updateWorkingBeatmap();
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
var localBeatmap = item?.Beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == item.Beatmap.Value.OnlineBeatmapID);
var item = SelectedItem.Value;
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
Mods.Value = item?.RequiredMods?.ToArray() ?? Array.Empty<Mod>();
if (item?.Ruleset != null)
Ruleset.Value = item.Ruleset.Value;
previewTrackManager.StopAnyPlaying(this);
}
/// <summary>
/// Handle the case where a beatmap is imported (and can be used by this match).
/// </summary>
private void updateWorkingBeatmap()
{
var beatmap = SelectedItem.Value?.Beatmap.Value;
// Retrieve the corresponding local beatmap, since we can't directly use the playlist's beatmap info
var localBeatmap = beatmap == null ? null : beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == beatmap.OnlineBeatmapID);
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
}
private void beatmapAdded(BeatmapSetInfo model) => Schedule(() =>
{
if (Beatmap.Value != beatmapManager.DefaultBeatmap)
return;
if (selectedItem.Value == null)
return;
// Try to retrieve the corresponding local beatmap
var localBeatmap = beatmapManager.QueryBeatmap(b => b.OnlineBeatmapID == selectedItem.Value.Beatmap.Value.OnlineBeatmapID);
if (localBeatmap != null)
Beatmap.Value = beatmapManager.GetWorkingBeatmap(localBeatmap);
updateWorkingBeatmap();
});
[Resolved(canBeNull: true)]
private Multiplayer multiplayer { get; set; }
private void onStart()
{
previewTrackManager.StopAnyPlaying(this);
switch (type.Value)
{
default:
case GameTypeTimeshift _:
multiplayer?.Start(() => new TimeshiftPlayer(selectedItem.Value)
multiplayer?.Start(() => new TimeshiftPlayer(SelectedItem.Value)
{
Exited = () => leaderboard.RefreshScores()
Exited = () => leaderboardChatDisplay.RefreshScores()
});
break;
}
@ -248,5 +252,15 @@ namespace osu.Game.Screens.Multi.Match
if (beatmapManager != null)
beatmapManager.ItemAdded -= beatmapAdded;
}
private class HeaderBackgroundSprite : MultiplayerBackgroundSprite
{
protected override UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new BackgroundSprite { RelativeSizeAxes = Axes.Both };
private class BackgroundSprite : UpdateableBeatmapBackgroundSprite
{
protected override double TransformDuration => 200;
}
}
}
}