mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 17:32:54 +08:00
update the line endings
This commit is contained in:
parent
5bfb386226
commit
142e1b8587
@ -1,97 +1,97 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays.Chat;
|
||||
using osu.Game.Users;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
public class TestCaseChatTabControl : OsuTestCase
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(ChatTabControl),
|
||||
typeof(ChannelTabControl),
|
||||
typeof(UserTabControl)
|
||||
};
|
||||
|
||||
private readonly ChatTabControl chatTabControl;
|
||||
|
||||
public TestCaseChatTabControl()
|
||||
{
|
||||
SpriteText currentText;
|
||||
Add(new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
chatTabControl = new ChatTabControl
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Height = 50
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Black.Opacity(0.1f),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 50,
|
||||
Depth = -1,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Add(new Container
|
||||
{
|
||||
Origin = Anchor.TopLeft,
|
||||
Anchor = Anchor.TopLeft,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
currentText = new SpriteText
|
||||
{
|
||||
Text = "Currently selected chat: "
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chatTabControl.OnRequestLeave += chat => chatTabControl.RemoveItem(chat);
|
||||
chatTabControl.Current.ValueChanged += chat => currentText.Text = "Currently selected chat: " + chat.ToString();
|
||||
|
||||
AddStep("Add random user", () => addUser(RNG.Next(100000), RNG.Next().ToString()));
|
||||
AddRepeatStep("3 random users", () => addUser(RNG.Next(100000), RNG.Next().ToString()), 3);
|
||||
AddStep("Add random channel", () => addChannel(RNG.Next().ToString()));
|
||||
}
|
||||
|
||||
private void addUser(long id, string name)
|
||||
{
|
||||
chatTabControl.AddItem(new Channel(new User
|
||||
{
|
||||
Id = id,
|
||||
Username = name
|
||||
}));
|
||||
}
|
||||
|
||||
private void addChannel(string name)
|
||||
{
|
||||
chatTabControl.AddItem(new Channel
|
||||
{
|
||||
Name = name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays.Chat;
|
||||
using osu.Game.Users;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual
|
||||
{
|
||||
public class TestCaseChatTabControl : OsuTestCase
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(ChatTabControl),
|
||||
typeof(ChannelTabControl),
|
||||
typeof(UserTabControl)
|
||||
};
|
||||
|
||||
private readonly ChatTabControl chatTabControl;
|
||||
|
||||
public TestCaseChatTabControl()
|
||||
{
|
||||
SpriteText currentText;
|
||||
Add(new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
chatTabControl = new ChatTabControl
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Height = 50
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Colour = Color4.Black.Opacity(0.1f),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 50,
|
||||
Depth = -1,
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Add(new Container
|
||||
{
|
||||
Origin = Anchor.TopLeft,
|
||||
Anchor = Anchor.TopLeft,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
currentText = new SpriteText
|
||||
{
|
||||
Text = "Currently selected chat: "
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
chatTabControl.OnRequestLeave += chat => chatTabControl.RemoveItem(chat);
|
||||
chatTabControl.Current.ValueChanged += chat => currentText.Text = "Currently selected chat: " + chat.ToString();
|
||||
|
||||
AddStep("Add random user", () => addUser(RNG.Next(100000), RNG.Next().ToString()));
|
||||
AddRepeatStep("3 random users", () => addUser(RNG.Next(100000), RNG.Next().ToString()), 3);
|
||||
AddStep("Add random channel", () => addChannel(RNG.Next().ToString()));
|
||||
}
|
||||
|
||||
private void addUser(long id, string name)
|
||||
{
|
||||
chatTabControl.AddItem(new Channel(new User
|
||||
{
|
||||
Id = id,
|
||||
Username = name
|
||||
}));
|
||||
}
|
||||
|
||||
private void addChannel(string name)
|
||||
{
|
||||
chatTabControl.AddItem(new Channel
|
||||
{
|
||||
Name = name
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,28 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Online.Chat;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public abstract class APIMessagesRequest : APIRequest<List<Message>>
|
||||
{
|
||||
private long? sinceId;
|
||||
|
||||
protected APIMessagesRequest(long? sinceId)
|
||||
{
|
||||
this.sinceId = sinceId;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var req = base.CreateWebRequest();
|
||||
|
||||
if (sinceId.HasValue) req.AddParameter(@"since", sinceId.Value.ToString());
|
||||
|
||||
return req;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Online.Chat;
|
||||
|
||||
namespace osu.Game.Online.API
|
||||
{
|
||||
public abstract class APIMessagesRequest : APIRequest<List<Message>>
|
||||
{
|
||||
private long? sinceId;
|
||||
|
||||
protected APIMessagesRequest(long? sinceId)
|
||||
{
|
||||
this.sinceId = sinceId;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
{
|
||||
var req = base.CreateWebRequest();
|
||||
|
||||
if (sinceId.HasValue) req.AddParameter(@"since", sinceId.Value.ToString());
|
||||
|
||||
return req;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,17 +1,17 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetPrivateMessagesRequest : APIMessagesRequest
|
||||
{
|
||||
private long? since;
|
||||
|
||||
public GetPrivateMessagesRequest(long? sinceId = null)
|
||||
: base(sinceId)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string Target => @"chat/messages/private";
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class GetPrivateMessagesRequest : APIMessagesRequest
|
||||
{
|
||||
private long? since;
|
||||
|
||||
public GetPrivateMessagesRequest(long? sinceId = null)
|
||||
: base(sinceId)
|
||||
{
|
||||
}
|
||||
|
||||
protected override string Target => @"chat/messages/private";
|
||||
}
|
||||
}
|
||||
|
@ -1,302 +1,302 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages everything channel related
|
||||
/// </summary>
|
||||
public class ChannelManager : Component, IOnlineComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The channels the player joins on startup
|
||||
/// </summary>
|
||||
private readonly string[] defaultChannels =
|
||||
{
|
||||
@"#lazer",
|
||||
@"#osu",
|
||||
@"#lobby"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The currently opened channel
|
||||
/// </summary>
|
||||
public Bindable<Channel> CurrentChannel { get; } = new Bindable<Channel>();
|
||||
|
||||
/// <summary>
|
||||
/// The Channels the player has joined
|
||||
/// </summary>
|
||||
public ObservableCollection<Channel> JoinedChannels { get; } = new ObservableCollection<Channel>();
|
||||
|
||||
/// <summary>
|
||||
/// The channels available for the player to join
|
||||
/// </summary>
|
||||
public ObservableCollection<Channel> AvailableChannels { get; } = new ObservableCollection<Channel>();
|
||||
|
||||
private APIAccess api;
|
||||
private readonly Scheduler scheduler;
|
||||
private ScheduledDelegate fetchMessagesScheduleder;
|
||||
private GetMessagesRequest fetchMsgReq;
|
||||
private GetPrivateMessagesRequest fetchPrivateMsgReq;
|
||||
private long? lastChannelMsgId;
|
||||
private long? lastUserMsgId;
|
||||
|
||||
public void OpenChannel(string name)
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
|
||||
CurrentChannel.Value = AvailableChannels.FirstOrDefault(c => c.Name == name)
|
||||
?? throw new ArgumentException($"Channel {name} was not found.");
|
||||
}
|
||||
|
||||
public void OpenUserChannel(User user)
|
||||
{
|
||||
if (user == null)
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
|
||||
CurrentChannel.Value = JoinedChannels.FirstOrDefault(c => c.Target == TargetType.User && c.Id == user.Id)
|
||||
?? new Channel(user);
|
||||
}
|
||||
|
||||
public ChannelManager()
|
||||
{
|
||||
CurrentChannel.ValueChanged += currentChannelChanged;
|
||||
}
|
||||
|
||||
private void currentChannelChanged(Channel channel)
|
||||
{
|
||||
if (!JoinedChannels.Contains(channel))
|
||||
JoinedChannels.Add(channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posts a message to the currently opened channel.
|
||||
/// </summary>
|
||||
/// <param name="text">The message text that is going to be posted</param>
|
||||
/// <param name="isAction">Is true if the message is an action, e.g.: user is currently eating </param>
|
||||
public void PostMessage(string text, bool isAction = false)
|
||||
{
|
||||
if (CurrentChannel.Value == null)
|
||||
return;
|
||||
|
||||
if (!api.IsLoggedIn)
|
||||
{
|
||||
CurrentChannel.Value.AddNewMessages(new ErrorMessage("Please sign in to participate in chat!"));
|
||||
return;
|
||||
}
|
||||
|
||||
var message = new LocalEchoMessage
|
||||
{
|
||||
Sender = api.LocalUser.Value,
|
||||
Timestamp = DateTimeOffset.Now,
|
||||
TargetType = CurrentChannel.Value.Target,
|
||||
TargetId = CurrentChannel.Value.Id,
|
||||
IsAction = isAction,
|
||||
Content = text
|
||||
};
|
||||
|
||||
CurrentChannel.Value.AddLocalEcho(message);
|
||||
|
||||
var req = new PostMessageRequest(message);
|
||||
req.Failure += e => CurrentChannel.Value?.ReplaceMessage(message, null);
|
||||
req.Success += m => CurrentChannel.Value?.ReplaceMessage(message, m);
|
||||
api.Queue(req);
|
||||
}
|
||||
|
||||
public void PostCommand(string text)
|
||||
{
|
||||
if (CurrentChannel.Value == null)
|
||||
return;
|
||||
|
||||
var parameters = text.Split(new[] { ' ' }, 2);
|
||||
string command = parameters[0];
|
||||
string content = parameters.Length == 2 ? parameters[1] : string.Empty;
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "me":
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
CurrentChannel.Value.AddNewMessages(new ErrorMessage("Usage: /me [action]"));
|
||||
break;
|
||||
}
|
||||
|
||||
PostMessage(content, true);
|
||||
break;
|
||||
|
||||
case "help":
|
||||
CurrentChannel.Value.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]"));
|
||||
break;
|
||||
|
||||
default:
|
||||
CurrentChannel.Value.AddNewMessages(new ErrorMessage($@"""/{command}"" is not supported! For a list of supported commands see /help"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchNewMessages()
|
||||
{
|
||||
if (fetchMsgReq == null)
|
||||
fetchMessages(
|
||||
() => new GetMessagesRequest(JoinedChannels.Where(c => c.Target == TargetType.Channel), lastChannelMsgId),
|
||||
messages =>
|
||||
{
|
||||
if (messages == null)
|
||||
return;
|
||||
handleChannelMessages(messages);
|
||||
lastChannelMsgId = messages.LastOrDefault()?.Id ?? lastChannelMsgId;
|
||||
fetchMsgReq = null;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
if (fetchPrivateMsgReq == null)
|
||||
fetchMessages(
|
||||
() => new GetPrivateMessagesRequest(lastChannelMsgId),
|
||||
messages =>
|
||||
{
|
||||
if (messages == null)
|
||||
return;
|
||||
handleUserMessages(messages);
|
||||
lastUserMsgId = messages.LastOrDefault()?.Id ?? lastUserMsgId;
|
||||
fetchPrivateMsgReq = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void fetchMessages(Func<APIMessagesRequest> messagesRequest, Action<List<Message>> handler)
|
||||
{
|
||||
if (messagesRequest == null)
|
||||
throw new ArgumentNullException(nameof(messagesRequest));
|
||||
if (handler == null)
|
||||
throw new ArgumentNullException(nameof(handler));
|
||||
|
||||
var messagesReq = messagesRequest.Invoke();
|
||||
|
||||
messagesReq.Success += handler.Invoke;
|
||||
messagesReq.Failure += exception => Logger.Error(exception, "Fetching messages failed.");
|
||||
|
||||
api.Queue(messagesReq);
|
||||
}
|
||||
|
||||
private void handleUserMessages(IEnumerable<Message> messages)
|
||||
{
|
||||
var joinedUserChannels = JoinedChannels.Where(c => c.Target == TargetType.User).ToList();
|
||||
|
||||
var outgoingMessages = messages.Where(m => m.Sender.Id == api.LocalUser.Value.Id);
|
||||
var outgoingMessagesGroups = outgoingMessages.GroupBy(m => m.TargetId);
|
||||
var incomingMessagesGroups = messages.Except(outgoingMessages).GroupBy(m => m.UserId);
|
||||
|
||||
foreach (var messageGroup in incomingMessagesGroups)
|
||||
{
|
||||
var targetUser = messageGroup.First().Sender;
|
||||
var channel = joinedUserChannels.FirstOrDefault(c => c.Id == targetUser.Id);
|
||||
|
||||
if (channel == null)
|
||||
{
|
||||
channel = new Channel(targetUser);
|
||||
JoinedChannels.Add(channel);
|
||||
joinedUserChannels.Add(channel);
|
||||
}
|
||||
|
||||
channel.AddNewMessages(messageGroup.ToArray());
|
||||
var outgoingTargetMessages = outgoingMessagesGroups.FirstOrDefault(g => g.Key == targetUser.Id);
|
||||
if (outgoingTargetMessages != null)
|
||||
channel.AddNewMessages(outgoingTargetMessages.ToArray());
|
||||
}
|
||||
|
||||
var withoutReplyGroups = outgoingMessagesGroups.Where(g => joinedUserChannels.All(m => m.Id != g.Key));
|
||||
|
||||
foreach (var withoutReplyGroup in withoutReplyGroups)
|
||||
{
|
||||
var userReq = new GetUserRequest(withoutReplyGroup.First().TargetId);
|
||||
|
||||
userReq.Failure += exception => Logger.Error(exception, "Failed to get user informations.");
|
||||
userReq.Success += user =>
|
||||
{
|
||||
var channel = new Channel(user);
|
||||
|
||||
channel.AddNewMessages(withoutReplyGroup.ToArray());
|
||||
JoinedChannels.Add(channel);
|
||||
};
|
||||
|
||||
api.Queue(userReq);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleChannelMessages(IEnumerable<Message> messages)
|
||||
{
|
||||
var channels = JoinedChannels.ToList();
|
||||
|
||||
foreach (var group in messages.GroupBy(m => m.TargetId))
|
||||
channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
|
||||
}
|
||||
|
||||
private void initializeDefaultChannels()
|
||||
{
|
||||
var req = new ListChannelsRequest();
|
||||
|
||||
req.Success += channels =>
|
||||
{
|
||||
channels.Where(channel => AvailableChannels.All(c => c.Id != channel.Id))
|
||||
.ForEach(channel => AvailableChannels.Add(channel));
|
||||
|
||||
channels.Where(channel => defaultChannels.Contains(channel.Name))
|
||||
.Where(channel => JoinedChannels.All(c => c.Id != channel.Id))
|
||||
.ForEach(channel =>
|
||||
{
|
||||
JoinedChannels.Add(channel);
|
||||
|
||||
var fetchInitialMsgReq = new GetMessagesRequest(new[] { channel }, null);
|
||||
fetchInitialMsgReq.Success += handleChannelMessages;
|
||||
fetchInitialMsgReq.Failure += exception => Logger.Error(exception, "Failed to fetch inital messages.");
|
||||
api.Queue(fetchInitialMsgReq);
|
||||
});
|
||||
|
||||
fetchNewMessages();
|
||||
};
|
||||
req.Failure += error => Logger.Error(error, "Fetching channel list failed");
|
||||
|
||||
api.Queue(req);
|
||||
}
|
||||
|
||||
public void APIStateChanged(APIAccess api, APIState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case APIState.Online:
|
||||
if (JoinedChannels.Count == 0)
|
||||
initializeDefaultChannels();
|
||||
fetchMessagesScheduleder = scheduler.AddDelayed(fetchNewMessages, 1000, true);
|
||||
break;
|
||||
default:
|
||||
fetchMsgReq?.Cancel();
|
||||
fetchMsgReq = null;
|
||||
fetchMessagesScheduleder?.Cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAPIProvider api)
|
||||
{
|
||||
this.api = this.api;
|
||||
api.Register(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Configuration;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
/// <summary>
|
||||
/// Manages everything channel related
|
||||
/// </summary>
|
||||
public class ChannelManager : Component, IOnlineComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// The channels the player joins on startup
|
||||
/// </summary>
|
||||
private readonly string[] defaultChannels =
|
||||
{
|
||||
@"#lazer",
|
||||
@"#osu",
|
||||
@"#lobby"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The currently opened channel
|
||||
/// </summary>
|
||||
public Bindable<Channel> CurrentChannel { get; } = new Bindable<Channel>();
|
||||
|
||||
/// <summary>
|
||||
/// The Channels the player has joined
|
||||
/// </summary>
|
||||
public ObservableCollection<Channel> JoinedChannels { get; } = new ObservableCollection<Channel>();
|
||||
|
||||
/// <summary>
|
||||
/// The channels available for the player to join
|
||||
/// </summary>
|
||||
public ObservableCollection<Channel> AvailableChannels { get; } = new ObservableCollection<Channel>();
|
||||
|
||||
private APIAccess api;
|
||||
private readonly Scheduler scheduler;
|
||||
private ScheduledDelegate fetchMessagesScheduleder;
|
||||
private GetMessagesRequest fetchMsgReq;
|
||||
private GetPrivateMessagesRequest fetchPrivateMsgReq;
|
||||
private long? lastChannelMsgId;
|
||||
private long? lastUserMsgId;
|
||||
|
||||
public void OpenChannel(string name)
|
||||
{
|
||||
if (name == null)
|
||||
throw new ArgumentNullException(nameof(name));
|
||||
|
||||
CurrentChannel.Value = AvailableChannels.FirstOrDefault(c => c.Name == name)
|
||||
?? throw new ArgumentException($"Channel {name} was not found.");
|
||||
}
|
||||
|
||||
public void OpenUserChannel(User user)
|
||||
{
|
||||
if (user == null)
|
||||
throw new ArgumentNullException(nameof(user));
|
||||
|
||||
CurrentChannel.Value = JoinedChannels.FirstOrDefault(c => c.Target == TargetType.User && c.Id == user.Id)
|
||||
?? new Channel(user);
|
||||
}
|
||||
|
||||
public ChannelManager()
|
||||
{
|
||||
CurrentChannel.ValueChanged += currentChannelChanged;
|
||||
}
|
||||
|
||||
private void currentChannelChanged(Channel channel)
|
||||
{
|
||||
if (!JoinedChannels.Contains(channel))
|
||||
JoinedChannels.Add(channel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Posts a message to the currently opened channel.
|
||||
/// </summary>
|
||||
/// <param name="text">The message text that is going to be posted</param>
|
||||
/// <param name="isAction">Is true if the message is an action, e.g.: user is currently eating </param>
|
||||
public void PostMessage(string text, bool isAction = false)
|
||||
{
|
||||
if (CurrentChannel.Value == null)
|
||||
return;
|
||||
|
||||
if (!api.IsLoggedIn)
|
||||
{
|
||||
CurrentChannel.Value.AddNewMessages(new ErrorMessage("Please sign in to participate in chat!"));
|
||||
return;
|
||||
}
|
||||
|
||||
var message = new LocalEchoMessage
|
||||
{
|
||||
Sender = api.LocalUser.Value,
|
||||
Timestamp = DateTimeOffset.Now,
|
||||
TargetType = CurrentChannel.Value.Target,
|
||||
TargetId = CurrentChannel.Value.Id,
|
||||
IsAction = isAction,
|
||||
Content = text
|
||||
};
|
||||
|
||||
CurrentChannel.Value.AddLocalEcho(message);
|
||||
|
||||
var req = new PostMessageRequest(message);
|
||||
req.Failure += e => CurrentChannel.Value?.ReplaceMessage(message, null);
|
||||
req.Success += m => CurrentChannel.Value?.ReplaceMessage(message, m);
|
||||
api.Queue(req);
|
||||
}
|
||||
|
||||
public void PostCommand(string text)
|
||||
{
|
||||
if (CurrentChannel.Value == null)
|
||||
return;
|
||||
|
||||
var parameters = text.Split(new[] { ' ' }, 2);
|
||||
string command = parameters[0];
|
||||
string content = parameters.Length == 2 ? parameters[1] : string.Empty;
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "me":
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
CurrentChannel.Value.AddNewMessages(new ErrorMessage("Usage: /me [action]"));
|
||||
break;
|
||||
}
|
||||
|
||||
PostMessage(content, true);
|
||||
break;
|
||||
|
||||
case "help":
|
||||
CurrentChannel.Value.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]"));
|
||||
break;
|
||||
|
||||
default:
|
||||
CurrentChannel.Value.AddNewMessages(new ErrorMessage($@"""/{command}"" is not supported! For a list of supported commands see /help"));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchNewMessages()
|
||||
{
|
||||
if (fetchMsgReq == null)
|
||||
fetchMessages(
|
||||
() => new GetMessagesRequest(JoinedChannels.Where(c => c.Target == TargetType.Channel), lastChannelMsgId),
|
||||
messages =>
|
||||
{
|
||||
if (messages == null)
|
||||
return;
|
||||
handleChannelMessages(messages);
|
||||
lastChannelMsgId = messages.LastOrDefault()?.Id ?? lastChannelMsgId;
|
||||
fetchMsgReq = null;
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
if (fetchPrivateMsgReq == null)
|
||||
fetchMessages(
|
||||
() => new GetPrivateMessagesRequest(lastChannelMsgId),
|
||||
messages =>
|
||||
{
|
||||
if (messages == null)
|
||||
return;
|
||||
handleUserMessages(messages);
|
||||
lastUserMsgId = messages.LastOrDefault()?.Id ?? lastUserMsgId;
|
||||
fetchPrivateMsgReq = null;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
private void fetchMessages(Func<APIMessagesRequest> messagesRequest, Action<List<Message>> handler)
|
||||
{
|
||||
if (messagesRequest == null)
|
||||
throw new ArgumentNullException(nameof(messagesRequest));
|
||||
if (handler == null)
|
||||
throw new ArgumentNullException(nameof(handler));
|
||||
|
||||
var messagesReq = messagesRequest.Invoke();
|
||||
|
||||
messagesReq.Success += handler.Invoke;
|
||||
messagesReq.Failure += exception => Logger.Error(exception, "Fetching messages failed.");
|
||||
|
||||
api.Queue(messagesReq);
|
||||
}
|
||||
|
||||
private void handleUserMessages(IEnumerable<Message> messages)
|
||||
{
|
||||
var joinedUserChannels = JoinedChannels.Where(c => c.Target == TargetType.User).ToList();
|
||||
|
||||
var outgoingMessages = messages.Where(m => m.Sender.Id == api.LocalUser.Value.Id);
|
||||
var outgoingMessagesGroups = outgoingMessages.GroupBy(m => m.TargetId);
|
||||
var incomingMessagesGroups = messages.Except(outgoingMessages).GroupBy(m => m.UserId);
|
||||
|
||||
foreach (var messageGroup in incomingMessagesGroups)
|
||||
{
|
||||
var targetUser = messageGroup.First().Sender;
|
||||
var channel = joinedUserChannels.FirstOrDefault(c => c.Id == targetUser.Id);
|
||||
|
||||
if (channel == null)
|
||||
{
|
||||
channel = new Channel(targetUser);
|
||||
JoinedChannels.Add(channel);
|
||||
joinedUserChannels.Add(channel);
|
||||
}
|
||||
|
||||
channel.AddNewMessages(messageGroup.ToArray());
|
||||
var outgoingTargetMessages = outgoingMessagesGroups.FirstOrDefault(g => g.Key == targetUser.Id);
|
||||
if (outgoingTargetMessages != null)
|
||||
channel.AddNewMessages(outgoingTargetMessages.ToArray());
|
||||
}
|
||||
|
||||
var withoutReplyGroups = outgoingMessagesGroups.Where(g => joinedUserChannels.All(m => m.Id != g.Key));
|
||||
|
||||
foreach (var withoutReplyGroup in withoutReplyGroups)
|
||||
{
|
||||
var userReq = new GetUserRequest(withoutReplyGroup.First().TargetId);
|
||||
|
||||
userReq.Failure += exception => Logger.Error(exception, "Failed to get user informations.");
|
||||
userReq.Success += user =>
|
||||
{
|
||||
var channel = new Channel(user);
|
||||
|
||||
channel.AddNewMessages(withoutReplyGroup.ToArray());
|
||||
JoinedChannels.Add(channel);
|
||||
};
|
||||
|
||||
api.Queue(userReq);
|
||||
}
|
||||
}
|
||||
|
||||
private void handleChannelMessages(IEnumerable<Message> messages)
|
||||
{
|
||||
var channels = JoinedChannels.ToList();
|
||||
|
||||
foreach (var group in messages.GroupBy(m => m.TargetId))
|
||||
channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
|
||||
}
|
||||
|
||||
private void initializeDefaultChannels()
|
||||
{
|
||||
var req = new ListChannelsRequest();
|
||||
|
||||
req.Success += channels =>
|
||||
{
|
||||
channels.Where(channel => AvailableChannels.All(c => c.Id != channel.Id))
|
||||
.ForEach(channel => AvailableChannels.Add(channel));
|
||||
|
||||
channels.Where(channel => defaultChannels.Contains(channel.Name))
|
||||
.Where(channel => JoinedChannels.All(c => c.Id != channel.Id))
|
||||
.ForEach(channel =>
|
||||
{
|
||||
JoinedChannels.Add(channel);
|
||||
|
||||
var fetchInitialMsgReq = new GetMessagesRequest(new[] { channel }, null);
|
||||
fetchInitialMsgReq.Success += handleChannelMessages;
|
||||
fetchInitialMsgReq.Failure += exception => Logger.Error(exception, "Failed to fetch inital messages.");
|
||||
api.Queue(fetchInitialMsgReq);
|
||||
});
|
||||
|
||||
fetchNewMessages();
|
||||
};
|
||||
req.Failure += error => Logger.Error(error, "Fetching channel list failed");
|
||||
|
||||
api.Queue(req);
|
||||
}
|
||||
|
||||
public void APIStateChanged(APIAccess api, APIState state)
|
||||
{
|
||||
switch (state)
|
||||
{
|
||||
case APIState.Online:
|
||||
if (JoinedChannels.Count == 0)
|
||||
initializeDefaultChannels();
|
||||
fetchMessagesScheduleder = scheduler.AddDelayed(fetchNewMessages, 1000, true);
|
||||
break;
|
||||
default:
|
||||
fetchMsgReq?.Cancel();
|
||||
fetchMsgReq = null;
|
||||
fetchMessagesScheduleder?.Cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAPIProvider api)
|
||||
{
|
||||
this.api = this.api;
|
||||
api.Register(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,331 +1,331 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Chat;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Configuration;
|
||||
using System;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public class ChannelTabControl : OsuTabControl<Channel>
|
||||
{
|
||||
private const float shear_width = 10;
|
||||
|
||||
public Action<Channel> OnRequestLeave;
|
||||
|
||||
public readonly Bindable<bool> ChannelSelectorActive = new Bindable<bool>();
|
||||
|
||||
private readonly ChannelTabItem.ChannelSelectorTabItem selectorTab;
|
||||
|
||||
public ChannelTabControl()
|
||||
{
|
||||
TabContainer.Margin = new MarginPadding { Left = 50 };
|
||||
TabContainer.Spacing = new Vector2(-shear_width, 0);
|
||||
TabContainer.Masking = false;
|
||||
|
||||
AddInternal(new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.fa_comments,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(20),
|
||||
Margin = new MarginPadding(10),
|
||||
});
|
||||
|
||||
AddTabItem(selectorTab = new ChannelTabItem.ChannelSelectorTabItem(new Channel { Name = "+" }));
|
||||
|
||||
ChannelSelectorActive.BindTo(selectorTab.Active);
|
||||
}
|
||||
|
||||
protected override void AddTabItem(TabItem<Channel> item, bool addToDropdown = true)
|
||||
{
|
||||
if (item != selectorTab && TabContainer.GetLayoutPosition(selectorTab) < float.MaxValue)
|
||||
// performTabSort might've made selectorTab's position wonky, fix it
|
||||
TabContainer.SetLayoutPosition(selectorTab, float.MaxValue);
|
||||
|
||||
base.AddTabItem(item, addToDropdown);
|
||||
}
|
||||
|
||||
protected override TabItem<Channel> CreateTabItem(Channel value) => new ChannelTabItem(value) { OnRequestClose = tabCloseRequested };
|
||||
|
||||
protected override void SelectTab(TabItem<Channel> tab)
|
||||
{
|
||||
if (tab is ChannelTabItem.ChannelSelectorTabItem)
|
||||
{
|
||||
tab.Active.Toggle();
|
||||
return;
|
||||
}
|
||||
|
||||
selectorTab.Active.Value = false;
|
||||
|
||||
base.SelectTab(tab);
|
||||
}
|
||||
|
||||
private void tabCloseRequested(TabItem<Channel> tab)
|
||||
{
|
||||
int totalTabs = TabContainer.Count - 1; // account for selectorTab
|
||||
int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(tab), 1, totalTabs);
|
||||
|
||||
if (tab == SelectedTab && totalTabs > 1)
|
||||
// Select the tab after tab-to-be-removed's index, or the tab before if current == last
|
||||
SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]);
|
||||
else if (totalTabs == 1 && !selectorTab.Active)
|
||||
// Open channel selection overlay if all channel tabs will be closed after removing this tab
|
||||
SelectTab(selectorTab);
|
||||
|
||||
OnRequestLeave?.Invoke(tab.Value);
|
||||
}
|
||||
|
||||
private class ChannelTabItem : TabItem<Channel>
|
||||
{
|
||||
private Color4 backgroundInactive;
|
||||
private Color4 backgroundHover;
|
||||
private Color4 backgroundActive;
|
||||
|
||||
public override bool IsRemovable => !Pinned;
|
||||
|
||||
private readonly SpriteText text;
|
||||
private readonly SpriteText textBold;
|
||||
private readonly ClickableContainer closeButton;
|
||||
private readonly Box box;
|
||||
private readonly Box highlightBox;
|
||||
private readonly SpriteIcon icon;
|
||||
|
||||
public Action<ChannelTabItem> OnRequestClose;
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (Active)
|
||||
fadeActive();
|
||||
else
|
||||
fadeInactive();
|
||||
}
|
||||
|
||||
private const float transition_length = 400;
|
||||
|
||||
private void fadeActive()
|
||||
{
|
||||
this.ResizeTo(new Vector2(Width, 1.1f), transition_length, Easing.OutQuint);
|
||||
|
||||
box.FadeColour(backgroundActive, transition_length, Easing.OutQuint);
|
||||
highlightBox.FadeIn(transition_length, Easing.OutQuint);
|
||||
|
||||
text.FadeOut(transition_length, Easing.OutQuint);
|
||||
textBold.FadeIn(transition_length, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private void fadeInactive()
|
||||
{
|
||||
this.ResizeTo(new Vector2(Width, 1), transition_length, Easing.OutQuint);
|
||||
|
||||
box.FadeColour(backgroundInactive, transition_length, Easing.OutQuint);
|
||||
highlightBox.FadeOut(transition_length, Easing.OutQuint);
|
||||
|
||||
text.FadeIn(transition_length, Easing.OutQuint);
|
||||
textBold.FadeOut(transition_length, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
if (IsRemovable)
|
||||
closeButton.FadeIn(200, Easing.OutQuint);
|
||||
|
||||
if (!Active)
|
||||
box.FadeColour(backgroundHover, transition_length, Easing.OutQuint);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
closeButton.FadeOut(200, Easing.OutQuint);
|
||||
updateState();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
backgroundActive = colours.ChatBlue;
|
||||
backgroundInactive = colours.Gray4;
|
||||
backgroundHover = colours.Gray7;
|
||||
|
||||
highlightBox.Colour = colours.Yellow;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
updateState();
|
||||
}
|
||||
|
||||
public ChannelTabItem(Channel value) : base(value)
|
||||
{
|
||||
Width = 150;
|
||||
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
||||
Anchor = Anchor.BottomLeft;
|
||||
Origin = Anchor.BottomLeft;
|
||||
|
||||
Shear = new Vector2(shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0);
|
||||
|
||||
Masking = true;
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 10,
|
||||
Colour = Color4.Black.Opacity(0.2f),
|
||||
};
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
box = new Box
|
||||
{
|
||||
EdgeSmoothness = new Vector2(1, 0),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
highlightBox = new Box
|
||||
{
|
||||
Width = 5,
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
EdgeSmoothness = new Vector2(1, 0),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Shear = new Vector2(-shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
icon = new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.fa_hashtag,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Colour = Color4.Black,
|
||||
X = -10,
|
||||
Alpha = 0.2f,
|
||||
Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT),
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Margin = new MarginPadding(5),
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Text = value.ToString(),
|
||||
TextSize = 18,
|
||||
},
|
||||
textBold = new OsuSpriteText
|
||||
{
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding(5),
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Text = value.ToString(),
|
||||
Font = @"Exo2.0-Bold",
|
||||
TextSize = 18,
|
||||
},
|
||||
closeButton = new CloseButton
|
||||
{
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Right = 20 },
|
||||
Origin = Anchor.CentreRight,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Action = delegate
|
||||
{
|
||||
if (IsRemovable) OnRequestClose?.Invoke(this);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public class CloseButton : OsuClickableContainer
|
||||
{
|
||||
private readonly SpriteIcon icon;
|
||||
|
||||
public CloseButton()
|
||||
{
|
||||
Size = new Vector2(20);
|
||||
|
||||
Child = icon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(0.75f),
|
||||
Icon = FontAwesome.fa_close,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||
{
|
||||
icon.ScaleTo(0.5f, 1000, Easing.OutQuint);
|
||||
return base.OnMouseDown(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||
{
|
||||
icon.ScaleTo(0.75f, 1000, Easing.OutElastic);
|
||||
return base.OnMouseUp(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
icon.FadeColour(Color4.Red, 200, Easing.OutQuint);
|
||||
return base.OnHover(state);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
icon.FadeColour(Color4.White, 200, Easing.OutQuint);
|
||||
base.OnHoverLost(state);
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelSelectorTabItem : ChannelTabItem
|
||||
{
|
||||
public override bool IsRemovable => false;
|
||||
|
||||
public ChannelSelectorTabItem(Channel value) : base(value)
|
||||
{
|
||||
Depth = float.MaxValue;
|
||||
Width = 45;
|
||||
|
||||
icon.Alpha = 0;
|
||||
|
||||
text.TextSize = 45;
|
||||
textBold.TextSize = 45;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private new void load(OsuColour colour)
|
||||
{
|
||||
backgroundInactive = colour.Gray2;
|
||||
backgroundActive = colour.Gray3;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnActivated() => updateState();
|
||||
|
||||
protected override void OnDeactivated() => updateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Chat;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
using osu.Framework.Configuration;
|
||||
using System;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public class ChannelTabControl : OsuTabControl<Channel>
|
||||
{
|
||||
private const float shear_width = 10;
|
||||
|
||||
public Action<Channel> OnRequestLeave;
|
||||
|
||||
public readonly Bindable<bool> ChannelSelectorActive = new Bindable<bool>();
|
||||
|
||||
private readonly ChannelTabItem.ChannelSelectorTabItem selectorTab;
|
||||
|
||||
public ChannelTabControl()
|
||||
{
|
||||
TabContainer.Margin = new MarginPadding { Left = 50 };
|
||||
TabContainer.Spacing = new Vector2(-shear_width, 0);
|
||||
TabContainer.Masking = false;
|
||||
|
||||
AddInternal(new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.fa_comments,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Size = new Vector2(20),
|
||||
Margin = new MarginPadding(10),
|
||||
});
|
||||
|
||||
AddTabItem(selectorTab = new ChannelTabItem.ChannelSelectorTabItem(new Channel { Name = "+" }));
|
||||
|
||||
ChannelSelectorActive.BindTo(selectorTab.Active);
|
||||
}
|
||||
|
||||
protected override void AddTabItem(TabItem<Channel> item, bool addToDropdown = true)
|
||||
{
|
||||
if (item != selectorTab && TabContainer.GetLayoutPosition(selectorTab) < float.MaxValue)
|
||||
// performTabSort might've made selectorTab's position wonky, fix it
|
||||
TabContainer.SetLayoutPosition(selectorTab, float.MaxValue);
|
||||
|
||||
base.AddTabItem(item, addToDropdown);
|
||||
}
|
||||
|
||||
protected override TabItem<Channel> CreateTabItem(Channel value) => new ChannelTabItem(value) { OnRequestClose = tabCloseRequested };
|
||||
|
||||
protected override void SelectTab(TabItem<Channel> tab)
|
||||
{
|
||||
if (tab is ChannelTabItem.ChannelSelectorTabItem)
|
||||
{
|
||||
tab.Active.Toggle();
|
||||
return;
|
||||
}
|
||||
|
||||
selectorTab.Active.Value = false;
|
||||
|
||||
base.SelectTab(tab);
|
||||
}
|
||||
|
||||
private void tabCloseRequested(TabItem<Channel> tab)
|
||||
{
|
||||
int totalTabs = TabContainer.Count - 1; // account for selectorTab
|
||||
int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(tab), 1, totalTabs);
|
||||
|
||||
if (tab == SelectedTab && totalTabs > 1)
|
||||
// Select the tab after tab-to-be-removed's index, or the tab before if current == last
|
||||
SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]);
|
||||
else if (totalTabs == 1 && !selectorTab.Active)
|
||||
// Open channel selection overlay if all channel tabs will be closed after removing this tab
|
||||
SelectTab(selectorTab);
|
||||
|
||||
OnRequestLeave?.Invoke(tab.Value);
|
||||
}
|
||||
|
||||
private class ChannelTabItem : TabItem<Channel>
|
||||
{
|
||||
private Color4 backgroundInactive;
|
||||
private Color4 backgroundHover;
|
||||
private Color4 backgroundActive;
|
||||
|
||||
public override bool IsRemovable => !Pinned;
|
||||
|
||||
private readonly SpriteText text;
|
||||
private readonly SpriteText textBold;
|
||||
private readonly ClickableContainer closeButton;
|
||||
private readonly Box box;
|
||||
private readonly Box highlightBox;
|
||||
private readonly SpriteIcon icon;
|
||||
|
||||
public Action<ChannelTabItem> OnRequestClose;
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
if (Active)
|
||||
fadeActive();
|
||||
else
|
||||
fadeInactive();
|
||||
}
|
||||
|
||||
private const float transition_length = 400;
|
||||
|
||||
private void fadeActive()
|
||||
{
|
||||
this.ResizeTo(new Vector2(Width, 1.1f), transition_length, Easing.OutQuint);
|
||||
|
||||
box.FadeColour(backgroundActive, transition_length, Easing.OutQuint);
|
||||
highlightBox.FadeIn(transition_length, Easing.OutQuint);
|
||||
|
||||
text.FadeOut(transition_length, Easing.OutQuint);
|
||||
textBold.FadeIn(transition_length, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private void fadeInactive()
|
||||
{
|
||||
this.ResizeTo(new Vector2(Width, 1), transition_length, Easing.OutQuint);
|
||||
|
||||
box.FadeColour(backgroundInactive, transition_length, Easing.OutQuint);
|
||||
highlightBox.FadeOut(transition_length, Easing.OutQuint);
|
||||
|
||||
text.FadeIn(transition_length, Easing.OutQuint);
|
||||
textBold.FadeOut(transition_length, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
if (IsRemovable)
|
||||
closeButton.FadeIn(200, Easing.OutQuint);
|
||||
|
||||
if (!Active)
|
||||
box.FadeColour(backgroundHover, transition_length, Easing.OutQuint);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
closeButton.FadeOut(200, Easing.OutQuint);
|
||||
updateState();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
backgroundActive = colours.ChatBlue;
|
||||
backgroundInactive = colours.Gray4;
|
||||
backgroundHover = colours.Gray7;
|
||||
|
||||
highlightBox.Colour = colours.Yellow;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
updateState();
|
||||
}
|
||||
|
||||
public ChannelTabItem(Channel value) : base(value)
|
||||
{
|
||||
Width = 150;
|
||||
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
||||
Anchor = Anchor.BottomLeft;
|
||||
Origin = Anchor.BottomLeft;
|
||||
|
||||
Shear = new Vector2(shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0);
|
||||
|
||||
Masking = true;
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 10,
|
||||
Colour = Color4.Black.Opacity(0.2f),
|
||||
};
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
box = new Box
|
||||
{
|
||||
EdgeSmoothness = new Vector2(1, 0),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
highlightBox = new Box
|
||||
{
|
||||
Width = 5,
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
EdgeSmoothness = new Vector2(1, 0),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Shear = new Vector2(-shear_width / ChatOverlay.TAB_AREA_HEIGHT, 0),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
icon = new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.fa_hashtag,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Colour = Color4.Black,
|
||||
X = -10,
|
||||
Alpha = 0.2f,
|
||||
Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT),
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
{
|
||||
Margin = new MarginPadding(5),
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Text = value.ToString(),
|
||||
TextSize = 18,
|
||||
},
|
||||
textBold = new OsuSpriteText
|
||||
{
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding(5),
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Text = value.ToString(),
|
||||
Font = @"Exo2.0-Bold",
|
||||
TextSize = 18,
|
||||
},
|
||||
closeButton = new CloseButton
|
||||
{
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Right = 20 },
|
||||
Origin = Anchor.CentreRight,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Action = delegate
|
||||
{
|
||||
if (IsRemovable) OnRequestClose?.Invoke(this);
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
public class CloseButton : OsuClickableContainer
|
||||
{
|
||||
private readonly SpriteIcon icon;
|
||||
|
||||
public CloseButton()
|
||||
{
|
||||
Size = new Vector2(20);
|
||||
|
||||
Child = icon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(0.75f),
|
||||
Icon = FontAwesome.fa_close,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||
{
|
||||
icon.ScaleTo(0.5f, 1000, Easing.OutQuint);
|
||||
return base.OnMouseDown(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||
{
|
||||
icon.ScaleTo(0.75f, 1000, Easing.OutElastic);
|
||||
return base.OnMouseUp(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
icon.FadeColour(Color4.Red, 200, Easing.OutQuint);
|
||||
return base.OnHover(state);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
icon.FadeColour(Color4.White, 200, Easing.OutQuint);
|
||||
base.OnHoverLost(state);
|
||||
}
|
||||
}
|
||||
|
||||
public class ChannelSelectorTabItem : ChannelTabItem
|
||||
{
|
||||
public override bool IsRemovable => false;
|
||||
|
||||
public ChannelSelectorTabItem(Channel value) : base(value)
|
||||
{
|
||||
Depth = float.MaxValue;
|
||||
Width = 45;
|
||||
|
||||
icon.Alpha = 0;
|
||||
|
||||
text.TextSize = 45;
|
||||
textBold.TextSize = 45;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private new void load(OsuColour colour)
|
||||
{
|
||||
backgroundInactive = colour.Gray2;
|
||||
backgroundActive = colour.Gray3;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnActivated() => updateState();
|
||||
|
||||
protected override void OnDeactivated() => updateState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,55 +1,55 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public class ChatTabItemCloseButton : OsuClickableContainer
|
||||
{
|
||||
private readonly SpriteIcon icon;
|
||||
|
||||
public ChatTabItemCloseButton()
|
||||
{
|
||||
Size = new Vector2(20);
|
||||
|
||||
Child = icon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(0.75f),
|
||||
Icon = FontAwesome.fa_close,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||
{
|
||||
icon.ScaleTo(0.5f, 1000, Easing.OutQuint);
|
||||
return base.OnMouseDown(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||
{
|
||||
icon.ScaleTo(0.75f, 1000, Easing.OutElastic);
|
||||
return base.OnMouseUp(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
icon.FadeColour(Color4.Red, 200, Easing.OutQuint);
|
||||
return base.OnHover(state);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
icon.FadeColour(Color4.White, 200, Easing.OutQuint);
|
||||
base.OnHoverLost(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public class ChatTabItemCloseButton : OsuClickableContainer
|
||||
{
|
||||
private readonly SpriteIcon icon;
|
||||
|
||||
public ChatTabItemCloseButton()
|
||||
{
|
||||
Size = new Vector2(20);
|
||||
|
||||
Child = icon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(0.75f),
|
||||
Icon = FontAwesome.fa_close,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
|
||||
{
|
||||
icon.ScaleTo(0.5f, 1000, Easing.OutQuint);
|
||||
return base.OnMouseDown(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
|
||||
{
|
||||
icon.ScaleTo(0.75f, 1000, Easing.OutElastic);
|
||||
return base.OnMouseUp(state, args);
|
||||
}
|
||||
|
||||
protected override bool OnHover(InputState state)
|
||||
{
|
||||
icon.FadeColour(Color4.Red, 200, Easing.OutQuint);
|
||||
return base.OnHover(state);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(InputState state)
|
||||
{
|
||||
icon.FadeColour(Color4.White, 200, Easing.OutQuint);
|
||||
base.OnHoverLost(state);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,46 +1,46 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Chat;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public class UserTabControl : OsuTabControl<Channel>
|
||||
{
|
||||
protected override TabItem<Channel> CreateTabItem(Channel value)
|
||||
{
|
||||
if (value.Target != TargetType.User)
|
||||
throw new ArgumentException("Argument value needs to have the targettype user.");
|
||||
return new UserTabItem(value) { OnRequestClose = tabCloseRequested };
|
||||
}
|
||||
|
||||
public Action<Channel> OnRequestLeave;
|
||||
|
||||
public UserTabControl()
|
||||
{
|
||||
TabContainer.Spacing = new Vector2(-10, 0);
|
||||
TabContainer.Masking = false;
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Right = 10
|
||||
};
|
||||
}
|
||||
|
||||
private void tabCloseRequested(TabItem<Channel> priv)
|
||||
{
|
||||
int totalTabs = TabContainer.Count -1; // account for selectorTab
|
||||
int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(priv), 1, totalTabs);
|
||||
|
||||
if (priv == SelectedTab && totalTabs > 1)
|
||||
// Select the tab after tab-to-be-removed's index, or the tab before if current == last
|
||||
SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]);
|
||||
|
||||
OnRequestLeave?.Invoke(priv.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.Chat;
|
||||
using OpenTK;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public class UserTabControl : OsuTabControl<Channel>
|
||||
{
|
||||
protected override TabItem<Channel> CreateTabItem(Channel value)
|
||||
{
|
||||
if (value.Target != TargetType.User)
|
||||
throw new ArgumentException("Argument value needs to have the targettype user.");
|
||||
return new UserTabItem(value) { OnRequestClose = tabCloseRequested };
|
||||
}
|
||||
|
||||
public Action<Channel> OnRequestLeave;
|
||||
|
||||
public UserTabControl()
|
||||
{
|
||||
TabContainer.Spacing = new Vector2(-10, 0);
|
||||
TabContainer.Masking = false;
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Right = 10
|
||||
};
|
||||
}
|
||||
|
||||
private void tabCloseRequested(TabItem<Channel> priv)
|
||||
{
|
||||
int totalTabs = TabContainer.Count -1; // account for selectorTab
|
||||
int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(priv), 1, totalTabs);
|
||||
|
||||
if (priv == SelectedTab && totalTabs > 1)
|
||||
// Select the tab after tab-to-be-removed's index, or the tab before if current == last
|
||||
SelectTab(TabContainer[currentIndex == totalTabs ? currentIndex - 1 : currentIndex + 1]);
|
||||
|
||||
OnRequestLeave?.Invoke(priv.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,215 +1,215 @@
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Users;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public class UserTabItem : TabItem<Channel>
|
||||
{
|
||||
private static readonly Vector2 shear = new Vector2(1f / 5f, 0);
|
||||
public override bool IsRemovable => true;
|
||||
|
||||
private readonly Box highlightBox;
|
||||
private readonly Container backgroundContainer;
|
||||
private readonly Box backgroundBox;
|
||||
private readonly OsuSpriteText username;
|
||||
private readonly Avatar avatarContainer;
|
||||
private readonly ChatTabItemCloseButton closeButton;
|
||||
|
||||
public UserTabItem(Channel value)
|
||||
: base(value)
|
||||
{
|
||||
if (value.Target != TargetType.User)
|
||||
throw new ArgumentException("Argument value needs to have the targettype user!");
|
||||
|
||||
AutoSizeAxes = Axes.X;
|
||||
Height = 50;
|
||||
Origin = Anchor.BottomRight;
|
||||
Anchor = Anchor.BottomRight;
|
||||
EdgeEffect = activateEdgeEffect;
|
||||
Masking = true;
|
||||
Shear = shear;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
backgroundBox = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
EdgeSmoothness = new Vector2(1, 0),
|
||||
},
|
||||
}
|
||||
},
|
||||
highlightBox = new Box
|
||||
{
|
||||
Width = 5,
|
||||
BypassAutoSizeAxes = Axes.X,
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
EdgeSmoothness = new Vector2(1, 0),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Colour = new OsuColour().Yellow
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Masking = true,
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Child = new FlowContainerWithOrigin
|
||||
{
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
X = -5,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Origin = Anchor.TopLeft,
|
||||
Anchor = Anchor.TopLeft,
|
||||
Shear = -shear,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Horizontal = 5
|
||||
},
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.fa_eercast,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(1.2f),
|
||||
X = -5,
|
||||
Y = 5,
|
||||
Anchor = Anchor.Centre,
|
||||
Colour = new OsuColour().BlueDarker,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Scale = new Vector2(0.95f),
|
||||
AutoSizeAxes = Axes.X,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Masking = true,
|
||||
Child = new DelayedLoadWrapper(new Avatar(value.JoinedUsers.First())
|
||||
{
|
||||
Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT),
|
||||
OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint),
|
||||
})
|
||||
{
|
||||
Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT),
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
username = new OsuSpriteText
|
||||
{
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Text = value.Name,
|
||||
Margin = new MarginPadding(1),
|
||||
TextSize = 18,
|
||||
Alpha = 0,
|
||||
},
|
||||
closeButton = new ChatTabItemCloseButton
|
||||
{
|
||||
Height = 1,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Right = 5
|
||||
},
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Action = delegate
|
||||
{
|
||||
if (IsRemovable) OnRequestClose?.Invoke(this);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Action<UserTabItem> OnRequestClose;
|
||||
|
||||
private readonly EdgeEffectParameters activateEdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 15,
|
||||
Colour = Color4.Black.Opacity(0.4f),
|
||||
};
|
||||
|
||||
protected override void OnActivated()
|
||||
{
|
||||
const int activate_length = 1000;
|
||||
|
||||
backgroundBox.ResizeHeightTo(1.1f, activate_length, Easing.OutQuint);
|
||||
highlightBox.ResizeHeightTo(1.1f, activate_length, Easing.OutQuint);
|
||||
highlightBox.FadeIn(activate_length, Easing.OutQuint);
|
||||
username.FadeIn(activate_length, Easing.OutQuint);
|
||||
username.ScaleTo(new Vector2(1, 1), activate_length, Easing.OutQuint);
|
||||
closeButton.ScaleTo(new Vector2(1, 1), activate_length, Easing.OutQuint);
|
||||
closeButton.FadeIn(activate_length, Easing.OutQuint);
|
||||
TweenEdgeEffectTo(activateEdgeEffect, activate_length);
|
||||
}
|
||||
|
||||
private readonly EdgeEffectParameters deactivateEdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 10,
|
||||
Colour = Color4.Black.Opacity(0.2f),
|
||||
};
|
||||
|
||||
protected override void OnDeactivated()
|
||||
{
|
||||
const int deactivate_length = 500;
|
||||
|
||||
backgroundBox.ResizeHeightTo(1, deactivate_length, Easing.OutQuint);
|
||||
highlightBox.ResizeHeightTo(1, deactivate_length, Easing.OutQuint);
|
||||
highlightBox.FadeOut(deactivate_length, Easing.OutQuint);
|
||||
username.FadeOut(deactivate_length, Easing.OutQuint);
|
||||
username.ScaleTo(new Vector2(0, 1), deactivate_length, Easing.OutQuint);
|
||||
closeButton.FadeOut(deactivate_length, Easing.OutQuint);
|
||||
closeButton.ScaleTo(new Vector2(0, 1), deactivate_length, Easing.OutQuint);
|
||||
TweenEdgeEffectTo(deactivateEdgeEffect, deactivate_length);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
var user = Value.JoinedUsers.First();
|
||||
|
||||
backgroundBox.Colour = user.Colour != null ? OsuColour.FromHex(user.Colour) : colours.BlueDark;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
|
||||
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Screens.Menu;
|
||||
using osu.Game.Users;
|
||||
using OpenTK;
|
||||
using OpenTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public class UserTabItem : TabItem<Channel>
|
||||
{
|
||||
private static readonly Vector2 shear = new Vector2(1f / 5f, 0);
|
||||
public override bool IsRemovable => true;
|
||||
|
||||
private readonly Box highlightBox;
|
||||
private readonly Container backgroundContainer;
|
||||
private readonly Box backgroundBox;
|
||||
private readonly OsuSpriteText username;
|
||||
private readonly Avatar avatarContainer;
|
||||
private readonly ChatTabItemCloseButton closeButton;
|
||||
|
||||
public UserTabItem(Channel value)
|
||||
: base(value)
|
||||
{
|
||||
if (value.Target != TargetType.User)
|
||||
throw new ArgumentException("Argument value needs to have the targettype user!");
|
||||
|
||||
AutoSizeAxes = Axes.X;
|
||||
Height = 50;
|
||||
Origin = Anchor.BottomRight;
|
||||
Anchor = Anchor.BottomRight;
|
||||
EdgeEffect = activateEdgeEffect;
|
||||
Masking = true;
|
||||
Shear = shear;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
backgroundBox = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
EdgeSmoothness = new Vector2(1, 0),
|
||||
},
|
||||
}
|
||||
},
|
||||
highlightBox = new Box
|
||||
{
|
||||
Width = 5,
|
||||
BypassAutoSizeAxes = Axes.X,
|
||||
Alpha = 0,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
EdgeSmoothness = new Vector2(1, 0),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Colour = new OsuColour().Yellow
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Masking = true,
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Child = new FlowContainerWithOrigin
|
||||
{
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
X = -5,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Origin = Anchor.TopLeft,
|
||||
Anchor = Anchor.TopLeft,
|
||||
Shear = -shear,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Horizontal = 5
|
||||
},
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.fa_eercast,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(1.2f),
|
||||
X = -5,
|
||||
Y = 5,
|
||||
Anchor = Anchor.Centre,
|
||||
Colour = new OsuColour().BlueDarker,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Scale = new Vector2(0.95f),
|
||||
AutoSizeAxes = Axes.X,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Masking = true,
|
||||
Child = new DelayedLoadWrapper(new Avatar(value.JoinedUsers.First())
|
||||
{
|
||||
Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT),
|
||||
OnLoadComplete = d => d.FadeInFromZero(300, Easing.OutQuint),
|
||||
})
|
||||
{
|
||||
Size = new Vector2(ChatOverlay.TAB_AREA_HEIGHT),
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
username = new OsuSpriteText
|
||||
{
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Text = value.Name,
|
||||
Margin = new MarginPadding(1),
|
||||
TextSize = 18,
|
||||
Alpha = 0,
|
||||
},
|
||||
closeButton = new ChatTabItemCloseButton
|
||||
{
|
||||
Height = 1,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Right = 5
|
||||
},
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Action = delegate
|
||||
{
|
||||
if (IsRemovable) OnRequestClose?.Invoke(this);
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public Action<UserTabItem> OnRequestClose;
|
||||
|
||||
private readonly EdgeEffectParameters activateEdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 15,
|
||||
Colour = Color4.Black.Opacity(0.4f),
|
||||
};
|
||||
|
||||
protected override void OnActivated()
|
||||
{
|
||||
const int activate_length = 1000;
|
||||
|
||||
backgroundBox.ResizeHeightTo(1.1f, activate_length, Easing.OutQuint);
|
||||
highlightBox.ResizeHeightTo(1.1f, activate_length, Easing.OutQuint);
|
||||
highlightBox.FadeIn(activate_length, Easing.OutQuint);
|
||||
username.FadeIn(activate_length, Easing.OutQuint);
|
||||
username.ScaleTo(new Vector2(1, 1), activate_length, Easing.OutQuint);
|
||||
closeButton.ScaleTo(new Vector2(1, 1), activate_length, Easing.OutQuint);
|
||||
closeButton.FadeIn(activate_length, Easing.OutQuint);
|
||||
TweenEdgeEffectTo(activateEdgeEffect, activate_length);
|
||||
}
|
||||
|
||||
private readonly EdgeEffectParameters deactivateEdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 10,
|
||||
Colour = Color4.Black.Opacity(0.2f),
|
||||
};
|
||||
|
||||
protected override void OnDeactivated()
|
||||
{
|
||||
const int deactivate_length = 500;
|
||||
|
||||
backgroundBox.ResizeHeightTo(1, deactivate_length, Easing.OutQuint);
|
||||
highlightBox.ResizeHeightTo(1, deactivate_length, Easing.OutQuint);
|
||||
highlightBox.FadeOut(deactivate_length, Easing.OutQuint);
|
||||
username.FadeOut(deactivate_length, Easing.OutQuint);
|
||||
username.ScaleTo(new Vector2(0, 1), deactivate_length, Easing.OutQuint);
|
||||
closeButton.FadeOut(deactivate_length, Easing.OutQuint);
|
||||
closeButton.ScaleTo(new Vector2(0, 1), deactivate_length, Easing.OutQuint);
|
||||
TweenEdgeEffectTo(deactivateEdgeEffect, deactivate_length);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
var user = Value.JoinedUsers.First();
|
||||
|
||||
backgroundBox.Colour = user.Colour != null ? OsuColour.FromHex(user.Colour) : colours.BlueDark;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user