diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index eddcd16b93..61c2f47e7d 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -16,6 +16,7 @@ using NUnit.Framework; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Overlays; +using osu.Framework.Configuration; namespace osu.Game.Tests.Visual { @@ -54,8 +55,9 @@ namespace osu.Game.Tests.Visual linkColour = colours.Blue; var chatManager = new ChannelManager(); - chatManager.AvailableChannels.Add(new Channel { Name = "#english"}); - chatManager.AvailableChannels.Add(new Channel { Name = "#japanese" }); + BindableCollection availableChannels = (BindableCollection)chatManager.AvailableChannels; + availableChannels.Add(new Channel { Name = "#english"}); + availableChannels.Add(new Channel { Name = "#japanese" }); Dependencies.Cache(chatManager); Dependencies.Cache(new ChatOverlay()); diff --git a/osu.Game.Tests/Visual/TestCaseIdleTracker.cs b/osu.Game.Tests/Visual/TestCaseIdleTracker.cs new file mode 100644 index 0000000000..33e2df9ff0 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseIdleTracker.cs @@ -0,0 +1,135 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Input; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseIdleTracker : ManualInputManagerTestCase + { + private readonly IdleTrackingBox box1; + private readonly IdleTrackingBox box2; + private readonly IdleTrackingBox box3; + private readonly IdleTrackingBox box4; + + public TestCaseIdleTracker() + { + Children = new Drawable[] + { + box1 = new IdleTrackingBox(1000) + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Red, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + }, + box2 = new IdleTrackingBox(2000) + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Green, + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + }, + box3 = new IdleTrackingBox(3000) + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Blue, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + box4 = new IdleTrackingBox(4000) + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Orange, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + }; + } + + [Test] + public void TestNudge() + { + AddStep("move mouse to top left", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre)); + + AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + + AddStep("nudge mouse", () => InputManager.MoveMouseTo(box1.ScreenSpaceDrawQuad.Centre + new Vector2(1))); + + AddAssert("check not idle", () => !box1.IsIdle); + AddAssert("check idle", () => box2.IsIdle); + AddAssert("check idle", () => box3.IsIdle); + AddAssert("check idle", () => box4.IsIdle); + } + + [Test] + public void TestMovement() + { + AddStep("move mouse", () => InputManager.MoveMouseTo(box2.ScreenSpaceDrawQuad.Centre)); + + AddAssert("check not idle", () => box1.IsIdle); + AddAssert("check not idle", () => !box2.IsIdle); + AddAssert("check idle", () => box3.IsIdle); + AddAssert("check idle", () => box4.IsIdle); + + AddStep("move mouse", () => InputManager.MoveMouseTo(box3.ScreenSpaceDrawQuad.Centre)); + AddStep("move mouse", () => InputManager.MoveMouseTo(box4.ScreenSpaceDrawQuad.Centre)); + + AddAssert("check not idle", () => box1.IsIdle); + AddAssert("check not idle", () => !box2.IsIdle); + AddAssert("check idle", () => !box3.IsIdle); + AddAssert("check idle", () => !box4.IsIdle); + + AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + } + + [Test] + public void TestTimings() + { + AddStep("move mouse", () => InputManager.MoveMouseTo(ScreenSpaceDrawQuad.Centre)); + + AddAssert("check not idle", () => !box1.IsIdle && !box2.IsIdle && !box3.IsIdle && !box4.IsIdle); + AddUntilStep(() => box1.IsIdle, "Wait for idle"); + AddAssert("check not idle", () => !box2.IsIdle && !box3.IsIdle && !box4.IsIdle); + AddUntilStep(() => box2.IsIdle, "Wait for idle"); + AddAssert("check not idle", () => !box3.IsIdle && !box4.IsIdle); + AddUntilStep(() => box3.IsIdle, "Wait for idle"); + + AddUntilStep(() => box1.IsIdle && box2.IsIdle && box3.IsIdle && box4.IsIdle, "Wait for all idle"); + } + + private class IdleTrackingBox : CompositeDrawable + { + private readonly IdleTracker idleTracker; + + public bool IsIdle => idleTracker.IsIdle.Value; + + public IdleTrackingBox(double timeToIdle) + { + Box box; + + Alpha = 0.6f; + Scale = new Vector2(0.6f); + + InternalChildren = new Drawable[] + { + idleTracker = new IdleTracker(timeToIdle), + box = new Box + { + Colour = Color4.White, + RelativeSizeAxes = Axes.Both, + }, + }; + + idleTracker.IsIdle.BindValueChanged(idle => box.Colour = idle ? Color4.White : Color4.Black, true); + } + } + } +} diff --git a/osu.Game.Tests/Visual/TestCaseRoomSettings.cs b/osu.Game.Tests/Visual/TestCaseRoomSettings.cs new file mode 100644 index 0000000000..40742ce709 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseRoomSettings.cs @@ -0,0 +1,119 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Framework.Testing.Input; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi.Screens.Match.Settings; +using osuTK.Input; + +namespace osu.Game.Tests.Visual +{ + [TestFixture] + public class TestCaseRoomSettings : ManualInputManagerTestCase + { + private readonly Room room; + private readonly TestRoomSettingsOverlay overlay; + + public TestCaseRoomSettings() + { + room = new Room + { + Name = { Value = "One Testing Room" }, + Availability = { Value = RoomAvailability.Public }, + Type = { Value = new GameTypeTeamVersus() }, + MaxParticipants = { Value = 10 }, + }; + + Add(overlay = new TestRoomSettingsOverlay(room) + { + RelativeSizeAxes = Axes.Both, + Height = 0.75f, + }); + + AddStep(@"show", overlay.Show); + assertAll(); + AddStep(@"set name", () => overlay.CurrentName = @"Two Testing Room"); + AddStep(@"set max", () => overlay.CurrentMaxParticipants = null); + AddStep(@"set availability", () => overlay.CurrentAvailability = RoomAvailability.InviteOnly); + AddStep(@"set type", () => overlay.CurrentType = new GameTypeTagTeam()); + apply(); + assertAll(); + AddStep(@"show", overlay.Show); + AddStep(@"set room name", () => room.Name.Value = @"Room Changed Name!"); + AddStep(@"set room availability", () => room.Availability.Value = RoomAvailability.Public); + AddStep(@"set room type", () => room.Type.Value = new GameTypeTag()); + AddStep(@"set room max", () => room.MaxParticipants.Value = 100); + assertAll(); + AddStep(@"set name", () => overlay.CurrentName = @"Unsaved Testing Room"); + AddStep(@"set max", () => overlay.CurrentMaxParticipants = 20); + AddStep(@"set availability", () => overlay.CurrentAvailability = RoomAvailability.FriendsOnly); + AddStep(@"set type", () => overlay.CurrentType = new GameTypeVersus()); + AddStep(@"hide", overlay.Hide); + AddWaitStep(5); + AddStep(@"show", overlay.Show); + assertAll(); + AddStep(@"hide", overlay.Hide); + } + + private void apply() + { + AddStep(@"apply", () => + { + overlay.ClickApplyButton(InputManager); + }); + } + + private void assertAll() + { + AddAssert(@"name == room name", () => overlay.CurrentName == room.Name.Value); + AddAssert(@"max == room max", () => overlay.CurrentMaxParticipants == room.MaxParticipants.Value); + AddAssert(@"availability == room availability", () => overlay.CurrentAvailability == room.Availability.Value); + AddAssert(@"type == room type", () => Equals(overlay.CurrentType, room.Type.Value)); + } + + private class TestRoomSettingsOverlay : RoomSettingsOverlay + { + public string CurrentName + { + get => NameField.Text; + set => NameField.Text = value; + } + + public int? CurrentMaxParticipants + { + get + { + if (int.TryParse(MaxParticipantsField.Text, out int max)) + return max; + + return null; + } + set => MaxParticipantsField.Text = value?.ToString(); + } + + public RoomAvailability CurrentAvailability + { + get => AvailabilityPicker.Current.Value; + set => AvailabilityPicker.Current.Value = value; + } + + public GameType CurrentType + { + get => TypePicker.Current.Value; + set => TypePicker.Current.Value = value; + } + + public TestRoomSettingsOverlay(Room room) : base(room) + { + } + + public void ClickApplyButton(ManualInputManager inputManager) + { + inputManager.MoveMouseTo(ApplyButton); + inputManager.Click(MouseButton.Left); + } + } + } +} diff --git a/osu.Game/Input/IdleTracker.cs b/osu.Game/Input/IdleTracker.cs new file mode 100644 index 0000000000..d96fa8bd34 --- /dev/null +++ b/osu.Game/Input/IdleTracker.cs @@ -0,0 +1,71 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Input; +using osu.Framework.Input.Bindings; +using osu.Framework.Input.Events; + +namespace osu.Game.Input +{ + /// + /// Track whether the end-user is in an idle state, based on their last interaction with the game. + /// + public class IdleTracker : Component, IKeyBindingHandler, IHandleGlobalInput + { + private readonly double timeToIdle; + + private double lastInteractionTime; + + protected double TimeSpentIdle => Clock.CurrentTime - lastInteractionTime; + + /// + /// Whether the user is currently in an idle state. + /// + public IBindable IsIdle => isIdle; + + private readonly BindableBool isIdle = new BindableBool(); + + /// + /// Intstantiate a new . + /// + /// The length in milliseconds until an idle state should be assumed. + public IdleTracker(double timeToIdle) + { + this.timeToIdle = timeToIdle; + RelativeSizeAxes = Axes.Both; + } + + protected override void Update() + { + base.Update(); + isIdle.Value = TimeSpentIdle > timeToIdle; + } + + public bool OnPressed(PlatformAction action) => updateLastInteractionTime(); + + public bool OnReleased(PlatformAction action) => updateLastInteractionTime(); + + protected override bool Handle(UIEvent e) + { + switch (e) + { + case KeyDownEvent _: + case KeyUpEvent _: + case MouseDownEvent _: + case MouseUpEvent _: + case MouseMoveEvent _: + return updateLastInteractionTime(); + default: + return base.Handle(e); + } + } + + private bool updateLastInteractionTime() + { + lastInteractionTime = Clock.CurrentTime; + return false; + } + } +} diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 73ac7c9df4..29f971078b 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Configuration; @@ -31,6 +30,9 @@ namespace osu.Game.Online.Chat @"#lobby" }; + private readonly BindableCollection availableChannels = new BindableCollection(); + private readonly BindableCollection joinedChannels = new BindableCollection(); + /// /// The currently opened channel /// @@ -39,12 +41,12 @@ namespace osu.Game.Online.Chat /// /// The Channels the player has joined /// - public ObservableCollection JoinedChannels { get; } = new ObservableCollection(); //todo: should be publicly readonly + public IBindableCollection JoinedChannels => joinedChannels; /// /// The channels available for the player to join /// - public ObservableCollection AvailableChannels { get; } = new ObservableCollection(); //todo: should be publicly readonly + public IBindableCollection AvailableChannels => availableChannels; private IAPIProvider api; private ScheduledDelegate fetchMessagesScheduleder; @@ -293,8 +295,8 @@ namespace osu.Game.Online.Chat found.Users.Remove(foundSelf); } - if (joined == null && addToJoined) JoinedChannels.Add(found); - if (available == null && addToAvailable) AvailableChannels.Add(found); + if (joined == null && addToJoined) joinedChannels.Add(found); + if (available == null && addToAvailable) availableChannels.Add(found); return found; } @@ -346,9 +348,10 @@ namespace osu.Game.Online.Chat { if (channel == null) return; - if (channel == CurrentChannel.Value) CurrentChannel.Value = null; + if (channel == CurrentChannel.Value) + CurrentChannel.Value = null; - JoinedChannels.Remove(channel); + joinedChannels.Remove(channel); if (channel.Joined.Value) { diff --git a/osu.Game/Online/Multiplayer/GameType.cs b/osu.Game/Online/Multiplayer/GameType.cs index 750401c067..8d39e8f59d 100644 --- a/osu.Game/Online/Multiplayer/GameType.cs +++ b/osu.Game/Online/Multiplayer/GameType.cs @@ -14,6 +14,9 @@ namespace osu.Game.Online.Multiplayer { public abstract string Name { get; } public abstract Drawable GetIcon(OsuColour colours, float size); + + public override int GetHashCode() => GetType().GetHashCode(); + public override bool Equals(object obj) => GetType() == obj?.GetType(); } public class GameTypeTag : GameType diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 1b0a0b2210..d91f96db53 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -26,6 +26,7 @@ using osu.Framework.Platform; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics; +using osu.Game.Input; using osu.Game.Rulesets.Scoring; using osu.Game.Overlays.Notifications; using osu.Game.Rulesets; @@ -88,6 +89,8 @@ namespace osu.Game public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight; + private IdleTracker idleTracker; + public readonly Bindable OverlayActivationMode = new Bindable(); private OsuScreen screenStack; @@ -316,6 +319,7 @@ namespace osu.Game }, mainContent = new Container { RelativeSizeAxes = Axes.Both }, overlayContent = new Container { RelativeSizeAxes = Axes.Both, Depth = float.MinValue }, + idleTracker = new IdleTracker(6000) }); loadComponentSingleFile(screenStack = new Loader(), d => @@ -373,6 +377,7 @@ namespace osu.Game Depth = -6, }, overlayContent.Add); + dependencies.Cache(idleTracker); dependencies.Cache(settings); dependencies.Cache(onscreenDisplay); dependencies.Cache(social); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index b1edfe0548..bdb28d1246 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; -using System.Collections.Specialized; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -181,28 +180,6 @@ namespace osu.Game.Overlays channelSelection.OnRequestLeave = channel => channelManager.LeaveChannel(channel); } - private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args) - { - switch (args.Action) - { - case NotifyCollectionChangedAction.Add: - foreach (Channel newChannel in args.NewItems) - { - channelTabControl.AddChannel(newChannel); - } - - break; - case NotifyCollectionChangedAction.Remove: - foreach (Channel removedChannel in args.OldItems) - { - channelTabControl.RemoveChannel(removedChannel); - loadedChannels.Remove(loadedChannels.Find(c => c.Channel == removedChannel)); - } - - break; - } - } - private void currentChannelChanged(Channel channel) { if (channel == null) @@ -322,19 +299,35 @@ namespace osu.Game.Overlays this.channelManager = channelManager; channelManager.CurrentChannel.ValueChanged += currentChannelChanged; - channelManager.JoinedChannels.CollectionChanged += joinedChannelsChanged; - channelManager.AvailableChannels.CollectionChanged += availableChannelsChanged; + channelManager.JoinedChannels.ItemsAdded += onChannelAddedToJoinedChannels; + channelManager.JoinedChannels.ItemsRemoved += onChannelRemovedFromJoinedChannels; + channelManager.AvailableChannels.ItemsAdded += availableChannelsChanged; + channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged; //for the case that channelmanager was faster at fetching the channels than our attachment to CollectionChanged. channelSelection.UpdateAvailableChannels(channelManager.AvailableChannels); - joinedChannelsChanged(this, new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, channelManager.JoinedChannels)); + foreach (Channel channel in channelManager.JoinedChannels) + channelTabControl.AddChannel(channel); } - private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs e) + private void onChannelAddedToJoinedChannels(IEnumerable channels) { - channelSelection.UpdateAvailableChannels(channelManager.AvailableChannels); + foreach (Channel channel in channels) + channelTabControl.AddChannel(channel); } + private void onChannelRemovedFromJoinedChannels(IEnumerable channels) + { + foreach (Channel channel in channels) + { + channelTabControl.RemoveChannel(channel); + loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel)); + } + } + + private void availableChannelsChanged(IEnumerable channels) + => channelSelection.UpdateAvailableChannels(channelManager.AvailableChannels); + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -342,8 +335,10 @@ namespace osu.Game.Overlays if (channelManager != null) { channelManager.CurrentChannel.ValueChanged -= currentChannelChanged; - channelManager.JoinedChannels.CollectionChanged -= joinedChannelsChanged; - channelManager.AvailableChannels.CollectionChanged -= availableChannelsChanged; + channelManager.JoinedChannels.ItemsAdded -= onChannelAddedToJoinedChannels; + channelManager.JoinedChannels.ItemsRemoved -= onChannelRemovedFromJoinedChannels; + channelManager.AvailableChannels.ItemsAdded -= availableChannelsChanged; + channelManager.AvailableChannels.ItemsRemoved -= availableChannelsChanged; } } diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index f54e3d90a6..ae1f27610b 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -8,12 +8,14 @@ using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Audio.Sample; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Bindings; using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Graphics; +using osu.Game.Input; using osu.Game.Input.Bindings; using osu.Game.Overlays; using osuTK; @@ -26,6 +28,8 @@ namespace osu.Game.Screens.Menu { public event Action StateChanged; + private readonly IBindable isIdle = new BindableBool(); + public Action OnEdit; public Action OnExit; public Action OnDirect; @@ -102,12 +106,22 @@ namespace osu.Game.Screens.Menu private OsuGame game; [BackgroundDependencyLoader(true)] - private void load(AudioManager audio, OsuGame game) + private void load(AudioManager audio, OsuGame game, IdleTracker idleTracker) { this.game = game; + + isIdle.ValueChanged += updateIdleState; + if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); + sampleBack = audio.Sample.Get(@"Menu/button-back-select"); } + private void updateIdleState(bool isIdle) + { + if (isIdle && State != ButtonSystemState.Exit) + State = ButtonSystemState.Initial; + } + public bool OnPressed(GlobalAction action) { switch (action) @@ -266,9 +280,6 @@ namespace osu.Game.Screens.Menu protected override void Update() { - //if (OsuGame.IdleTime > 6000 && State != MenuState.Exit) - // State = MenuState.Initial; - base.Update(); if (logo != null) diff --git a/osu.Game/Screens/Multi/Screens/Match/Match.cs b/osu.Game/Screens/Multi/Screens/Match/Match.cs index ce3f7825a4..f7d98df60e 100644 --- a/osu.Game/Screens/Multi/Screens/Match/Match.cs +++ b/osu.Game/Screens/Multi/Screens/Match/Match.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi.Screens.Match.Settings; using osu.Game.Screens.Select; using osu.Game.Users; @@ -34,11 +35,15 @@ namespace osu.Game.Screens.Multi.Screens.Match { this.room = room; Header header; + RoomSettingsOverlay settings; Info info; Children = new Drawable[] { - header = new Header(), + header = new Header + { + Depth = -1, + }, info = new Info { Margin = new MarginPadding { Top = Header.HEIGHT }, @@ -48,6 +53,16 @@ namespace osu.Game.Screens.Multi.Screens.Match RelativeSizeAxes = Axes.Both, Padding = new MarginPadding { Top = Header.HEIGHT + Info.HEIGHT }, }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = Header.HEIGHT }, + Child = settings = new RoomSettingsOverlay(room) + { + RelativeSizeAxes = Axes.Both, + Height = 0.9f, + }, + }, }; header.OnRequestSelectBeatmap = () => Push(new MatchSongSelect()); @@ -59,6 +74,20 @@ namespace osu.Game.Screens.Multi.Screens.Match info.Beatmap = b; }, true); + header.Tabs.Current.ValueChanged += t => + { + if (t == MatchHeaderPage.Settings) + settings.Show(); + else + settings.Hide(); + }; + + settings.StateChanged += s => + { + if (s == Visibility.Hidden) + header.Tabs.Current.Value = MatchHeaderPage.Room; + }; + nameBind.BindTo(room.Name); nameBind.BindValueChanged(n => info.Name = n, true); diff --git a/osu.Game/Screens/Multi/Screens/Match/Settings/GameTypePicker.cs b/osu.Game/Screens/Multi/Screens/Match/Settings/GameTypePicker.cs new file mode 100644 index 0000000000..cd8b081b4e --- /dev/null +++ b/osu.Game/Screens/Multi/Screens/Match/Settings/GameTypePicker.cs @@ -0,0 +1,110 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Online.Multiplayer; +using osu.Game.Screens.Multi.Components; +using osuTK; + +namespace osu.Game.Screens.Multi.Screens.Match.Settings +{ + public class GameTypePicker : TabControl + { + private const float height = 40; + private const float selection_width = 3; + + protected override TabItem CreateTabItem(GameType value) => new GameTypePickerItem(value); + protected override Dropdown CreateDropdown() => null; + + public GameTypePicker() + { + Height = height + selection_width * 2; + TabContainer.Spacing = new Vector2(10 - selection_width * 2); + + AddItem(new GameTypeTag()); + AddItem(new GameTypeVersus()); + AddItem(new GameTypeTagTeam()); + AddItem(new GameTypeTeamVersus()); + } + + private class GameTypePickerItem : TabItem + { + private const float transition_duration = 200; + + private readonly CircularContainer hover, selection; + + public GameTypePickerItem(GameType value) : base(value) + { + AutoSizeAxes = Axes.Both; + + Children = new Drawable[] + { + selection = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + new DrawableGameType(Value) + { + Size = new Vector2(height), + Margin = new MarginPadding(selection_width), + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(selection_width), + Child = hover = new CircularContainer + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + }, + }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + selection.Colour = colours.Yellow; + } + + protected override bool OnHover(HoverEvent e) + { + hover.FadeTo(0.05f, transition_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hover.FadeOut(transition_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + + protected override void OnActivated() + { + selection.FadeIn(transition_duration, Easing.OutQuint); + } + + protected override void OnDeactivated() + { + selection.FadeOut(transition_duration, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Screens/Match/Settings/RoomAvailabilityPicker.cs b/osu.Game/Screens/Multi/Screens/Match/Settings/RoomAvailabilityPicker.cs new file mode 100644 index 0000000000..251bd062ec --- /dev/null +++ b/osu.Game/Screens/Multi/Screens/Match/Settings/RoomAvailabilityPicker.cs @@ -0,0 +1,105 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Multiplayer; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Multi.Screens.Match.Settings +{ + public class RoomAvailabilityPicker : TabControl + { + protected override TabItem CreateTabItem(RoomAvailability value) => new RoomAvailabilityPickerItem(value); + protected override Dropdown CreateDropdown() => null; + + public RoomAvailabilityPicker() + { + RelativeSizeAxes = Axes.X; + Height = 35; + + TabContainer.Spacing = new Vector2(10); + + AddItem(RoomAvailability.Public); + AddItem(RoomAvailability.FriendsOnly); + AddItem(RoomAvailability.InviteOnly); + } + + private class RoomAvailabilityPickerItem : TabItem + { + private const float transition_duration = 200; + + private readonly Box hover, selection; + + public RoomAvailabilityPickerItem(RoomAvailability value) : base(value) + { + RelativeSizeAxes = Axes.Y; + Width = 120; + Masking = true; + CornerRadius = 5; + + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"3d3943"), + }, + selection = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + hover = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + Alpha = 0, + }, + new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = @"Exo2.0-Bold", + Text = value.GetDescription(), + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + selection.Colour = colours.GreenLight; + } + + protected override bool OnHover(HoverEvent e) + { + hover.FadeTo(0.05f, transition_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hover.FadeOut(transition_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + + protected override void OnActivated() + { + selection.FadeIn(transition_duration, Easing.OutQuint); + } + + protected override void OnDeactivated() + { + selection.FadeOut(transition_duration, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/Multi/Screens/Match/Settings/RoomSettingsOverlay.cs b/osu.Game/Screens/Multi/Screens/Match/Settings/RoomSettingsOverlay.cs new file mode 100644 index 0000000000..d45ba48f0e --- /dev/null +++ b/osu.Game/Screens/Multi/Screens/Match/Settings/RoomSettingsOverlay.cs @@ -0,0 +1,270 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Framework.Configuration; +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.Graphics.UserInterface; +using osu.Game.Online.Multiplayer; +using osu.Game.Overlays.SearchableList; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Screens.Multi.Screens.Match.Settings +{ + public class RoomSettingsOverlay : FocusedOverlayContainer + { + private const float transition_duration = 350; + private const float field_padding = 45; + + private readonly Bindable nameBind = new Bindable(); + private readonly Bindable availabilityBind = new Bindable(); + private readonly Bindable typeBind = new Bindable(); + private readonly Bindable maxParticipantsBind = new Bindable(); + + private readonly Container content; + private readonly OsuSpriteText typeLabel; + + protected readonly OsuTextBox NameField, MaxParticipantsField; + protected readonly RoomAvailabilityPicker AvailabilityPicker; + protected readonly GameTypePicker TypePicker; + protected readonly TriangleButton ApplyButton; + + public RoomSettingsOverlay(Room room) + { + Masking = true; + + Child = content = new Container + { + RelativeSizeAxes = Axes.Both, + RelativePositionAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex(@"28242d"), + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 35, Bottom = 75, Horizontal = SearchableListOverlay.WIDTH_PADDING }, + Children = new[] + { + new SectionContainer + { + Padding = new MarginPadding { Right = field_padding / 2 }, + Children = new[] + { + new Section("ROOM NAME") + { + Child = NameField = new SettingsTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + OnCommit = (sender, text) => apply(), + }, + }, + new Section("ROOM VISIBILITY") + { + Child = AvailabilityPicker = new RoomAvailabilityPicker(), + }, + new Section("GAME TYPE") + { + Child = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(7), + Children = new Drawable[] + { + TypePicker = new GameTypePicker + { + RelativeSizeAxes = Axes.X, + }, + typeLabel = new OsuSpriteText + { + TextSize = 14, + }, + }, + }, + }, + }, + }, + new SectionContainer + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Padding = new MarginPadding { Left = field_padding / 2 }, + Children = new[] + { + new Section("MAX PARTICIPANTS") + { + Child = MaxParticipantsField = new SettingsNumberTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + OnCommit = (sender, text) => apply(), + }, + }, + new Section("PASSWORD (OPTIONAL)") + { + Child = new SettingsPasswordTextBox + { + RelativeSizeAxes = Axes.X, + TabbableContentContainer = this, + OnCommit = (sender, text) => apply(), + }, + }, + }, + }, + }, + }, + ApplyButton = new ApplySettingsButton + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(230, 35), + Margin = new MarginPadding { Bottom = 20 }, + Action = apply, + }, + }, + }; + + TypePicker.Current.ValueChanged += t => typeLabel.Text = t.Name; + + nameBind.ValueChanged += n => NameField.Text = n; + availabilityBind.ValueChanged += a => AvailabilityPicker.Current.Value = a; + typeBind.ValueChanged += t => TypePicker.Current.Value = t; + maxParticipantsBind.ValueChanged += m => MaxParticipantsField.Text = m?.ToString(); + + nameBind.BindTo(room.Name); + availabilityBind.BindTo(room.Availability); + typeBind.BindTo(room.Type); + maxParticipantsBind.BindTo(room.MaxParticipants); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + typeLabel.Colour = colours.Yellow; + } + + protected override void PopIn() + { + // reapply the rooms values if the overlay was completely closed + if (content.Y == -1) + { + nameBind.TriggerChange(); + availabilityBind.TriggerChange(); + typeBind.TriggerChange(); + maxParticipantsBind.TriggerChange(); + } + + content.MoveToY(0, transition_duration, Easing.OutQuint); + } + + protected override void PopOut() + { + content.MoveToY(-1, transition_duration, Easing.InSine); + } + + private void apply() + { + nameBind.Value = NameField.Text; + availabilityBind.Value = AvailabilityPicker.Current.Value; + typeBind.Value = TypePicker.Current.Value; + + if (int.TryParse(MaxParticipantsField.Text, out int max)) + maxParticipantsBind.Value = max; + else + maxParticipantsBind.Value = null; + + Hide(); + } + + private class SettingsTextBox : OsuTextBox + { + protected override Color4 BackgroundUnfocused => Color4.Black; + protected override Color4 BackgroundFocused => Color4.Black; + } + + private class SettingsNumberTextBox : SettingsTextBox + { + protected override bool CanAddCharacter(char character) => char.IsNumber(character); + } + + private class SettingsPasswordTextBox : OsuPasswordTextBox + { + protected override Color4 BackgroundUnfocused => Color4.Black; + protected override Color4 BackgroundFocused => Color4.Black; + } + + private class SectionContainer : FillFlowContainer
+ { + public SectionContainer() + { + RelativeSizeAxes = Axes.Both; + Width = 0.5f; + Direction = FillDirection.Vertical; + Spacing = new Vector2(field_padding); + } + } + + private class Section : Container + { + private readonly Container content; + + protected override Container Content => content; + + public Section(string title) + { + AutoSizeAxes = Axes.Y; + RelativeSizeAxes = Axes.X; + + InternalChild = new FillFlowContainer + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + Direction = FillDirection.Vertical, + Spacing = new Vector2(5), + Children = new Drawable[] + { + new OsuSpriteText + { + TextSize = 12, + Font = @"Exo2.0-Bold", + Text = title.ToUpper(), + }, + content = new Container + { + AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.X, + }, + }, + }; + } + } + + private class ApplySettingsButton : TriangleButton + { + public ApplySettingsButton() + { + Text = "Apply"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + BackgroundColour = colours.Yellow; + Triangles.ColourLight = colours.YellowLight; + Triangles.ColourDark = colours.YellowDark; + } + } + } +}