mirror of
https://github.com/ppy/osu.git
synced 2024-12-13 08:32:57 +08:00
Merge branch 'master' into change-default-hover-click-sample
This commit is contained in:
commit
9c6c1dd237
@ -33,13 +33,13 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
|
||||
private class MouseInputHelper : Drawable, IKeyBindingHandler<CatchAction>, IRequireHighFrequencyMousePosition
|
||||
{
|
||||
private readonly Catcher catcher;
|
||||
private readonly CatcherArea catcherArea;
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
public MouseInputHelper(CatchPlayfield playfield)
|
||||
{
|
||||
catcher = playfield.CatcherArea.MovableCatcher;
|
||||
catcherArea = playfield.CatcherArea;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Mods
|
||||
|
||||
protected override bool OnMouseMove(MouseMoveEvent e)
|
||||
{
|
||||
catcher.UpdatePosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH);
|
||||
catcherArea.SetCatcherPosition(e.MousePosition.X / DrawSize.X * CatchPlayfield.WIDTH);
|
||||
return base.OnMouseMove(e);
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Pooling;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
@ -26,7 +25,7 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public class Catcher : SkinReloadableDrawable, IKeyBindingHandler<CatchAction>
|
||||
public class Catcher : SkinReloadableDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// The default colour used to tint hyper-dash fruit, along with the moving catcher, its trail
|
||||
@ -54,6 +53,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
/// </summary>
|
||||
public const double BASE_SPEED = 1.0;
|
||||
|
||||
/// <summary>
|
||||
/// The current speed of the catcher.
|
||||
/// </summary>
|
||||
public double Speed => (Dashing ? 1 : 0.5) * BASE_SPEED * hyperDashModifier;
|
||||
|
||||
/// <summary>
|
||||
/// The amount by which caught fruit should be offset from the plate surface to make them look visually "caught".
|
||||
/// </summary>
|
||||
@ -96,7 +100,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
public bool Dashing
|
||||
{
|
||||
get => dashing;
|
||||
protected set
|
||||
set
|
||||
{
|
||||
if (value == dashing) return;
|
||||
|
||||
@ -106,6 +110,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
}
|
||||
}
|
||||
|
||||
public Direction VisualDirection
|
||||
{
|
||||
get => Scale.X > 0 ? Direction.Right : Direction.Left;
|
||||
set => Scale = new Vector2((value == Direction.Right ? 1 : -1) * Math.Abs(Scale.X), Scale.Y);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Width of the area that can be used to attempt catches during gameplay.
|
||||
/// </summary>
|
||||
@ -116,8 +126,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
private Color4 hyperDashColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||
private Color4 hyperDashEndGlowColour = DEFAULT_HYPER_DASH_COLOUR;
|
||||
|
||||
private int currentDirection;
|
||||
|
||||
private double hyperDashModifier = 1;
|
||||
private int hyperDashDirection;
|
||||
private float hyperDashTargetPosition;
|
||||
@ -315,55 +323,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePosition(float position)
|
||||
{
|
||||
position = Math.Clamp(position, 0, CatchPlayfield.WIDTH);
|
||||
|
||||
if (position == X)
|
||||
return;
|
||||
|
||||
Scale = new Vector2(Math.Abs(Scale.X) * (position > X ? 1 : -1), Scale.Y);
|
||||
X = position;
|
||||
}
|
||||
|
||||
public bool OnPressed(CatchAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case CatchAction.MoveLeft:
|
||||
currentDirection--;
|
||||
return true;
|
||||
|
||||
case CatchAction.MoveRight:
|
||||
currentDirection++;
|
||||
return true;
|
||||
|
||||
case CatchAction.Dash:
|
||||
Dashing = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(CatchAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case CatchAction.MoveLeft:
|
||||
currentDirection++;
|
||||
break;
|
||||
|
||||
case CatchAction.MoveRight:
|
||||
currentDirection--;
|
||||
break;
|
||||
|
||||
case CatchAction.Dash:
|
||||
Dashing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Drop any fruit off the plate.
|
||||
/// </summary>
|
||||
@ -405,15 +364,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (currentDirection == 0) return;
|
||||
|
||||
var direction = Math.Sign(currentDirection);
|
||||
|
||||
var dashModifier = Dashing ? 1 : 0.5;
|
||||
var speed = BASE_SPEED * dashModifier * hyperDashModifier;
|
||||
|
||||
UpdatePosition((float)(X + direction * Clock.ElapsedFrameTime * speed));
|
||||
|
||||
// Correct overshooting.
|
||||
if ((hyperDashDirection > 0 && hyperDashTargetPosition < X) ||
|
||||
(hyperDashDirection < 0 && hyperDashTargetPosition > X))
|
||||
|
@ -1,8 +1,10 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Catch.Judgements;
|
||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||
@ -14,13 +16,20 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public class CatcherArea : Container
|
||||
public class CatcherArea : Container, IKeyBindingHandler<CatchAction>
|
||||
{
|
||||
public const float CATCHER_SIZE = 106.75f;
|
||||
|
||||
public readonly Catcher MovableCatcher;
|
||||
private readonly CatchComboDisplay comboDisplay;
|
||||
|
||||
/// <summary>
|
||||
/// <c>-1</c> when only left button is pressed.
|
||||
/// <c>1</c> when only right button is pressed.
|
||||
/// <c>0</c> when none or both left and right buttons are pressed.
|
||||
/// </summary>
|
||||
private int currentDirection;
|
||||
|
||||
public CatcherArea(Container<CaughtObject> droppedObjectContainer, BeatmapDifficulty difficulty = null)
|
||||
{
|
||||
Size = new Vector2(CatchPlayfield.WIDTH, CATCHER_SIZE);
|
||||
@ -63,16 +72,73 @@ namespace osu.Game.Rulesets.Catch.UI
|
||||
MovableCatcher.OnRevertResult(hitObject, result);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
var replayState = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState<CatchAction>)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
|
||||
|
||||
SetCatcherPosition(
|
||||
replayState?.CatcherX ??
|
||||
(float)(MovableCatcher.X + MovableCatcher.Speed * currentDirection * Clock.ElapsedFrameTime));
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
var state = (GetContainingInputManager().CurrentState as RulesetInputManagerInputState<CatchAction>)?.LastReplayState as CatchFramedReplayInputHandler.CatchReplayState;
|
||||
|
||||
if (state?.CatcherX != null)
|
||||
MovableCatcher.X = state.CatcherX.Value;
|
||||
|
||||
comboDisplay.X = MovableCatcher.X;
|
||||
}
|
||||
|
||||
public void SetCatcherPosition(float X)
|
||||
{
|
||||
float lastPosition = MovableCatcher.X;
|
||||
float newPosition = Math.Clamp(X, 0, CatchPlayfield.WIDTH);
|
||||
|
||||
MovableCatcher.X = newPosition;
|
||||
|
||||
if (lastPosition < newPosition)
|
||||
MovableCatcher.VisualDirection = Direction.Right;
|
||||
else if (lastPosition > newPosition)
|
||||
MovableCatcher.VisualDirection = Direction.Left;
|
||||
}
|
||||
|
||||
public bool OnPressed(CatchAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case CatchAction.MoveLeft:
|
||||
currentDirection--;
|
||||
return true;
|
||||
|
||||
case CatchAction.MoveRight:
|
||||
currentDirection++;
|
||||
return true;
|
||||
|
||||
case CatchAction.Dash:
|
||||
MovableCatcher.Dashing = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(CatchAction action)
|
||||
{
|
||||
switch (action)
|
||||
{
|
||||
case CatchAction.MoveLeft:
|
||||
currentDirection++;
|
||||
break;
|
||||
|
||||
case CatchAction.MoveRight:
|
||||
currentDirection--;
|
||||
break;
|
||||
|
||||
case CatchAction.Dash:
|
||||
MovableCatcher.Dashing = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
11
osu.Game.Rulesets.Catch/UI/Direction.cs
Normal file
11
osu.Game.Rulesets.Catch/UI/Direction.cs
Normal file
@ -0,0 +1,11 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.UI
|
||||
{
|
||||
public enum Direction
|
||||
{
|
||||
Right = 1,
|
||||
Left = -1
|
||||
}
|
||||
}
|
BIN
osu.Game.Tests/Resources/special-skin/scorebar-bg.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-bg.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 250 B |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-0.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-0.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-1.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-2.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-3.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-colour-3.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
BIN
osu.Game.Tests/Resources/special-skin/scorebar-marker.png
Normal file
BIN
osu.Game.Tests/Resources/special-skin/scorebar-marker.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 126 B |
240
osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs
Normal file
240
osu.Game.Tests/Visual/Online/TestSceneMessageNotifier.cs
Normal file
@ -0,0 +1,240 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Users;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
public class TestSceneMessageNotifier : OsuManualInputManagerTestScene
|
||||
{
|
||||
private User friend;
|
||||
private Channel publicChannel;
|
||||
private Channel privateMessageChannel;
|
||||
private TestContainer testContainer;
|
||||
|
||||
private int messageIdCounter;
|
||||
|
||||
[SetUp]
|
||||
public void Setup()
|
||||
{
|
||||
if (API is DummyAPIAccess daa)
|
||||
{
|
||||
daa.HandleRequest = dummyAPIHandleRequest;
|
||||
}
|
||||
|
||||
friend = new User { Id = 0, Username = "Friend" };
|
||||
publicChannel = new Channel { Id = 1, Name = "osu" };
|
||||
privateMessageChannel = new Channel(friend) { Id = 2, Name = friend.Username, Type = ChannelType.PM };
|
||||
|
||||
Schedule(() =>
|
||||
{
|
||||
Child = testContainer = new TestContainer(new[] { publicChannel, privateMessageChannel })
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
testContainer.ChatOverlay.Show();
|
||||
});
|
||||
}
|
||||
|
||||
private bool dummyAPIHandleRequest(APIRequest request)
|
||||
{
|
||||
switch (request)
|
||||
{
|
||||
case GetMessagesRequest messagesRequest:
|
||||
messagesRequest.TriggerSuccess(new List<Message>(0));
|
||||
return true;
|
||||
|
||||
case CreateChannelRequest createChannelRequest:
|
||||
var apiChatChannel = new APIChatChannel
|
||||
{
|
||||
RecentMessages = new List<Message>(0),
|
||||
ChannelID = (int)createChannelRequest.Channel.Id
|
||||
};
|
||||
createChannelRequest.TriggerSuccess(apiChatChannel);
|
||||
return true;
|
||||
|
||||
case ListChannelsRequest listChannelsRequest:
|
||||
listChannelsRequest.TriggerSuccess(new List<Channel>(1) { publicChannel });
|
||||
return true;
|
||||
|
||||
case GetUpdatesRequest updatesRequest:
|
||||
updatesRequest.TriggerSuccess(new GetUpdatesResponse
|
||||
{
|
||||
Messages = new List<Message>(0),
|
||||
Presence = new List<Channel>(0)
|
||||
});
|
||||
return true;
|
||||
|
||||
case JoinChannelRequest joinChannelRequest:
|
||||
joinChannelRequest.TriggerSuccess();
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPublicChannelMention()
|
||||
{
|
||||
AddStep("switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel);
|
||||
|
||||
AddStep("receive public message", () => receiveMessage(friend, publicChannel, "Hello everyone"));
|
||||
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||
|
||||
AddStep("receive message containing mention", () => receiveMessage(friend, publicChannel, $"Hello {API.LocalUser.Value.Username.ToLowerInvariant()}!"));
|
||||
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
|
||||
|
||||
AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show());
|
||||
AddStep("click notification", clickNotification<MessageNotifier.MentionNotification>);
|
||||
|
||||
AddAssert("chat overlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible);
|
||||
AddAssert("public channel is selected", () => testContainer.ChannelManager.CurrentChannel.Value == publicChannel);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPrivateMessageNotification()
|
||||
{
|
||||
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
|
||||
|
||||
AddStep("receive PM", () => receiveMessage(friend, privateMessageChannel, $"Hello {API.LocalUser.Value.Username}"));
|
||||
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
|
||||
|
||||
AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show());
|
||||
AddStep("click notification", clickNotification<MessageNotifier.PrivateMessageNotification>);
|
||||
|
||||
AddAssert("chat overlay is open", () => testContainer.ChatOverlay.State.Value == Visibility.Visible);
|
||||
AddAssert("PM channel is selected", () => testContainer.ChannelManager.CurrentChannel.Value == privateMessageChannel);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoNotificationWhenPMChannelOpen()
|
||||
{
|
||||
AddStep("switch to PMs", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel);
|
||||
|
||||
AddStep("receive PM", () => receiveMessage(friend, privateMessageChannel, "you're reading this, right?"));
|
||||
|
||||
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoNotificationWhenMentionedInOpenPublicChannel()
|
||||
{
|
||||
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
|
||||
|
||||
AddStep("receive mention", () => receiveMessage(friend, publicChannel, $"{API.LocalUser.Value.Username.ToUpperInvariant()} has been reading this"));
|
||||
|
||||
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoNotificationOnSelfMention()
|
||||
{
|
||||
AddStep("switch to PM channel", () => testContainer.ChannelManager.CurrentChannel.Value = privateMessageChannel);
|
||||
|
||||
AddStep("receive self-mention", () => receiveMessage(API.LocalUser.Value, publicChannel, $"my name is {API.LocalUser.Value.Username}"));
|
||||
|
||||
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoNotificationOnPMFromSelf()
|
||||
{
|
||||
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
|
||||
|
||||
AddStep("receive PM from self", () => receiveMessage(API.LocalUser.Value, privateMessageChannel, "hey hey"));
|
||||
|
||||
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNotificationsNotFiredTwice()
|
||||
{
|
||||
AddStep("switch to public channel", () => testContainer.ChannelManager.CurrentChannel.Value = publicChannel);
|
||||
|
||||
AddStep("receive same PM twice", () =>
|
||||
{
|
||||
var message = createMessage(friend, privateMessageChannel, "hey hey");
|
||||
privateMessageChannel.AddNewMessages(message, message);
|
||||
});
|
||||
|
||||
AddStep("open notification overlay", () => testContainer.NotificationOverlay.Show());
|
||||
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
|
||||
}
|
||||
|
||||
private void receiveMessage(User sender, Channel channel, string content) => channel.AddNewMessages(createMessage(sender, channel, content));
|
||||
|
||||
private Message createMessage(User sender, Channel channel, string content) => new Message(messageIdCounter++)
|
||||
{
|
||||
Content = content,
|
||||
Sender = sender,
|
||||
ChannelId = channel.Id
|
||||
};
|
||||
|
||||
private void clickNotification<T>() where T : Notification
|
||||
{
|
||||
var notification = testContainer.NotificationOverlay.ChildrenOfType<T>().Single();
|
||||
|
||||
InputManager.MoveMouseTo(notification);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
}
|
||||
|
||||
private class TestContainer : Container
|
||||
{
|
||||
[Cached]
|
||||
public ChannelManager ChannelManager { get; } = new ChannelManager();
|
||||
|
||||
[Cached]
|
||||
public NotificationOverlay NotificationOverlay { get; } = new NotificationOverlay
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
};
|
||||
|
||||
[Cached]
|
||||
public ChatOverlay ChatOverlay { get; } = new ChatOverlay();
|
||||
|
||||
private readonly MessageNotifier messageNotifier = new MessageNotifier();
|
||||
|
||||
private readonly Channel[] channels;
|
||||
|
||||
public TestContainer(Channel[] channels)
|
||||
{
|
||||
this.channels = channels;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
ChannelManager,
|
||||
ChatOverlay,
|
||||
NotificationOverlay,
|
||||
messageNotifier,
|
||||
};
|
||||
|
||||
((BindableList<Channel>)ChannelManager.AvailableChannels).AddRange(channels);
|
||||
|
||||
foreach (var channel in channels)
|
||||
ChannelManager.JoinChannel(channel);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -61,6 +61,9 @@ namespace osu.Game.Configuration
|
||||
|
||||
SetDefault(OsuSetting.ShowOnlineExplicitContent, false);
|
||||
|
||||
SetDefault(OsuSetting.NotifyOnUsernameMentioned, true);
|
||||
SetDefault(OsuSetting.NotifyOnPrivateMessage, true);
|
||||
|
||||
// Audio
|
||||
SetDefault(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01);
|
||||
|
||||
@ -259,6 +262,8 @@ namespace osu.Game.Configuration
|
||||
ScalingSizeY,
|
||||
UIScale,
|
||||
IntroSequence,
|
||||
NotifyOnUsernameMentioned,
|
||||
NotifyOnPrivateMessage,
|
||||
UIHoldActivationDelay,
|
||||
HitLighting,
|
||||
MenuBackgroundSource,
|
||||
|
@ -11,11 +11,11 @@ namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class CreateChannelRequest : APIRequest<APIChatChannel>
|
||||
{
|
||||
private readonly Channel channel;
|
||||
public readonly Channel Channel;
|
||||
|
||||
public CreateChannelRequest(Channel channel)
|
||||
{
|
||||
this.channel = channel;
|
||||
Channel = channel;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
@ -24,7 +24,7 @@ namespace osu.Game.Online.API.Requests
|
||||
req.Method = HttpMethod.Post;
|
||||
|
||||
req.AddParameter("type", $"{ChannelType.PM}");
|
||||
req.AddParameter("target_id", $"{channel.Users.First().Id}");
|
||||
req.AddParameter("target_id", $"{Channel.Users.First().Id}");
|
||||
|
||||
return req;
|
||||
}
|
||||
|
@ -63,5 +63,7 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
|
||||
public override int GetHashCode() => Id.GetHashCode();
|
||||
|
||||
public override string ToString() => $"[{ChannelId}] ({Id}) {Sender}: {Content}";
|
||||
}
|
||||
}
|
||||
|
181
osu.Game/Online/Chat/MessageNotifier.cs
Normal file
181
osu.Game/Online/Chat/MessageNotifier.cs
Normal file
@ -0,0 +1,181 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
/// <summary>
|
||||
/// Component that handles creating and posting notifications for incoming messages.
|
||||
/// </summary>
|
||||
public class MessageNotifier : Component
|
||||
{
|
||||
[Resolved]
|
||||
private NotificationOverlay notifications { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private ChatOverlay chatOverlay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private ChannelManager channelManager { get; set; }
|
||||
|
||||
private Bindable<bool> notifyOnUsername;
|
||||
private Bindable<bool> notifyOnPrivateMessage;
|
||||
|
||||
private readonly IBindable<User> localUser = new Bindable<User>();
|
||||
private readonly IBindableList<Channel> joinedChannels = new BindableList<Channel>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config, IAPIProvider api)
|
||||
{
|
||||
notifyOnUsername = config.GetBindable<bool>(OsuSetting.NotifyOnUsernameMentioned);
|
||||
notifyOnPrivateMessage = config.GetBindable<bool>(OsuSetting.NotifyOnPrivateMessage);
|
||||
|
||||
localUser.BindTo(api.LocalUser);
|
||||
joinedChannels.BindTo(channelManager.JoinedChannels);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
joinedChannels.BindCollectionChanged(channelsChanged, true);
|
||||
}
|
||||
|
||||
private void channelsChanged(object sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (var channel in e.NewItems.Cast<Channel>())
|
||||
channel.NewMessagesArrived += checkNewMessages;
|
||||
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (var channel in e.OldItems.Cast<Channel>())
|
||||
channel.NewMessagesArrived -= checkNewMessages;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void checkNewMessages(IEnumerable<Message> messages)
|
||||
{
|
||||
if (!messages.Any())
|
||||
return;
|
||||
|
||||
var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == messages.First().ChannelId);
|
||||
|
||||
if (channel == null)
|
||||
return;
|
||||
|
||||
// Only send notifications, if ChatOverlay and the target channel aren't visible.
|
||||
if (chatOverlay.IsPresent && channelManager.CurrentChannel.Value == channel)
|
||||
return;
|
||||
|
||||
foreach (var message in messages.OrderByDescending(m => m.Id))
|
||||
{
|
||||
// ignore messages that already have been read
|
||||
if (message.Id <= channel.LastReadId)
|
||||
return;
|
||||
|
||||
if (message.Sender.Id == localUser.Value.Id)
|
||||
continue;
|
||||
|
||||
// check for private messages first to avoid both posting two notifications about the same message
|
||||
if (checkForPMs(channel, message))
|
||||
continue;
|
||||
|
||||
checkForMentions(channel, message);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks whether the user enabled private message notifications and whether specified <paramref name="message"/> is a direct message.
|
||||
/// </summary>
|
||||
/// <param name="channel">The channel associated to the <paramref name="message"/></param>
|
||||
/// <param name="message">The message to be checked</param>
|
||||
/// <returns>Whether a notification was fired.</returns>
|
||||
private bool checkForPMs(Channel channel, Message message)
|
||||
{
|
||||
if (!notifyOnPrivateMessage.Value || channel.Type != ChannelType.PM)
|
||||
return false;
|
||||
|
||||
notifications.Post(new PrivateMessageNotification(message.Sender.Username, channel));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void checkForMentions(Channel channel, Message message)
|
||||
{
|
||||
if (!notifyOnUsername.Value || !checkContainsUsername(message.Content, localUser.Value.Username)) return;
|
||||
|
||||
notifications.Post(new MentionNotification(message.Sender.Username, channel));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if <paramref name="message"/> contains <paramref name="username"/>.
|
||||
/// This will match against the case where underscores are used instead of spaces (which is how osu-stable handles usernames with spaces).
|
||||
/// </summary>
|
||||
private static bool checkContainsUsername(string message, string username) => message.Contains(username, StringComparison.OrdinalIgnoreCase) || message.Contains(username.Replace(' ', '_'), StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
public class PrivateMessageNotification : OpenChannelNotification
|
||||
{
|
||||
public PrivateMessageNotification(string username, Channel channel)
|
||||
: base(channel)
|
||||
{
|
||||
Icon = FontAwesome.Solid.Envelope;
|
||||
Text = $"You received a private message from '{username}'. Click to read it!";
|
||||
}
|
||||
}
|
||||
|
||||
public class MentionNotification : OpenChannelNotification
|
||||
{
|
||||
public MentionNotification(string username, Channel channel)
|
||||
: base(channel)
|
||||
{
|
||||
Icon = FontAwesome.Solid.At;
|
||||
Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!";
|
||||
}
|
||||
}
|
||||
|
||||
public abstract class OpenChannelNotification : SimpleNotification
|
||||
{
|
||||
protected OpenChannelNotification(Channel channel)
|
||||
{
|
||||
this.channel = channel;
|
||||
}
|
||||
|
||||
private readonly Channel channel;
|
||||
|
||||
public override bool IsImportant => false;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager)
|
||||
{
|
||||
IconBackgound.Colour = colours.PurpleDark;
|
||||
|
||||
Activated = delegate
|
||||
{
|
||||
notificationOverlay.Hide();
|
||||
chatOverlay.Show();
|
||||
channelManager.CurrentChannel.Value = channel;
|
||||
|
||||
return true;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -728,6 +728,7 @@ namespace osu.Game
|
||||
var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true);
|
||||
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(new MessageNotifier(), AddInternal, true);
|
||||
loadComponentSingleFile(Settings = new SettingsOverlay { GetToolbarHeight = () => ToolbarOffset }, leftFloatingOverlayContent.Add, true);
|
||||
var changelogOverlay = loadComponentSingleFile(new ChangelogOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(userProfile = new UserProfileOverlay(), overlayContent.Add, true);
|
||||
|
@ -6,18 +6,18 @@ using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Chat;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
|
@ -0,0 +1,32 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Configuration;
|
||||
|
||||
namespace osu.Game.Overlays.Settings.Sections.Online
|
||||
{
|
||||
public class AlertsAndPrivacySettings : SettingsSubsection
|
||||
{
|
||||
protected override string Header => "Alerts and Privacy";
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Show a notification when someone mentions your name",
|
||||
Current = config.GetBindable<bool>(OsuSetting.NotifyOnUsernameMentioned)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = "Show a notification when you receive a private message",
|
||||
Current = config.GetBindable<bool>(OsuSetting.NotifyOnPrivateMessage)
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ namespace osu.Game.Overlays.Settings.Sections
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new WebSettings(),
|
||||
new AlertsAndPrivacySettings(),
|
||||
new IntegrationSettings()
|
||||
};
|
||||
}
|
||||
|
@ -148,9 +148,9 @@ namespace osu.Game.Skinning
|
||||
}
|
||||
}
|
||||
|
||||
internal class LegacyOldStyleFill : LegacyHealthPiece
|
||||
internal abstract class LegacyFill : LegacyHealthPiece
|
||||
{
|
||||
public LegacyOldStyleFill(ISkin skin)
|
||||
protected LegacyFill(ISkin skin)
|
||||
{
|
||||
// required for sizing correctly..
|
||||
var firstFrame = getTexture(skin, "colour-0");
|
||||
@ -162,27 +162,29 @@ namespace osu.Game.Skinning
|
||||
}
|
||||
else
|
||||
{
|
||||
InternalChild = skin.GetAnimation("scorebar-colour", true, true, startAtCurrentTime: false, applyConfigFrameRate: true) ?? Drawable.Empty();
|
||||
InternalChild = skin.GetAnimation("scorebar-colour", true, true, startAtCurrentTime: false, applyConfigFrameRate: true) ?? Empty();
|
||||
Size = new Vector2(firstFrame.DisplayWidth, firstFrame.DisplayHeight);
|
||||
}
|
||||
|
||||
Position = new Vector2(3, 10) * 1.6f;
|
||||
Masking = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal class LegacyNewStyleFill : LegacyHealthPiece
|
||||
internal class LegacyOldStyleFill : LegacyFill
|
||||
{
|
||||
public LegacyOldStyleFill(ISkin skin)
|
||||
: base(skin)
|
||||
{
|
||||
Position = new Vector2(3, 10) * 1.6f;
|
||||
}
|
||||
}
|
||||
|
||||
internal class LegacyNewStyleFill : LegacyFill
|
||||
{
|
||||
public LegacyNewStyleFill(ISkin skin)
|
||||
: base(skin)
|
||||
{
|
||||
InternalChild = new Sprite
|
||||
{
|
||||
Texture = getTexture(skin, "colour"),
|
||||
};
|
||||
|
||||
Size = InternalChild.Size;
|
||||
Position = new Vector2(7.5f, 7.8f) * 1.6f;
|
||||
Masking = true;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
|
Loading…
Reference in New Issue
Block a user