mirror of
https://github.com/ppy/osu.git
synced 2025-02-15 10:22:56 +08:00
Merge pull request #13861 from peppy/add-password-support
Add multiplayer room password support
This commit is contained in:
commit
844152e1b1
@ -4,13 +4,11 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Rulesets.Catch;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
@ -29,7 +27,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
JoinRequested = joinRequested
|
||||
};
|
||||
});
|
||||
|
||||
@ -43,11 +40,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddAssert("has 2 rooms", () => container.Rooms.Count == 2);
|
||||
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0));
|
||||
|
||||
AddStep("select first room", () => container.Rooms.First().Action?.Invoke());
|
||||
AddStep("select first room", () => container.Rooms.First().Click());
|
||||
AddAssert("first room selected", () => checkRoomSelected(RoomManager.Rooms.First()));
|
||||
|
||||
AddStep("join first room", () => container.Rooms.First().Action?.Invoke());
|
||||
AddAssert("first room joined", () => RoomManager.Rooms.First().Status.Value is JoinedRoomStatus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -66,9 +60,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
press(Key.Down);
|
||||
press(Key.Down);
|
||||
AddAssert("last room selected", () => checkRoomSelected(RoomManager.Rooms.Last()));
|
||||
|
||||
press(Key.Enter);
|
||||
AddAssert("last room joined", () => RoomManager.Rooms.Last().Status.Value is JoinedRoomStatus);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -123,15 +114,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("3 rooms visible", () => container.Rooms.Count(r => r.IsPresent) == 3);
|
||||
}
|
||||
|
||||
private bool checkRoomSelected(Room room) => SelectedRoom.Value == room;
|
||||
|
||||
private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus();
|
||||
|
||||
private class JoinedRoomStatus : RoomStatus
|
||||
[Test]
|
||||
public void TestPasswordProtectedRooms()
|
||||
{
|
||||
public override string Message => "Joined";
|
||||
|
||||
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Yellow;
|
||||
AddStep("add rooms", () => RoomManager.AddRooms(3, withPassword: true));
|
||||
}
|
||||
|
||||
private bool checkRoomSelected(Room room) => SelectedRoom.Value == room;
|
||||
}
|
||||
}
|
||||
|
@ -7,6 +7,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
@ -22,6 +23,7 @@ using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens;
|
||||
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.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
|
||||
@ -85,6 +87,124 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
// used to test the flow of multiplayer from visual tests.
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCreateRoomWithoutPassword()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestJoinRoomWithoutPassword()
|
||||
{
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
API.Queue(new CreateRoomRequest(new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria());
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("join room", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||
AddUntilStep("wait for join", () => client.Room != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCreateRoomWithPassword()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
Password = { Value = "password" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddAssert("room has password", () => client.APIRoom?.Password.Value == "password");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestJoinRoomWithPassword()
|
||||
{
|
||||
AddStep("create room", () =>
|
||||
{
|
||||
API.Queue(new CreateRoomRequest(new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
Password = { Value = "password" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
AddStep("refresh rooms", () => multiplayerScreen.RoomManager.Filter.Value = new FilterCriteria());
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("join room", () => InputManager.Key(Key.Enter));
|
||||
|
||||
DrawableRoom.PasswordEntryPopover passwordEntryPopover = null;
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
|
||||
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().Click());
|
||||
|
||||
AddUntilStep("wait for room open", () => this.ChildrenOfType<MultiplayerMatchSubScreen>().FirstOrDefault()?.IsLoaded == true);
|
||||
AddUntilStep("wait for join", () => client.Room != null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLocalPasswordUpdatedWhenMultiplayerSettingsChange()
|
||||
{
|
||||
createRoom(() => new Room
|
||||
{
|
||||
Name = { Value = "Test Room" },
|
||||
Password = { Value = "password" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem
|
||||
{
|
||||
Beatmap = { Value = beatmaps.GetWorkingBeatmap(importedSet.Beatmaps.First(b => b.RulesetID == 0)).BeatmapInfo },
|
||||
Ruleset = { Value = new OsuRuleset().RulesetInfo },
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("change password", () => client.ChangeSettings(password: "password2"));
|
||||
AddUntilStep("local password changed", () => client.APIRoom?.Password.Value == "password2");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestUserSetToIdleWhenBeatmapDeleted()
|
||||
{
|
||||
|
@ -0,0 +1,89 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneMultiplayerLoungeSubScreen : OnlinePlayTestScene
|
||||
{
|
||||
protected new BasicTestRoomManager RoomManager => (BasicTestRoomManager)base.RoomManager;
|
||||
|
||||
private LoungeSubScreen loungeScreen;
|
||||
|
||||
private Room lastJoinedRoom;
|
||||
private string lastJoinedPassword;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("push screen", () => LoadScreen(loungeScreen = new MultiplayerLoungeSubScreen()));
|
||||
|
||||
AddUntilStep("wait for present", () => loungeScreen.IsCurrentScreen());
|
||||
|
||||
AddStep("bind to event", () =>
|
||||
{
|
||||
lastJoinedRoom = null;
|
||||
lastJoinedPassword = null;
|
||||
RoomManager.JoinRoomRequested = onRoomJoined;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestJoinRoomWithoutPassword()
|
||||
{
|
||||
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: false));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("join room", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First());
|
||||
AddAssert("room join password correct", () => lastJoinedPassword == null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPopoverHidesOnLeavingScreen()
|
||||
{
|
||||
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("password prompt appeared", () => InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().Any());
|
||||
AddStep("exit screen", () => Stack.Exit());
|
||||
AddUntilStep("password prompt hidden", () => !InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().Any());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestJoinRoomWithPassword()
|
||||
{
|
||||
DrawableRoom.PasswordEntryPopover passwordEntryPopover = null;
|
||||
|
||||
AddStep("add room", () => RoomManager.AddRooms(1, withPassword: true));
|
||||
AddStep("select room", () => InputManager.Key(Key.Down));
|
||||
AddStep("attempt join room", () => InputManager.Key(Key.Enter));
|
||||
AddUntilStep("password prompt appeared", () => (passwordEntryPopover = InputManager.ChildrenOfType<DrawableRoom.PasswordEntryPopover>().FirstOrDefault()) != null);
|
||||
AddStep("enter password in text box", () => passwordEntryPopover.ChildrenOfType<TextBox>().First().Text = "password");
|
||||
AddStep("press join room button", () => passwordEntryPopover.ChildrenOfType<OsuButton>().First().Click());
|
||||
|
||||
AddAssert("room join requested", () => lastJoinedRoom == RoomManager.Rooms.First());
|
||||
AddAssert("room join password correct", () => lastJoinedPassword == "password");
|
||||
}
|
||||
|
||||
private void onRoomJoined(Room room, string password)
|
||||
{
|
||||
lastJoinedRoom = room;
|
||||
lastJoinedPassword = password;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,8 +2,12 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Online.Rooms.RoomStatuses;
|
||||
using osu.Game.Screens.OnlinePlay.Lounge.Components;
|
||||
@ -12,40 +16,66 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public class TestSceneRoomStatus : OsuTestScene
|
||||
{
|
||||
public TestSceneRoomStatus()
|
||||
[Test]
|
||||
public void TestMultipleStatuses()
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
AddStep("create rooms", () =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0.5f,
|
||||
Children = new Drawable[]
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
new DrawableRoom(new Room
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 0.5f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Name = { Value = "Open - ending in 1 day" },
|
||||
Status = { Value = new RoomStatusOpen() },
|
||||
EndDate = { Value = DateTimeOffset.Now.AddDays(1) }
|
||||
}) { MatchingFilter = true },
|
||||
new DrawableRoom(new Room
|
||||
{
|
||||
Name = { Value = "Playing - ending in 1 day" },
|
||||
Status = { Value = new RoomStatusPlaying() },
|
||||
EndDate = { Value = DateTimeOffset.Now.AddDays(1) }
|
||||
}) { MatchingFilter = true },
|
||||
new DrawableRoom(new Room
|
||||
{
|
||||
Name = { Value = "Ended" },
|
||||
Status = { Value = new RoomStatusEnded() },
|
||||
EndDate = { Value = DateTimeOffset.Now }
|
||||
}) { MatchingFilter = true },
|
||||
new DrawableRoom(new Room
|
||||
{
|
||||
Name = { Value = "Open" },
|
||||
Status = { Value = new RoomStatusOpen() },
|
||||
Category = { Value = RoomCategory.Realtime }
|
||||
}) { MatchingFilter = true },
|
||||
}
|
||||
};
|
||||
new DrawableRoom(new Room
|
||||
{
|
||||
Name = { Value = "Open - ending in 1 day" },
|
||||
Status = { Value = new RoomStatusOpen() },
|
||||
EndDate = { Value = DateTimeOffset.Now.AddDays(1) }
|
||||
}) { MatchingFilter = true },
|
||||
new DrawableRoom(new Room
|
||||
{
|
||||
Name = { Value = "Playing - ending in 1 day" },
|
||||
Status = { Value = new RoomStatusPlaying() },
|
||||
EndDate = { Value = DateTimeOffset.Now.AddDays(1) }
|
||||
}) { MatchingFilter = true },
|
||||
new DrawableRoom(new Room
|
||||
{
|
||||
Name = { Value = "Ended" },
|
||||
Status = { Value = new RoomStatusEnded() },
|
||||
EndDate = { Value = DateTimeOffset.Now }
|
||||
}) { MatchingFilter = true },
|
||||
new DrawableRoom(new Room
|
||||
{
|
||||
Name = { Value = "Open" },
|
||||
Status = { Value = new RoomStatusOpen() },
|
||||
Category = { Value = RoomCategory.Realtime }
|
||||
}) { MatchingFilter = true },
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestEnableAndDisablePassword()
|
||||
{
|
||||
DrawableRoom drawableRoom = null;
|
||||
Room room = null;
|
||||
|
||||
AddStep("create room", () => Child = drawableRoom = new DrawableRoom(room = new Room
|
||||
{
|
||||
Name = { Value = "Room with password" },
|
||||
Status = { Value = new RoomStatusOpen() },
|
||||
Category = { Value = RoomCategory.Realtime },
|
||||
}) { MatchingFilter = true });
|
||||
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||
|
||||
AddStep("set password", () => room.Password.Value = "password");
|
||||
AddAssert("password icon visible", () => Precision.AlmostEquals(1, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||
|
||||
AddStep("unset password", () => room.Password.Value = string.Empty);
|
||||
AddAssert("password icon hidden", () => Precision.AlmostEquals(0, drawableRoom.ChildrenOfType<DrawableRoom.PasswordProtectedIcon>().Single().Alpha));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
|
||||
AddUntilStep("first room is not masked", () => checkRoomVisible(roomsContainer.Rooms.First()));
|
||||
|
||||
AddStep("select last room", () => roomsContainer.Rooms.Last().Action?.Invoke());
|
||||
AddStep("select last room", () => roomsContainer.Rooms.Last().Click());
|
||||
|
||||
AddUntilStep("first room is masked", () => !checkRoomVisible(roomsContainer.Rooms.First()));
|
||||
AddUntilStep("last room is not masked", () => checkRoomVisible(roomsContainer.Rooms.Last()));
|
||||
|
@ -152,7 +152,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
onSuccess?.Invoke(room);
|
||||
}
|
||||
|
||||
public void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null) => throw new NotImplementedException();
|
||||
public void JoinRoom(Room room, string password, Action<Room> onSuccess = null, Action<string> onError = null) => throw new NotImplementedException();
|
||||
|
||||
public void PartRoom() => throw new NotImplementedException();
|
||||
}
|
||||
|
@ -15,6 +15,16 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// </summary>
|
||||
/// <param name="roomId">The databased room ID.</param>
|
||||
/// <exception cref="InvalidStateException">If the user is already in the requested (or another) room.</exception>
|
||||
/// <exception cref="InvalidPasswordException">If the room required a password.</exception>
|
||||
Task<MultiplayerRoom> JoinRoom(long roomId);
|
||||
|
||||
/// <summary>
|
||||
/// Request to join a multiplayer room with a provided password.
|
||||
/// </summary>
|
||||
/// <param name="roomId">The databased room ID.</param>
|
||||
/// <param name="password">The password for the join request.</param>
|
||||
/// <exception cref="InvalidStateException">If the user is already in the requested (or another) room.</exception>
|
||||
/// <exception cref="InvalidPasswordException">If the room provided password was incorrect.</exception>
|
||||
Task<MultiplayerRoom> JoinRoomWithPassword(long roomId, string password);
|
||||
}
|
||||
}
|
||||
|
22
osu.Game/Online/Multiplayer/InvalidPasswordException.cs
Normal file
22
osu.Game/Online/Multiplayer/InvalidPasswordException.cs
Normal file
@ -0,0 +1,22 @@
|
||||
// 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.Runtime.Serialization;
|
||||
using Microsoft.AspNetCore.SignalR;
|
||||
|
||||
namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
[Serializable]
|
||||
public class InvalidPasswordException : HubException
|
||||
{
|
||||
public InvalidPasswordException()
|
||||
{
|
||||
}
|
||||
|
||||
protected InvalidPasswordException(SerializationInfo info, StreamingContext context)
|
||||
: base(info, context)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -92,7 +92,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
[Resolved]
|
||||
private UserLookupCache userLookupCache { get; set; } = null!;
|
||||
|
||||
private Room? apiRoom;
|
||||
protected Room? APIRoom { get; private set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -115,7 +115,8 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// Joins the <see cref="MultiplayerRoom"/> for a given API <see cref="Room"/>.
|
||||
/// </summary>
|
||||
/// <param name="room">The API <see cref="Room"/>.</param>
|
||||
public async Task JoinRoom(Room room)
|
||||
/// <param name="password">An optional password to use for the join operation.</param>
|
||||
public async Task JoinRoom(Room room, string? password = null)
|
||||
{
|
||||
var cancellationSource = joinCancellationSource = new CancellationTokenSource();
|
||||
|
||||
@ -127,7 +128,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
Debug.Assert(room.RoomID.Value != null);
|
||||
|
||||
// Join the server-side room.
|
||||
var joinedRoom = await JoinRoom(room.RoomID.Value.Value).ConfigureAwait(false);
|
||||
var joinedRoom = await JoinRoom(room.RoomID.Value.Value, password ?? room.Password.Value).ConfigureAwait(false);
|
||||
Debug.Assert(joinedRoom != null);
|
||||
|
||||
// Populate users.
|
||||
@ -138,7 +139,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
await scheduleAsync(() =>
|
||||
{
|
||||
Room = joinedRoom;
|
||||
apiRoom = room;
|
||||
APIRoom = room;
|
||||
foreach (var user in joinedRoom.Users)
|
||||
updateUserPlayingState(user.UserID, user.State);
|
||||
}, cancellationSource.Token).ConfigureAwait(false);
|
||||
@ -152,8 +153,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// Joins the <see cref="MultiplayerRoom"/> with a given ID.
|
||||
/// </summary>
|
||||
/// <param name="roomId">The room ID.</param>
|
||||
/// <param name="password">An optional password to use when joining the room.</param>
|
||||
/// <returns>The joined <see cref="MultiplayerRoom"/>.</returns>
|
||||
protected abstract Task<MultiplayerRoom> JoinRoom(long roomId);
|
||||
protected abstract Task<MultiplayerRoom> JoinRoom(long roomId, string? password = null);
|
||||
|
||||
public Task LeaveRoom()
|
||||
{
|
||||
@ -166,7 +168,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
// For example, if a room was left and the user immediately pressed the "create room" button, then the user could be taken into the lobby if the value of Room is not reset in time.
|
||||
var scheduledReset = scheduleAsync(() =>
|
||||
{
|
||||
apiRoom = null;
|
||||
APIRoom = null;
|
||||
Room = null;
|
||||
CurrentMatchPlayingUserIds.Clear();
|
||||
|
||||
@ -189,8 +191,9 @@ namespace osu.Game.Online.Multiplayer
|
||||
/// A room must be joined for this to have any effect.
|
||||
/// </remarks>
|
||||
/// <param name="name">The new room name, if any.</param>
|
||||
/// <param name="password">The new password, if any.</param>
|
||||
/// <param name="item">The new room playlist item, if any.</param>
|
||||
public Task ChangeSettings(Optional<string> name = default, Optional<PlaylistItem> item = default)
|
||||
public Task ChangeSettings(Optional<string> name = default, Optional<string> password = default, Optional<PlaylistItem> item = default)
|
||||
{
|
||||
if (Room == null)
|
||||
throw new InvalidOperationException("Must be joined to a match to change settings.");
|
||||
@ -212,6 +215,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
return ChangeSettings(new MultiplayerRoomSettings
|
||||
{
|
||||
Name = name.GetOr(Room.Settings.Name),
|
||||
Password = password.GetOr(Room.Settings.Password),
|
||||
BeatmapID = item.GetOr(existingPlaylistItem).BeatmapID,
|
||||
BeatmapChecksum = item.GetOr(existingPlaylistItem).Beatmap.Value.MD5Hash,
|
||||
RulesetID = item.GetOr(existingPlaylistItem).RulesetID,
|
||||
@ -301,22 +305,22 @@ namespace osu.Game.Online.Multiplayer
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(apiRoom != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
Room.State = state;
|
||||
|
||||
switch (state)
|
||||
{
|
||||
case MultiplayerRoomState.Open:
|
||||
apiRoom.Status.Value = new RoomStatusOpen();
|
||||
APIRoom.Status.Value = new RoomStatusOpen();
|
||||
break;
|
||||
|
||||
case MultiplayerRoomState.Playing:
|
||||
apiRoom.Status.Value = new RoomStatusPlaying();
|
||||
APIRoom.Status.Value = new RoomStatusPlaying();
|
||||
break;
|
||||
|
||||
case MultiplayerRoomState.Closed:
|
||||
apiRoom.Status.Value = new RoomStatusEnded();
|
||||
APIRoom.Status.Value = new RoomStatusEnded();
|
||||
break;
|
||||
}
|
||||
|
||||
@ -377,12 +381,12 @@ namespace osu.Game.Online.Multiplayer
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(apiRoom != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
var user = Room.Users.FirstOrDefault(u => u.UserID == userId);
|
||||
|
||||
Room.Host = user;
|
||||
apiRoom.Host.Value = user?.User;
|
||||
APIRoom.Host.Value = user?.User;
|
||||
|
||||
RoomUpdated?.Invoke();
|
||||
}, false);
|
||||
@ -525,11 +529,12 @@ namespace osu.Game.Online.Multiplayer
|
||||
if (Room == null)
|
||||
return;
|
||||
|
||||
Debug.Assert(apiRoom != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
// Update a few properties of the room instantaneously.
|
||||
Room.Settings = settings;
|
||||
apiRoom.Name.Value = Room.Settings.Name;
|
||||
APIRoom.Name.Value = Room.Settings.Name;
|
||||
APIRoom.Password.Value = Room.Settings.Password;
|
||||
|
||||
// The current item update is delayed until an online beatmap lookup (below) succeeds.
|
||||
// In-order for the client to not display an outdated beatmap, the current item is forcefully cleared here.
|
||||
@ -551,7 +556,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
if (Room == null || !Room.Settings.Equals(settings))
|
||||
return;
|
||||
|
||||
Debug.Assert(apiRoom != null);
|
||||
Debug.Assert(APIRoom != null);
|
||||
|
||||
var beatmap = beatmapSet.Beatmaps.Single(b => b.OnlineBeatmapID == settings.BeatmapID);
|
||||
beatmap.MD5Hash = settings.BeatmapChecksum;
|
||||
@ -561,7 +566,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
var allowedMods = settings.AllowedMods.Select(m => m.ToMod(ruleset));
|
||||
|
||||
// Try to retrieve the existing playlist item from the API room.
|
||||
var playlistItem = apiRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId);
|
||||
var playlistItem = APIRoom.Playlist.FirstOrDefault(i => i.ID == settings.PlaylistItemId);
|
||||
|
||||
if (playlistItem != null)
|
||||
updateItem(playlistItem);
|
||||
@ -569,7 +574,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
{
|
||||
// An existing playlist item does not exist, so append a new one.
|
||||
updateItem(playlistItem = new PlaylistItem());
|
||||
apiRoom.Playlist.Add(playlistItem);
|
||||
APIRoom.Playlist.Add(playlistItem);
|
||||
}
|
||||
|
||||
CurrentMatchPlayingItem.Value = playlistItem;
|
||||
|
@ -36,12 +36,16 @@ namespace osu.Game.Online.Multiplayer
|
||||
[Key(6)]
|
||||
public long PlaylistItemId { get; set; }
|
||||
|
||||
[Key(7)]
|
||||
public string Password { get; set; } = string.Empty;
|
||||
|
||||
public bool Equals(MultiplayerRoomSettings other)
|
||||
=> BeatmapID == other.BeatmapID
|
||||
&& BeatmapChecksum == other.BeatmapChecksum
|
||||
&& RequiredMods.SequenceEqual(other.RequiredMods)
|
||||
&& AllowedMods.SequenceEqual(other.AllowedMods)
|
||||
&& RulesetID == other.RulesetID
|
||||
&& Password.Equals(other.Password, StringComparison.Ordinal)
|
||||
&& Name.Equals(other.Name, StringComparison.Ordinal)
|
||||
&& PlaylistItemId == other.PlaylistItemId;
|
||||
|
||||
@ -49,6 +53,7 @@ namespace osu.Game.Online.Multiplayer
|
||||
+ $" Beatmap:{BeatmapID} ({BeatmapChecksum})"
|
||||
+ $" RequiredMods:{string.Join(',', RequiredMods)}"
|
||||
+ $" AllowedMods:{string.Join(',', AllowedMods)}"
|
||||
+ $" Password:{(string.IsNullOrEmpty(Password) ? "no" : "yes")}"
|
||||
+ $" Ruleset:{RulesetID}"
|
||||
+ $" Item:{PlaylistItemId}";
|
||||
}
|
||||
|
@ -62,12 +62,12 @@ namespace osu.Game.Online.Multiplayer
|
||||
}
|
||||
}
|
||||
|
||||
protected override Task<MultiplayerRoom> JoinRoom(long roomId)
|
||||
protected override Task<MultiplayerRoom> JoinRoom(long roomId, string? password = null)
|
||||
{
|
||||
if (!IsConnected.Value)
|
||||
return Task.FromCanceled<MultiplayerRoom>(new CancellationToken(true));
|
||||
|
||||
return connection.InvokeAsync<MultiplayerRoom>(nameof(IMultiplayerServer.JoinRoom), roomId);
|
||||
return connection.InvokeAsync<MultiplayerRoom>(nameof(IMultiplayerServer.JoinRoomWithPassword), roomId, password ?? string.Empty);
|
||||
}
|
||||
|
||||
protected override Task LeaveRoomInternal()
|
||||
|
@ -9,11 +9,13 @@ namespace osu.Game.Online.Rooms
|
||||
{
|
||||
public class JoinRoomRequest : APIRequest
|
||||
{
|
||||
private readonly Room room;
|
||||
public readonly Room Room;
|
||||
public readonly string Password;
|
||||
|
||||
public JoinRoomRequest(Room room)
|
||||
public JoinRoomRequest(Room room, string password)
|
||||
{
|
||||
this.room = room;
|
||||
Room = room;
|
||||
Password = password;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
@ -23,6 +25,7 @@ namespace osu.Game.Online.Rooms
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => $"rooms/{room.RoomID.Value}/users/{User.Id}";
|
||||
// Todo: Password needs to be specified here rather than via AddParameter() because this is a PUT request. May be a framework bug.
|
||||
protected override string Target => $"rooms/{Room.RoomID.Value}/users/{User.Id}?password={Password}";
|
||||
}
|
||||
}
|
||||
|
@ -49,10 +49,6 @@ namespace osu.Game.Online.Rooms
|
||||
set => Category.Value = value;
|
||||
}
|
||||
|
||||
[Cached]
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<TimeSpan?> Duration = new Bindable<TimeSpan?>();
|
||||
|
||||
[Cached]
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<int?> MaxAttempts = new Bindable<int?>();
|
||||
@ -77,6 +73,9 @@ namespace osu.Game.Online.Rooms
|
||||
[JsonProperty("current_user_score")]
|
||||
public readonly Bindable<PlaylistAggregateScore> UserScore = new Bindable<PlaylistAggregateScore>();
|
||||
|
||||
[JsonProperty("has_password")]
|
||||
public readonly BindableBool HasPassword = new BindableBool();
|
||||
|
||||
[Cached]
|
||||
[JsonProperty("recent_participants")]
|
||||
public readonly BindableList<User> RecentParticipants = new BindableList<User>();
|
||||
@ -85,6 +84,16 @@ namespace osu.Game.Online.Rooms
|
||||
[JsonProperty("participant_count")]
|
||||
public readonly Bindable<int> ParticipantCount = new Bindable<int>();
|
||||
|
||||
#region Properties only used for room creation request
|
||||
|
||||
[Cached(Name = nameof(Password))]
|
||||
[JsonProperty("password")]
|
||||
public readonly Bindable<string> Password = new Bindable<string>();
|
||||
|
||||
[Cached]
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<TimeSpan?> Duration = new Bindable<TimeSpan?>();
|
||||
|
||||
[JsonProperty("duration")]
|
||||
private int? duration
|
||||
{
|
||||
@ -98,6 +107,8 @@ namespace osu.Game.Online.Rooms
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
// Only supports retrieval for now
|
||||
[Cached]
|
||||
[JsonProperty("ends_at")]
|
||||
@ -117,6 +128,11 @@ namespace osu.Game.Online.Rooms
|
||||
[JsonIgnore]
|
||||
public readonly Bindable<int> Position = new Bindable<int>(-1);
|
||||
|
||||
public Room()
|
||||
{
|
||||
Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Create a copy of this room without online information.
|
||||
/// Should be used to create a local copy of a room for submitting in the future.
|
||||
@ -145,6 +161,7 @@ namespace osu.Game.Online.Rooms
|
||||
ChannelId.Value = other.ChannelId.Value;
|
||||
Status.Value = other.Status.Value;
|
||||
Availability.Value = other.Availability.Value;
|
||||
HasPassword.Value = other.HasPassword.Value;
|
||||
Type.Value = other.Type.Value;
|
||||
MaxParticipants.Value = other.MaxParticipants.Value;
|
||||
ParticipantCount.Value = other.ParticipantCount.Value;
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Development;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.IO.Stores;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -341,7 +342,11 @@ namespace osu.Game
|
||||
globalBindings = new GlobalActionContainer(this)
|
||||
};
|
||||
|
||||
MenuCursorContainer.Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both };
|
||||
MenuCursorContainer.Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = content = new OsuTooltipContainer(MenuCursorContainer.Cursor) { RelativeSizeAxes = Axes.Both }
|
||||
};
|
||||
|
||||
base.Content.Add(CreateScalingContainer().WithChildren(mainContent));
|
||||
|
||||
|
@ -84,10 +84,10 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
|
||||
private JoinRoomRequest currentJoinRoomRequest;
|
||||
|
||||
public virtual void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
public virtual void JoinRoom(Room room, string password = null, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
{
|
||||
currentJoinRoomRequest?.Cancel();
|
||||
currentJoinRoomRequest = new JoinRoomRequest(room);
|
||||
currentJoinRoomRequest = new JoinRoomRequest(room, password);
|
||||
|
||||
currentJoinRoomRequest.Success += () =>
|
||||
{
|
||||
|
@ -6,6 +6,8 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Online.Rooms;
|
||||
|
||||
#nullable enable
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay
|
||||
{
|
||||
[Cached(typeof(IRoomManager))]
|
||||
@ -32,15 +34,16 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
/// <param name="room">The <see cref="Room"/> to create.</param>
|
||||
/// <param name="onSuccess">An action to be invoked if the creation succeeds.</param>
|
||||
/// <param name="onError">An action to be invoked if an error occurred.</param>
|
||||
void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null);
|
||||
void CreateRoom(Room room, Action<Room>? onSuccess = null, Action<string>? onError = null);
|
||||
|
||||
/// <summary>
|
||||
/// Joins a <see cref="Room"/>.
|
||||
/// </summary>
|
||||
/// <param name="room">The <see cref="Room"/> to join. <see cref="Room.RoomID"/> must be populated.</param>
|
||||
/// <param name="password">An optional password to use for the join operation.</param>
|
||||
/// <param name="onSuccess"></param>
|
||||
/// <param name="onError"></param>
|
||||
void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null);
|
||||
void JoinRoom(Room room, string? password = null, Action<Room>? onSuccess = null, Action<string>? onError = null);
|
||||
|
||||
/// <summary>
|
||||
/// Parts the currently-joined <see cref="Room"/>.
|
||||
|
@ -6,19 +6,25 @@ using System.Collections.Generic;
|
||||
using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
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.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
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;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osuTK;
|
||||
@ -26,7 +32,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public class DrawableRoom : OsuClickableContainer, IStateful<SelectionState>, IFilterable, IHasContextMenu
|
||||
public class DrawableRoom : OsuClickableContainer, IStateful<SelectionState>, IFilterable, IHasContextMenu, IHasPopover, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
public const float SELECTION_BORDER_WIDTH = 4;
|
||||
private const float corner_radius = 5;
|
||||
@ -46,6 +52,12 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
[Resolved]
|
||||
private BeatmapManager beatmaps { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private Bindable<Room> selectedRoom { get; set; }
|
||||
|
||||
[Resolved(canBeNull: true)]
|
||||
private LoungeSubScreen lounge { get; set; }
|
||||
|
||||
public readonly Room Room;
|
||||
|
||||
private SelectionState state;
|
||||
@ -91,6 +103,10 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
public bool FilteringActive { get; set; }
|
||||
|
||||
private PasswordProtectedIcon passwordIcon;
|
||||
|
||||
private readonly Bindable<bool> hasPassword = new Bindable<bool>();
|
||||
|
||||
public DrawableRoom(Room room)
|
||||
{
|
||||
Room = room;
|
||||
@ -200,6 +216,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
},
|
||||
},
|
||||
},
|
||||
passwordIcon = new PasswordProtectedIcon { Alpha = 0 }
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -222,10 +239,69 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
this.FadeInFromZero(transition_duration);
|
||||
else
|
||||
Alpha = 0;
|
||||
|
||||
hasPassword.BindTo(Room.HasPassword);
|
||||
hasPassword.BindValueChanged(v => passwordIcon.Alpha = v.NewValue ? 1 : 0, true);
|
||||
}
|
||||
|
||||
public Popover GetPopover() => new PasswordEntryPopover(Room) { JoinRequested = lounge.Join };
|
||||
|
||||
public MenuItem[] ContextMenuItems => new MenuItem[]
|
||||
{
|
||||
new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
|
||||
{
|
||||
parentScreen?.OpenNewRoom(Room.DeepClone());
|
||||
})
|
||||
};
|
||||
|
||||
public bool OnPressed(GlobalAction action)
|
||||
{
|
||||
if (selectedRoom.Value != Room)
|
||||
return false;
|
||||
|
||||
switch (action)
|
||||
{
|
||||
case GlobalAction.Select:
|
||||
Click();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(GlobalAction action)
|
||||
{
|
||||
}
|
||||
|
||||
protected override bool ShouldBeConsideredForInput(Drawable child) => state == SelectionState.Selected;
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
if (selectedRoom.Value != Room)
|
||||
return true;
|
||||
|
||||
return base.OnMouseDown(e);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (Room != selectedRoom.Value)
|
||||
{
|
||||
selectedRoom.Value = Room;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Room.HasPassword.Value)
|
||||
{
|
||||
this.ShowPopover();
|
||||
return true;
|
||||
}
|
||||
|
||||
lounge?.Join(Room, null);
|
||||
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
private class RoomName : OsuSpriteText
|
||||
{
|
||||
[Resolved(typeof(Room), nameof(Online.Rooms.Room.Name))]
|
||||
@ -238,12 +314,83 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
}
|
||||
}
|
||||
|
||||
public MenuItem[] ContextMenuItems => new MenuItem[]
|
||||
public class PasswordProtectedIcon : CompositeDrawable
|
||||
{
|
||||
new OsuMenuItem("Create copy", MenuItemType.Standard, () =>
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
parentScreen?.OpenNewRoom(Room.DeepClone());
|
||||
})
|
||||
};
|
||||
Anchor = Anchor.TopRight;
|
||||
Origin = Anchor.TopRight;
|
||||
|
||||
Size = new Vector2(32);
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopCentre,
|
||||
Colour = colours.Gray5,
|
||||
Rotation = 45,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Width = 2,
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.Solid.Lock,
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Margin = new MarginPadding(6),
|
||||
Size = new Vector2(14),
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class PasswordEntryPopover : OsuPopover
|
||||
{
|
||||
private readonly Room room;
|
||||
|
||||
public Action<Room, string> JoinRequested;
|
||||
|
||||
public PasswordEntryPopover(Room room)
|
||||
{
|
||||
this.room = room;
|
||||
}
|
||||
|
||||
private OsuPasswordTextBox passwordTextbox;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
Margin = new MarginPadding(10),
|
||||
Spacing = new Vector2(5),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
passwordTextbox = new OsuPasswordTextBox
|
||||
{
|
||||
Width = 200,
|
||||
},
|
||||
new TriangleButton
|
||||
{
|
||||
Width = 80,
|
||||
Text = "Join Room",
|
||||
Action = () => JoinRequested?.Invoke(room, passwordTextbox.Text)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Schedule(() => GetContainingInputManager().ChangeFocus(passwordTextbox));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -24,8 +24,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
public class RoomsContainer : CompositeDrawable, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
public Action<Room> JoinRequested;
|
||||
|
||||
private readonly IBindableList<Room> rooms = new BindableList<Room>();
|
||||
|
||||
private readonly FillFlowContainer<DrawableRoom> roomFlow;
|
||||
@ -121,19 +119,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
foreach (var room in rooms)
|
||||
{
|
||||
roomFlow.Add(new DrawableRoom(room)
|
||||
{
|
||||
Action = () =>
|
||||
{
|
||||
if (room == selectedRoom.Value)
|
||||
{
|
||||
joinSelected();
|
||||
return;
|
||||
}
|
||||
|
||||
selectRoom(room);
|
||||
}
|
||||
});
|
||||
roomFlow.Add(new DrawableRoom(room));
|
||||
}
|
||||
|
||||
Filter(filter?.Value);
|
||||
@ -150,7 +136,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
roomFlow.Remove(toRemove);
|
||||
|
||||
selectRoom(null);
|
||||
selectedRoom.Value = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -160,18 +146,9 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
roomFlow.SetLayoutPosition(room, room.Room.Position.Value);
|
||||
}
|
||||
|
||||
private void selectRoom(Room room) => selectedRoom.Value = room;
|
||||
|
||||
private void joinSelected()
|
||||
{
|
||||
if (selectedRoom.Value == null) return;
|
||||
|
||||
JoinRequested?.Invoke(selectedRoom.Value);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
selectRoom(null);
|
||||
selectedRoom.Value = null;
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
@ -181,10 +158,6 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case GlobalAction.Select:
|
||||
joinSelected();
|
||||
return true;
|
||||
|
||||
case GlobalAction.SelectNext:
|
||||
beginRepeatSelection(() => selectNext(1), action);
|
||||
return true;
|
||||
@ -253,7 +226,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge.Components
|
||||
|
||||
// we already have a valid selection only change selection if we still have a room to switch to.
|
||||
if (room != null)
|
||||
selectRoom(room);
|
||||
selectedRoom.Value = room;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
@ -46,10 +47,11 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
[CanBeNull]
|
||||
private IDisposable joiningRoomOperation { get; set; }
|
||||
|
||||
private RoomsContainer roomsContainer;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RoomsContainer roomsContainer;
|
||||
OsuScrollContainer scrollContainer;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
@ -70,7 +72,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ScrollbarOverlapsContent = false,
|
||||
Padding = new MarginPadding(10),
|
||||
Child = roomsContainer = new RoomsContainer { JoinRequested = joinRequested }
|
||||
Child = roomsContainer = new RoomsContainer()
|
||||
},
|
||||
loadingLayer = new LoadingLayer(true),
|
||||
}
|
||||
@ -150,31 +152,39 @@ namespace osu.Game.Screens.OnlinePlay.Lounge
|
||||
onReturning();
|
||||
}
|
||||
|
||||
private void onReturning()
|
||||
{
|
||||
filter.HoldFocus = true;
|
||||
}
|
||||
|
||||
public override bool OnExiting(IScreen next)
|
||||
{
|
||||
filter.HoldFocus = false;
|
||||
onLeaving();
|
||||
return base.OnExiting(next);
|
||||
}
|
||||
|
||||
public override void OnSuspending(IScreen next)
|
||||
{
|
||||
onLeaving();
|
||||
base.OnSuspending(next);
|
||||
filter.HoldFocus = false;
|
||||
}
|
||||
|
||||
private void joinRequested(Room room)
|
||||
private void onReturning()
|
||||
{
|
||||
filter.HoldFocus = true;
|
||||
}
|
||||
|
||||
private void onLeaving()
|
||||
{
|
||||
filter.HoldFocus = false;
|
||||
|
||||
// ensure any password prompt is dismissed.
|
||||
this.HidePopover();
|
||||
}
|
||||
|
||||
public void Join(Room room, string password)
|
||||
{
|
||||
if (joiningRoomOperation != null)
|
||||
return;
|
||||
|
||||
joiningRoomOperation = ongoingOperationTracker?.BeginOperation();
|
||||
|
||||
RoomManager?.JoinRoom(room, r =>
|
||||
RoomManager?.JoinRoom(room, password, r =>
|
||||
{
|
||||
Open(room);
|
||||
joiningRoomOperation?.Dispose();
|
||||
|
@ -25,8 +25,12 @@ namespace osu.Game.Screens.OnlinePlay.Match.Components
|
||||
private void load()
|
||||
{
|
||||
Masking = true;
|
||||
|
||||
Add(Settings = CreateSettings());
|
||||
}
|
||||
|
||||
protected abstract OnlinePlayComposite CreateSettings();
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
Settings.MoveToY(0, TRANSITION_DURATION, Easing.OutQuint);
|
||||
|
@ -27,16 +27,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
{
|
||||
public class MultiplayerMatchSettingsOverlay : MatchSettingsOverlay
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Child = Settings = new MatchSettings
|
||||
protected override OnlinePlayComposite CreateSettings()
|
||||
=> new MatchSettings
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativePositionAxes = Axes.Y,
|
||||
SettingsApplied = Hide
|
||||
};
|
||||
}
|
||||
|
||||
protected class MatchSettings : OnlinePlayComposite
|
||||
{
|
||||
@ -47,6 +44,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
public OsuTextBox NameField, MaxParticipantsField;
|
||||
public RoomAvailabilityPicker AvailabilityPicker;
|
||||
public GameTypePicker TypePicker;
|
||||
public OsuTextBox PasswordTextBox;
|
||||
public TriangleButton ApplyButton;
|
||||
|
||||
public OsuSpriteText ErrorText;
|
||||
@ -193,12 +191,10 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
},
|
||||
new Section("Password (optional)")
|
||||
{
|
||||
Alpha = disabled_alpha,
|
||||
Child = new SettingsPasswordTextBox
|
||||
Child = PasswordTextBox = new SettingsPasswordTextBox
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
TabbableContentContainer = this,
|
||||
ReadOnly = true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -275,6 +271,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
Type.BindValueChanged(type => TypePicker.Current.Value = type.NewValue, true);
|
||||
MaxParticipants.BindValueChanged(count => MaxParticipantsField.Text = count.NewValue?.ToString(), true);
|
||||
RoomID.BindValueChanged(roomId => initialBeatmapControl.Alpha = roomId.NewValue == null ? 1 : 0, true);
|
||||
Password.BindValueChanged(password => PasswordTextBox.Text = password.NewValue ?? string.Empty, true);
|
||||
|
||||
operationInProgress.BindTo(ongoingOperationTracker.InProgress);
|
||||
operationInProgress.BindValueChanged(v =>
|
||||
@ -307,7 +304,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).ContinueWith(t => Schedule(() =>
|
||||
client.ChangeSettings(name: NameField.Text, password: PasswordTextBox.Text).ContinueWith(t => Schedule(() =>
|
||||
{
|
||||
if (t.IsCompletedSuccessfully)
|
||||
onSuccess(currentRoom.Value);
|
||||
@ -320,6 +317,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Match
|
||||
currentRoom.Value.Name.Value = NameField.Text;
|
||||
currentRoom.Value.Availability.Value = AvailabilityPicker.Current.Value;
|
||||
currentRoom.Value.Type.Value = TypePicker.Current.Value;
|
||||
currentRoom.Value.Password.Value = PasswordTextBox.Current.Value;
|
||||
|
||||
if (int.TryParse(MaxParticipantsField.Text, out int max))
|
||||
currentRoom.Value.MaxParticipants.Value = max;
|
||||
|
@ -38,9 +38,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
}
|
||||
|
||||
public override void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
=> base.CreateRoom(room, r => joinMultiplayerRoom(r, onSuccess, onError), onError);
|
||||
=> base.CreateRoom(room, r => joinMultiplayerRoom(r, r.Password.Value, onSuccess, onError), onError);
|
||||
|
||||
public override void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
public override void JoinRoom(Room room, string password = null, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
{
|
||||
if (!multiplayerClient.IsConnected.Value)
|
||||
{
|
||||
@ -56,7 +56,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
return;
|
||||
}
|
||||
|
||||
base.JoinRoom(room, r => joinMultiplayerRoom(r, onSuccess, onError), onError);
|
||||
base.JoinRoom(room, password, r => joinMultiplayerRoom(r, password, onSuccess, onError), onError);
|
||||
}
|
||||
|
||||
public override void PartRoom()
|
||||
@ -79,11 +79,11 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
});
|
||||
}
|
||||
|
||||
private void joinMultiplayerRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
private void joinMultiplayerRoom(Room room, string password, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
{
|
||||
Debug.Assert(room.RoomID.Value != null);
|
||||
|
||||
multiplayerClient.JoinRoom(room).ContinueWith(t =>
|
||||
multiplayerClient.JoinRoom(room, password).ContinueWith(t =>
|
||||
{
|
||||
if (t.IsCompletedSuccessfully)
|
||||
Schedule(() => onSuccess?.Invoke(room));
|
||||
|
@ -56,6 +56,9 @@ namespace osu.Game.Screens.OnlinePlay
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<RoomAvailability> Availability { get; private set; }
|
||||
|
||||
[Resolved(typeof(Room), nameof(Room.Password))]
|
||||
public Bindable<string> Password { get; private set; }
|
||||
|
||||
[Resolved(typeof(Room))]
|
||||
protected Bindable<TimeSpan?> Duration { get; private set; }
|
||||
|
||||
|
@ -26,16 +26,13 @@ namespace osu.Game.Screens.OnlinePlay.Playlists
|
||||
{
|
||||
public Action EditPlaylist;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Child = Settings = new MatchSettings
|
||||
protected override OnlinePlayComposite CreateSettings()
|
||||
=> new MatchSettings
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativePositionAxes = Axes.Y,
|
||||
EditPlaylist = () => EditPlaylist?.Invoke()
|
||||
};
|
||||
}
|
||||
|
||||
protected class MatchSettings : OnlinePlayComposite
|
||||
{
|
||||
|
@ -28,7 +28,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public override IBindable<bool> IsConnected => isConnected;
|
||||
private readonly Bindable<bool> isConnected = new Bindable<bool>(true);
|
||||
|
||||
public Room? APIRoom { get; private set; }
|
||||
public new Room? APIRoom => base.APIRoom;
|
||||
|
||||
public Action<MultiplayerRoom>? RoomSetupAction;
|
||||
|
||||
@ -115,10 +115,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
((IMultiplayerClient)this).UserBeatmapAvailabilityChanged(userId, newBeatmapAvailability);
|
||||
}
|
||||
|
||||
protected override Task<MultiplayerRoom> JoinRoom(long roomId)
|
||||
protected override Task<MultiplayerRoom> JoinRoom(long roomId, string? password = null)
|
||||
{
|
||||
var apiRoom = roomManager.Rooms.Single(r => r.RoomID.Value == roomId);
|
||||
|
||||
if (password != apiRoom.Password.Value)
|
||||
throw new InvalidOperationException("Invalid password.");
|
||||
|
||||
var localUser = new MultiplayerRoomUser(api.LocalUser.Value.Id)
|
||||
{
|
||||
User = api.LocalUser.Value
|
||||
@ -134,7 +137,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
BeatmapChecksum = apiRoom.Playlist.Last().Beatmap.Value.MD5Hash,
|
||||
RequiredMods = apiRoom.Playlist.Last().RequiredMods.Select(m => new APIMod(m)).ToArray(),
|
||||
AllowedMods = apiRoom.Playlist.Last().AllowedMods.Select(m => new APIMod(m)).ToArray(),
|
||||
PlaylistItemId = apiRoom.Playlist.Last().ID
|
||||
PlaylistItemId = apiRoom.Playlist.Last().ID,
|
||||
// ReSharper disable once ConstantNullCoalescingCondition Incorrect inspection due to lack of nullable in Room.cs.
|
||||
Password = password ?? string.Empty,
|
||||
},
|
||||
Users = { localUser },
|
||||
Host = localUser
|
||||
@ -143,16 +148,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
RoomSetupAction?.Invoke(room);
|
||||
RoomSetupAction = null;
|
||||
|
||||
APIRoom = apiRoom;
|
||||
|
||||
return Task.FromResult(room);
|
||||
}
|
||||
|
||||
protected override Task LeaveRoomInternal()
|
||||
{
|
||||
APIRoom = null;
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
protected override Task LeaveRoomInternal() => Task.CompletedTask;
|
||||
|
||||
public override Task TransferHost(int userId) => ((IMultiplayerClient)this).HostChanged(userId);
|
||||
|
||||
|
@ -45,21 +45,38 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
switch (req)
|
||||
{
|
||||
case CreateRoomRequest createRoomRequest:
|
||||
var createdRoom = new APICreatedRoom();
|
||||
var apiRoom = new Room();
|
||||
|
||||
createdRoom.CopyFrom(createRoomRequest.Room);
|
||||
createdRoom.RoomID.Value ??= currentRoomId++;
|
||||
apiRoom.CopyFrom(createRoomRequest.Room);
|
||||
apiRoom.RoomID.Value ??= currentRoomId++;
|
||||
|
||||
for (int i = 0; i < createdRoom.Playlist.Count; i++)
|
||||
createdRoom.Playlist[i].ID = currentPlaylistItemId++;
|
||||
// Passwords are explicitly not copied between rooms.
|
||||
apiRoom.HasPassword.Value = !string.IsNullOrEmpty(createRoomRequest.Room.Password.Value);
|
||||
apiRoom.Password.Value = createRoomRequest.Room.Password.Value;
|
||||
|
||||
Rooms.Add(createdRoom);
|
||||
createRoomRequest.TriggerSuccess(createdRoom);
|
||||
for (int i = 0; i < apiRoom.Playlist.Count; i++)
|
||||
apiRoom.Playlist[i].ID = currentPlaylistItemId++;
|
||||
|
||||
var responseRoom = new APICreatedRoom();
|
||||
responseRoom.CopyFrom(createResponseRoom(apiRoom, false));
|
||||
|
||||
Rooms.Add(apiRoom);
|
||||
createRoomRequest.TriggerSuccess(responseRoom);
|
||||
return true;
|
||||
|
||||
case JoinRoomRequest joinRoomRequest:
|
||||
{
|
||||
var room = Rooms.Single(r => r.RoomID.Value == joinRoomRequest.Room.RoomID.Value);
|
||||
|
||||
if (joinRoomRequest.Password != room.Password.Value)
|
||||
{
|
||||
joinRoomRequest.TriggerFailure(new InvalidOperationException("Invalid password."));
|
||||
return true;
|
||||
}
|
||||
|
||||
joinRoomRequest.TriggerSuccess();
|
||||
return true;
|
||||
}
|
||||
|
||||
case PartRoomRequest partRoomRequest:
|
||||
partRoomRequest.TriggerSuccess();
|
||||
@ -69,20 +86,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
var roomsWithoutParticipants = new List<Room>();
|
||||
|
||||
foreach (var r in Rooms)
|
||||
{
|
||||
var newRoom = new Room();
|
||||
|
||||
newRoom.CopyFrom(r);
|
||||
newRoom.RecentParticipants.Clear();
|
||||
|
||||
roomsWithoutParticipants.Add(newRoom);
|
||||
}
|
||||
roomsWithoutParticipants.Add(createResponseRoom(r, false));
|
||||
|
||||
getRoomsRequest.TriggerSuccess(roomsWithoutParticipants);
|
||||
return true;
|
||||
|
||||
case GetRoomRequest getRoomRequest:
|
||||
getRoomRequest.TriggerSuccess(Rooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId));
|
||||
getRoomRequest.TriggerSuccess(createResponseRoom(Rooms.Single(r => r.RoomID.Value == getRoomRequest.RoomId), true));
|
||||
return true;
|
||||
|
||||
case GetBeatmapSetRequest getBeatmapSetRequest:
|
||||
@ -118,6 +128,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
};
|
||||
}
|
||||
|
||||
private Room createResponseRoom(Room room, bool withParticipants)
|
||||
{
|
||||
var responseRoom = new Room();
|
||||
responseRoom.CopyFrom(room);
|
||||
responseRoom.Password.Value = null;
|
||||
if (!withParticipants)
|
||||
responseRoom.RecentParticipants.Clear();
|
||||
return responseRoom;
|
||||
}
|
||||
|
||||
public new void ClearRooms() => base.ClearRooms();
|
||||
|
||||
public new void Schedule(Action action) => base.Schedule(action);
|
||||
|
@ -26,6 +26,8 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
|
||||
public readonly BindableList<Room> Rooms = new BindableList<Room>();
|
||||
|
||||
public Action<Room, string> JoinRoomRequested;
|
||||
|
||||
public IBindable<bool> InitialRoomsReceived { get; } = new Bindable<bool>(true);
|
||||
|
||||
IBindableList<Room> IRoomManager.Rooms => Rooms;
|
||||
@ -37,13 +39,17 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
onSuccess?.Invoke(room);
|
||||
}
|
||||
|
||||
public void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null) => onSuccess?.Invoke(room);
|
||||
public void JoinRoom(Room room, string password, Action<Room> onSuccess = null, Action<string> onError = null)
|
||||
{
|
||||
JoinRoomRequested?.Invoke(room, password);
|
||||
onSuccess?.Invoke(room);
|
||||
}
|
||||
|
||||
public void PartRoom()
|
||||
{
|
||||
}
|
||||
|
||||
public void AddRooms(int count, RulesetInfo ruleset = null)
|
||||
public void AddRooms(int count, RulesetInfo ruleset = null, bool withPassword = false)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
@ -53,7 +59,8 @@ namespace osu.Game.Tests.Visual.OnlinePlay
|
||||
Name = { Value = $"Room {i}" },
|
||||
Host = { Value = new User { Username = "Host" } },
|
||||
EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) },
|
||||
Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal }
|
||||
Category = { Value = i % 2 == 0 ? RoomCategory.Spotlight : RoomCategory.Normal },
|
||||
Password = { Value = withPassword ? "password" : string.Empty }
|
||||
};
|
||||
|
||||
if (ruleset != null)
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Testing.Input;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
@ -34,9 +35,16 @@ namespace osu.Game.Tests.Visual
|
||||
{
|
||||
MenuCursorContainer cursorContainer;
|
||||
|
||||
CompositeDrawable mainContent =
|
||||
(cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both })
|
||||
.WithChild(content = new OsuTooltipContainer(cursorContainer.Cursor) { RelativeSizeAxes = Axes.Both });
|
||||
CompositeDrawable mainContent = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = cursorContainer = new MenuCursorContainer { RelativeSizeAxes = Axes.Both, }
|
||||
};
|
||||
|
||||
cursorContainer.Child = content = new OsuTooltipContainer(cursorContainer.Cursor)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
};
|
||||
|
||||
if (CreateNestedActionContainer)
|
||||
{
|
||||
|
Loading…
Reference in New Issue
Block a user