mirror of
https://github.com/ppy/osu.git
synced 2025-01-18 10:53:21 +08:00
Merge branch 'master' into api-startup-state
This commit is contained in:
commit
3523a2fe61
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.1224.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.114.1" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -13,7 +13,6 @@ using Android.Graphics;
|
||||
using Android.OS;
|
||||
using Android.Views;
|
||||
using osu.Framework.Android;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Game.Database;
|
||||
using Debug = System.Diagnostics.Debug;
|
||||
using Uri = Android.Net.Uri;
|
||||
@ -50,9 +49,23 @@ namespace osu.Android
|
||||
/// <remarks>Adjusted on startup to match expected UX for the current device type (phone/tablet).</remarks>
|
||||
public ScreenOrientation DefaultOrientation = ScreenOrientation.Unspecified;
|
||||
|
||||
private OsuGameAndroid game = null!;
|
||||
private readonly OsuGameAndroid game;
|
||||
|
||||
protected override Framework.Game CreateGame() => game = new OsuGameAndroid(this);
|
||||
private bool gameCreated;
|
||||
|
||||
protected override Framework.Game CreateGame()
|
||||
{
|
||||
if (gameCreated)
|
||||
throw new InvalidOperationException("Framework tried to create a game twice.");
|
||||
|
||||
gameCreated = true;
|
||||
return game;
|
||||
}
|
||||
|
||||
public OsuGameActivity()
|
||||
{
|
||||
game = new OsuGameAndroid(this);
|
||||
}
|
||||
|
||||
protected override void OnCreate(Bundle? savedInstanceState)
|
||||
{
|
||||
@ -95,25 +108,38 @@ namespace osu.Android
|
||||
|
||||
private void handleIntent(Intent? intent)
|
||||
{
|
||||
switch (intent?.Action)
|
||||
if (intent == null)
|
||||
return;
|
||||
|
||||
switch (intent.Action)
|
||||
{
|
||||
case Intent.ActionDefault:
|
||||
if (intent.Scheme == ContentResolver.SchemeContent)
|
||||
handleImportFromUris(intent.Data.AsNonNull());
|
||||
{
|
||||
if (intent.Data != null)
|
||||
handleImportFromUris(intent.Data);
|
||||
}
|
||||
else if (osu_url_schemes.Contains(intent.Scheme))
|
||||
game.HandleLink(intent.DataString);
|
||||
{
|
||||
if (intent.DataString != null)
|
||||
game.HandleLink(intent.DataString);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case Intent.ActionSend:
|
||||
case Intent.ActionSendMultiple:
|
||||
{
|
||||
if (intent.ClipData == null)
|
||||
break;
|
||||
|
||||
var uris = new List<Uri>();
|
||||
|
||||
for (int i = 0; i < intent.ClipData?.ItemCount; i++)
|
||||
for (int i = 0; i < intent.ClipData.ItemCount; i++)
|
||||
{
|
||||
var content = intent.ClipData?.GetItemAt(i);
|
||||
if (content != null)
|
||||
uris.Add(content.Uri.AsNonNull());
|
||||
var item = intent.ClipData.GetItemAt(i);
|
||||
if (item?.Uri != null)
|
||||
uris.Add(item.Uri);
|
||||
}
|
||||
|
||||
handleImportFromUris(uris.ToArray());
|
||||
|
@ -0,0 +1,129 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Tests.Visual.Metadata;
|
||||
using osu.Game.Users;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Components
|
||||
{
|
||||
public partial class TestSceneFriendPresenceNotifier : OsuManualInputManagerTestScene
|
||||
{
|
||||
private ChannelManager channelManager = null!;
|
||||
private NotificationOverlay notificationOverlay = null!;
|
||||
private ChatOverlay chatOverlay = null!;
|
||||
private TestMetadataClient metadataClient = null!;
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
Child = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies =
|
||||
[
|
||||
(typeof(ChannelManager), channelManager = new ChannelManager(API)),
|
||||
(typeof(INotificationOverlay), notificationOverlay = new NotificationOverlay()),
|
||||
(typeof(ChatOverlay), chatOverlay = new ChatOverlay()),
|
||||
(typeof(MetadataClient), metadataClient = new TestMetadataClient()),
|
||||
],
|
||||
Children = new Drawable[]
|
||||
{
|
||||
channelManager,
|
||||
notificationOverlay,
|
||||
chatOverlay,
|
||||
metadataClient,
|
||||
new FriendPresenceNotifier()
|
||||
}
|
||||
};
|
||||
|
||||
for (int i = 1; i <= 100; i++)
|
||||
((DummyAPIAccess)API).Friends.Add(new APIRelation { TargetID = i, TargetUser = new APIUser { Username = $"Friend {i}" } });
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestNotifications()
|
||||
{
|
||||
AddStep("bring friend 1 online", () => metadataClient.FriendPresenceUpdated(1, new UserPresence { Status = UserStatus.Online }));
|
||||
AddUntilStep("wait for notification", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
|
||||
AddStep("bring friend 1 offline", () => metadataClient.FriendPresenceUpdated(1, null));
|
||||
AddUntilStep("wait for notification", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(2));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSingleUserNotificationOpensChat()
|
||||
{
|
||||
AddStep("bring friend 1 online", () => metadataClient.FriendPresenceUpdated(1, new UserPresence { Status = UserStatus.Online }));
|
||||
AddUntilStep("wait for notification", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
|
||||
|
||||
AddStep("click notification", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<Notification>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("chat overlay opened", () => chatOverlay.State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
AddUntilStep("user channel selected", () => channelManager.CurrentChannel.Value.Name, () => Is.EqualTo(((DummyAPIAccess)API).Friends[0].TargetUser!.Username));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleUserNotificationDoesNotOpenChat()
|
||||
{
|
||||
AddStep("bring friends 1 & 2 online", () =>
|
||||
{
|
||||
metadataClient.FriendPresenceUpdated(1, new UserPresence { Status = UserStatus.Online });
|
||||
metadataClient.FriendPresenceUpdated(2, new UserPresence { Status = UserStatus.Online });
|
||||
});
|
||||
|
||||
AddUntilStep("wait for notification", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
|
||||
|
||||
AddStep("click notification", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<Notification>().First());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("chat overlay not opened", () => chatOverlay.State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonFriendsDoNotNotify()
|
||||
{
|
||||
AddStep("bring non-friend 1000 online", () => metadataClient.UserPresenceUpdated(1000, new UserPresence { Status = UserStatus.Online }));
|
||||
AddWaitStep("wait for possible notification", 10);
|
||||
AddAssert("no notification", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestPostManyDebounced()
|
||||
{
|
||||
AddStep("bring friends 1-10 online", () =>
|
||||
{
|
||||
for (int i = 1; i <= 10; i++)
|
||||
metadataClient.FriendPresenceUpdated(i, new UserPresence { Status = UserStatus.Online });
|
||||
});
|
||||
|
||||
AddUntilStep("wait for notification", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
|
||||
|
||||
AddStep("bring friends 1-10 offline", () =>
|
||||
{
|
||||
for (int i = 1; i <= 10; i++)
|
||||
metadataClient.FriendPresenceUpdated(i, null);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for notification", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(2));
|
||||
}
|
||||
}
|
||||
}
|
@ -138,7 +138,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddUntilStep("section top is visible", () =>
|
||||
{
|
||||
var scrollContainer = container.ChildrenOfType<UserTrackingScrollContainer>().Single();
|
||||
float sectionPosition = scrollContainer.GetChildPosInContent(container.Children[scrollIndex]);
|
||||
double sectionPosition = scrollContainer.GetChildPosInContent(container.Children[scrollIndex]);
|
||||
return scrollContainer.Current < sectionPosition;
|
||||
});
|
||||
}
|
||||
|
@ -96,6 +96,7 @@ namespace osu.Game.Configuration
|
||||
|
||||
SetDefault(OsuSetting.NotifyOnUsernameMentioned, true);
|
||||
SetDefault(OsuSetting.NotifyOnPrivateMessage, true);
|
||||
SetDefault(OsuSetting.NotifyOnFriendPresenceChange, true);
|
||||
|
||||
// Audio
|
||||
SetDefault(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01);
|
||||
@ -418,6 +419,7 @@ namespace osu.Game.Configuration
|
||||
IntroSequence,
|
||||
NotifyOnUsernameMentioned,
|
||||
NotifyOnPrivateMessage,
|
||||
NotifyOnFriendPresenceChange,
|
||||
UIHoldActivationDelay,
|
||||
HitLighting,
|
||||
StarFountains,
|
||||
|
@ -59,11 +59,11 @@ namespace osu.Game.Graphics.Containers
|
||||
/// <param name="extraScroll">An added amount to scroll beyond the requirement to bring the target into view.</param>
|
||||
public void ScrollIntoView(Drawable d, bool animated = true, float extraScroll = 0)
|
||||
{
|
||||
float childPos0 = GetChildPosInContent(d);
|
||||
float childPos1 = GetChildPosInContent(d, d.DrawSize);
|
||||
double childPos0 = GetChildPosInContent(d);
|
||||
double childPos1 = GetChildPosInContent(d, d.DrawSize);
|
||||
|
||||
float minPos = Math.Min(childPos0, childPos1);
|
||||
float maxPos = Math.Max(childPos0, childPos1);
|
||||
double minPos = Math.Min(childPos0, childPos1);
|
||||
double maxPos = Math.Max(childPos0, childPos1);
|
||||
|
||||
if (minPos < Current || (minPos > Current && d.DrawSize[ScrollDim] > DisplayableContent))
|
||||
ScrollTo(minPos - extraScroll, animated);
|
||||
|
@ -208,7 +208,7 @@ namespace osu.Game.Graphics.Containers
|
||||
private float getScrollTargetForDrawable(Drawable target)
|
||||
{
|
||||
// implementation similar to ScrollIntoView but a bit more nuanced.
|
||||
return scrollContainer.GetChildPosInContent(target) - scrollContainer.DisplayableContent * scroll_y_centre;
|
||||
return (float)(scrollContainer.GetChildPosInContent(target) - scrollContainer.DisplayableContent * scroll_y_centre);
|
||||
}
|
||||
|
||||
public void ScrollToTop() => scrollContainer.ScrollTo(0);
|
||||
@ -259,7 +259,7 @@ namespace osu.Game.Graphics.Containers
|
||||
updateSectionsMargin();
|
||||
}
|
||||
|
||||
float currentScroll = scrollContainer.Current;
|
||||
float currentScroll = (float)scrollContainer.Current;
|
||||
|
||||
if (currentScroll != lastKnownScroll)
|
||||
{
|
||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
}
|
||||
|
||||
protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default)
|
||||
protected override void OnUserScroll(double value, bool animated = true, double? distanceDecay = default)
|
||||
{
|
||||
UserScrolling = true;
|
||||
base.OnUserScroll(value, animated, distanceDecay);
|
||||
@ -53,7 +53,7 @@ namespace osu.Game.Graphics.Containers
|
||||
base.ScrollFromMouseEvent(e);
|
||||
}
|
||||
|
||||
public new void ScrollTo(float value, bool animated = true, double? distanceDecay = null)
|
||||
public new void ScrollTo(double value, bool animated = true, double? distanceDecay = null)
|
||||
{
|
||||
UserScrolling = false;
|
||||
base.ScrollTo(value, animated, distanceDecay);
|
||||
|
@ -1,17 +1,17 @@
|
||||
// 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.Input;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class OsuNumberBox : OsuTextBox
|
||||
{
|
||||
protected override bool AllowIme => false;
|
||||
|
||||
public OsuNumberBox()
|
||||
{
|
||||
InputProperties = new TextInputProperties(TextInputType.Number, false);
|
||||
|
||||
SelectAllOnFocus = true;
|
||||
}
|
||||
|
||||
protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character);
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public partial class OsuPasswordTextBox : OsuTextBox, ISuppressKeyEventLogging
|
||||
public partial class OsuPasswordTextBox : OsuTextBox
|
||||
{
|
||||
protected override Drawable GetDrawableCharacter(char c) => new FallingDownContainer
|
||||
{
|
||||
@ -28,12 +28,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override bool AllowUniqueCharacterSamples => false;
|
||||
|
||||
protected override bool AllowClipboardExport => false;
|
||||
|
||||
protected override bool AllowWordNavigation => false;
|
||||
|
||||
protected override bool AllowIme => false;
|
||||
|
||||
private readonly CapsWarning warning;
|
||||
|
||||
[Resolved]
|
||||
@ -41,6 +35,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
public OsuPasswordTextBox()
|
||||
{
|
||||
InputProperties = new TextInputProperties(TextInputType.Password, false);
|
||||
|
||||
Add(warning = new CapsWarning
|
||||
{
|
||||
Size = new Vector2(20),
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Globalization;
|
||||
using osu.Framework.Input;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
@ -19,6 +20,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
public bool AllowDecimals { get; init; }
|
||||
|
||||
public InnerNumberBox()
|
||||
{
|
||||
InputProperties = new TextInputProperties(TextInputType.Number, false);
|
||||
}
|
||||
|
||||
protected override bool CanAddCharacter(char character)
|
||||
=> char.IsAsciiDigit(character) || (AllowDecimals && CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator.Contains(character));
|
||||
}
|
||||
|
@ -29,6 +29,16 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString NotifyOnPrivateMessage => new TranslatableString(getKey(@"notify_on_private_message"), @"Show a notification when you receive a private message");
|
||||
|
||||
/// <summary>
|
||||
/// "Show notification popups when friends change status"
|
||||
/// </summary>
|
||||
public static LocalisableString NotifyOnFriendPresenceChange => new TranslatableString(getKey(@"notify_on_friend_presence_change"), @"Show notification popups when friends change status");
|
||||
|
||||
/// <summary>
|
||||
/// "Notifications will be shown when friends go online/offline."
|
||||
/// </summary>
|
||||
public static LocalisableString NotifyOnFriendPresenceChangeTooltip => new TranslatableString(getKey(@"notify_on_friend_presence_change_tooltip"), @"Notifications will be shown when friends go online/offline.");
|
||||
|
||||
/// <summary>
|
||||
/// "Integrations"
|
||||
/// </summary>
|
||||
@ -84,6 +94,6 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString HideCountryFlags => new TranslatableString(getKey(@"hide_country_flags"), @"Hide country flags");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Sockets;
|
||||
@ -629,8 +630,14 @@ namespace osu.Game.Online.API
|
||||
friendsReq.Failure += _ => state.Value = APIState.Failing;
|
||||
friendsReq.Success += res =>
|
||||
{
|
||||
friends.Clear();
|
||||
friends.AddRange(res);
|
||||
var existingFriends = friends.Select(f => f.TargetID).ToHashSet();
|
||||
var updatedFriends = res.Select(f => f.TargetID).ToHashSet();
|
||||
|
||||
// Add new friends into local list.
|
||||
friends.AddRange(res.Where(r => !existingFriends.Contains(r.TargetID)));
|
||||
|
||||
// Remove non-friends from local list.
|
||||
friends.RemoveAll(f => !updatedFriends.Contains(f.TargetID));
|
||||
};
|
||||
|
||||
Queue(friendsReq);
|
||||
|
216
osu.Game/Online/FriendPresenceNotifier.cs
Normal file
216
osu.Game/Online/FriendPresenceNotifier.cs
Normal file
@ -0,0 +1,216 @@
|
||||
// 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.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.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Online.Metadata;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online
|
||||
{
|
||||
public partial class FriendPresenceNotifier : Component
|
||||
{
|
||||
[Resolved]
|
||||
private INotificationOverlay notifications { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private MetadataClient metadataClient { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private ChannelManager channelManager { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private ChatOverlay chatOverlay { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuConfigManager config { get; set; } = null!;
|
||||
|
||||
private readonly Bindable<bool> notifyOnFriendPresenceChange = new BindableBool();
|
||||
|
||||
private readonly IBindableList<APIRelation> friends = new BindableList<APIRelation>();
|
||||
private readonly IBindableDictionary<int, UserPresence> friendStates = new BindableDictionary<int, UserPresence>();
|
||||
|
||||
private readonly HashSet<APIUser> onlineAlertQueue = new HashSet<APIUser>();
|
||||
private readonly HashSet<APIUser> offlineAlertQueue = new HashSet<APIUser>();
|
||||
|
||||
private double? lastOnlineAlertTime;
|
||||
private double? lastOfflineAlertTime;
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
config.BindWith(OsuSetting.NotifyOnFriendPresenceChange, notifyOnFriendPresenceChange);
|
||||
|
||||
friends.BindTo(api.Friends);
|
||||
friends.BindCollectionChanged(onFriendsChanged, true);
|
||||
|
||||
friendStates.BindTo(metadataClient.FriendStates);
|
||||
friendStates.BindCollectionChanged(onFriendStatesChanged, true);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
alertOnlineUsers();
|
||||
alertOfflineUsers();
|
||||
}
|
||||
|
||||
private void onFriendsChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyCollectionChangedAction.Add:
|
||||
foreach (APIRelation friend in e.NewItems!.Cast<APIRelation>())
|
||||
{
|
||||
if (friend.TargetUser is not APIUser user)
|
||||
continue;
|
||||
|
||||
if (friendStates.TryGetValue(friend.TargetID, out _))
|
||||
markUserOnline(user);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NotifyCollectionChangedAction.Remove:
|
||||
foreach (APIRelation friend in e.OldItems!.Cast<APIRelation>())
|
||||
{
|
||||
if (friend.TargetUser is not APIUser user)
|
||||
continue;
|
||||
|
||||
onlineAlertQueue.Remove(user);
|
||||
offlineAlertQueue.Remove(user);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void onFriendStatesChanged(object? sender, NotifyDictionaryChangedEventArgs<int, UserPresence> e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case NotifyDictionaryChangedAction.Add:
|
||||
foreach ((int friendId, _) in e.NewItems!)
|
||||
{
|
||||
APIRelation? friend = friends.FirstOrDefault(f => f.TargetID == friendId);
|
||||
|
||||
if (friend?.TargetUser is APIUser user)
|
||||
markUserOnline(user);
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
case NotifyDictionaryChangedAction.Remove:
|
||||
foreach ((int friendId, _) in e.OldItems!)
|
||||
{
|
||||
APIRelation? friend = friends.FirstOrDefault(f => f.TargetID == friendId);
|
||||
|
||||
if (friend?.TargetUser is APIUser user)
|
||||
markUserOffline(user);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void markUserOnline(APIUser user)
|
||||
{
|
||||
if (!offlineAlertQueue.Remove(user))
|
||||
{
|
||||
onlineAlertQueue.Add(user);
|
||||
lastOnlineAlertTime ??= Time.Current;
|
||||
}
|
||||
}
|
||||
|
||||
private void markUserOffline(APIUser user)
|
||||
{
|
||||
if (!onlineAlertQueue.Remove(user))
|
||||
{
|
||||
offlineAlertQueue.Add(user);
|
||||
lastOfflineAlertTime ??= Time.Current;
|
||||
}
|
||||
}
|
||||
|
||||
private void alertOnlineUsers()
|
||||
{
|
||||
if (onlineAlertQueue.Count == 0)
|
||||
return;
|
||||
|
||||
if (lastOnlineAlertTime == null || Time.Current - lastOnlineAlertTime < 1000)
|
||||
return;
|
||||
|
||||
if (!notifyOnFriendPresenceChange.Value)
|
||||
{
|
||||
lastOnlineAlertTime = null;
|
||||
return;
|
||||
}
|
||||
|
||||
APIUser? singleUser = onlineAlertQueue.Count == 1 ? onlineAlertQueue.Single() : null;
|
||||
|
||||
notifications.Post(new SimpleNotification
|
||||
{
|
||||
Icon = FontAwesome.Solid.UserPlus,
|
||||
Text = $"Online: {string.Join(@", ", onlineAlertQueue.Select(u => u.Username))}",
|
||||
IconColour = colours.Green,
|
||||
Activated = () =>
|
||||
{
|
||||
if (singleUser != null)
|
||||
{
|
||||
channelManager.OpenPrivateChannel(singleUser);
|
||||
chatOverlay.Show();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
onlineAlertQueue.Clear();
|
||||
lastOnlineAlertTime = null;
|
||||
}
|
||||
|
||||
private void alertOfflineUsers()
|
||||
{
|
||||
if (offlineAlertQueue.Count == 0)
|
||||
return;
|
||||
|
||||
if (lastOfflineAlertTime == null || Time.Current - lastOfflineAlertTime < 1000)
|
||||
return;
|
||||
|
||||
if (!notifyOnFriendPresenceChange.Value)
|
||||
{
|
||||
lastOfflineAlertTime = null;
|
||||
return;
|
||||
}
|
||||
|
||||
notifications.Post(new SimpleNotification
|
||||
{
|
||||
Icon = FontAwesome.Solid.UserMinus,
|
||||
Text = $"Offline: {string.Join(@", ", offlineAlertQueue.Select(u => u.Username))}",
|
||||
IconColour = colours.Red
|
||||
});
|
||||
|
||||
offlineAlertQueue.Clear();
|
||||
lastOfflineAlertTime = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -375,8 +375,8 @@ namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
float fadeBottom = scrollContainer.Current + scrollContainer.DrawHeight;
|
||||
float fadeTop = scrollContainer.Current + LeaderboardScore.HEIGHT;
|
||||
float fadeBottom = (float)(scrollContainer.Current + scrollContainer.DrawHeight);
|
||||
float fadeTop = (float)(scrollContainer.Current + LeaderboardScore.HEIGHT);
|
||||
|
||||
if (!scrollContainer.IsScrolledToEnd())
|
||||
fadeBottom -= LeaderboardScore.HEIGHT;
|
||||
|
@ -101,6 +101,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
private void load(IAPIProvider api, OsuColour colour)
|
||||
{
|
||||
var user = Score.User;
|
||||
bool isUserFriend = api.Friends.Any(friend => friend.TargetID == user.OnlineID);
|
||||
|
||||
statisticsLabels = GetStatistics(Score).Select(s => new ScoreComponentLabel(s)).ToList();
|
||||
|
||||
@ -129,7 +130,7 @@ namespace osu.Game.Online.Leaderboards
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = user.OnlineID == api.LocalUser.Value.Id && isOnlineScope ? colour.Green : Color4.Black,
|
||||
Colour = isUserFriend ? colour.Yellow : (user.OnlineID == api.LocalUser.Value.Id && isOnlineScope ? colour.Green : Color4.Black),
|
||||
Alpha = background_alpha,
|
||||
},
|
||||
},
|
||||
|
@ -21,6 +21,11 @@ namespace osu.Game.Online.Metadata
|
||||
/// </summary>
|
||||
Task UserPresenceUpdated(int userId, UserPresence? status);
|
||||
|
||||
/// <summary>
|
||||
/// Delivers and update of the <see cref="UserPresence"/> of a friend with the supplied <paramref name="userId"/>.
|
||||
/// </summary>
|
||||
Task FriendPresenceUpdated(int userId, UserPresence? presence);
|
||||
|
||||
/// <summary>
|
||||
/// Delivers an update of the current "daily challenge" status.
|
||||
/// Null value means there is no "daily challenge" currently active.
|
||||
|
@ -42,6 +42,11 @@ namespace osu.Game.Online.Metadata
|
||||
/// </summary>
|
||||
public abstract IBindableDictionary<int, UserPresence> UserStates { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Dictionary keyed by user ID containing all of the <see cref="UserPresence"/> information about currently online friends received from the server.
|
||||
/// </summary>
|
||||
public abstract IBindableDictionary<int, UserPresence> FriendStates { get; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract Task UpdateActivity(UserActivity? activity);
|
||||
|
||||
@ -57,6 +62,9 @@ namespace osu.Game.Online.Metadata
|
||||
/// <inheritdoc/>
|
||||
public abstract Task UserPresenceUpdated(int userId, UserPresence? presence);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public abstract Task FriendPresenceUpdated(int userId, UserPresence? presence);
|
||||
|
||||
#endregion
|
||||
|
||||
#region Daily Challenge
|
||||
|
@ -26,15 +26,16 @@ namespace osu.Game.Online.Metadata
|
||||
public override IBindableDictionary<int, UserPresence> UserStates => userStates;
|
||||
private readonly BindableDictionary<int, UserPresence> userStates = new BindableDictionary<int, UserPresence>();
|
||||
|
||||
public override IBindableDictionary<int, UserPresence> FriendStates => friendStates;
|
||||
private readonly BindableDictionary<int, UserPresence> friendStates = new BindableDictionary<int, UserPresence>();
|
||||
|
||||
public override IBindable<DailyChallengeInfo?> DailyChallengeInfo => dailyChallengeInfo;
|
||||
private readonly Bindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();
|
||||
|
||||
private readonly string endpoint;
|
||||
|
||||
private IHubClientConnector? connector;
|
||||
|
||||
private Bindable<int> lastQueueId = null!;
|
||||
|
||||
private IBindable<APIUser> localUser = null!;
|
||||
private IBindable<UserActivity?> userActivity = null!;
|
||||
private IBindable<UserStatus?>? userStatus;
|
||||
@ -61,6 +62,7 @@ namespace osu.Game.Online.Metadata
|
||||
// https://github.com/dotnet/aspnetcore/issues/15198
|
||||
connection.On<BeatmapUpdates>(nameof(IMetadataClient.BeatmapSetsUpdated), ((IMetadataClient)this).BeatmapSetsUpdated);
|
||||
connection.On<int, UserPresence?>(nameof(IMetadataClient.UserPresenceUpdated), ((IMetadataClient)this).UserPresenceUpdated);
|
||||
connection.On<int, UserPresence?>(nameof(IMetadataClient.FriendPresenceUpdated), ((IMetadataClient)this).FriendPresenceUpdated);
|
||||
connection.On<DailyChallengeInfo?>(nameof(IMetadataClient.DailyChallengeUpdated), ((IMetadataClient)this).DailyChallengeUpdated);
|
||||
connection.On<MultiplayerRoomScoreSetEvent>(nameof(IMetadataClient.MultiplayerRoomScoreSet), ((IMetadataClient)this).MultiplayerRoomScoreSet);
|
||||
connection.On(nameof(IStatefulUserHubClient.DisconnectRequested), ((IMetadataClient)this).DisconnectRequested);
|
||||
@ -106,6 +108,7 @@ namespace osu.Game.Online.Metadata
|
||||
{
|
||||
isWatchingUserPresence.Value = false;
|
||||
userStates.Clear();
|
||||
friendStates.Clear();
|
||||
dailyChallengeInfo.Value = null;
|
||||
});
|
||||
return;
|
||||
@ -207,6 +210,19 @@ namespace osu.Game.Online.Metadata
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task FriendPresenceUpdated(int userId, UserPresence? presence)
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (presence?.Status != null)
|
||||
friendStates[userId] = presence.Value;
|
||||
else
|
||||
friendStates.Remove(userId);
|
||||
});
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override async Task BeginWatchingUserPresence()
|
||||
{
|
||||
if (connector?.IsConnected.Value != true)
|
||||
|
@ -1125,6 +1125,7 @@ namespace osu.Game
|
||||
Add(externalLinkOpener = new ExternalLinkOpener());
|
||||
Add(new MusicKeyBindingHandler());
|
||||
Add(new OnlineStatusNotifier(() => ScreenStack.CurrentScreen));
|
||||
Add(new FriendPresenceNotifier());
|
||||
|
||||
// side overlays which cancel each other.
|
||||
var singleDisplaySideOverlays = new OverlayContainer[] { Settings, Notifications, FirstRunOverlay };
|
||||
|
@ -41,13 +41,13 @@ namespace osu.Game.Overlays.Chat
|
||||
|
||||
#region Scroll handling
|
||||
|
||||
protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = null)
|
||||
protected override void OnUserScroll(double value, bool animated = true, double? distanceDecay = null)
|
||||
{
|
||||
base.OnUserScroll(value, animated, distanceDecay);
|
||||
updateTrackState();
|
||||
}
|
||||
|
||||
public new void ScrollTo(float value, bool animated = true, double? distanceDecay = null)
|
||||
public new void ScrollTo(double value, bool animated = true, double? distanceDecay = null)
|
||||
{
|
||||
base.ScrollTo(value, animated, distanceDecay);
|
||||
updateTrackState();
|
||||
|
@ -117,7 +117,7 @@ namespace osu.Game.Overlays.Chat
|
||||
if (chatLine == null)
|
||||
return;
|
||||
|
||||
float center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2;
|
||||
double center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2;
|
||||
scroll.ScrollTo(Math.Clamp(center, 0, scroll.ScrollableExtent));
|
||||
chatLine.Highlight();
|
||||
|
||||
|
@ -7,6 +7,7 @@ using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
@ -63,6 +64,7 @@ namespace osu.Game.Overlays.Login
|
||||
},
|
||||
username = new OsuTextBox
|
||||
{
|
||||
InputProperties = new TextInputProperties(TextInputType.Username, false),
|
||||
PlaceholderText = UsersStrings.LoginUsername.ToLower(),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Text = api.ProvidedUsername,
|
||||
|
@ -710,13 +710,13 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
// the bounds below represent the horizontal range of scroll items to be considered fully visible/active, in the scroll's internal coordinate space.
|
||||
// note that clamping is applied to the left scroll bound to ensure scrolling past extents does not change the set of active columns.
|
||||
float leftVisibleBound = Math.Clamp(Current, 0, ScrollableExtent);
|
||||
float rightVisibleBound = leftVisibleBound + DrawWidth;
|
||||
double leftVisibleBound = Math.Clamp(Current, 0, ScrollableExtent);
|
||||
double rightVisibleBound = leftVisibleBound + DrawWidth;
|
||||
|
||||
// if a movement is occurring at this time, the bounds below represent the full range of columns that the scroll movement will encompass.
|
||||
// this will be used to ensure that columns do not change state from active to inactive back and forth until they are fully scrolled past.
|
||||
float leftMovementBound = Math.Min(Current, Target);
|
||||
float rightMovementBound = Math.Max(Current, Target) + DrawWidth;
|
||||
double leftMovementBound = Math.Min(Current, Target);
|
||||
double rightMovementBound = Math.Max(Current, Target) + DrawWidth;
|
||||
|
||||
foreach (var column in Child)
|
||||
{
|
||||
|
@ -136,7 +136,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
sidebarContainer.Height = DrawHeight;
|
||||
sidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0));
|
||||
sidebarContainer.Y = (float)Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0));
|
||||
}
|
||||
|
||||
private void loadListing(int? year = null)
|
||||
|
@ -88,7 +88,7 @@ namespace osu.Game.Overlays
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
// don't block header by applying padding equal to the visible header height
|
||||
loadingContainer.Padding = new MarginPadding { Top = Math.Max(0, Header.Height - ScrollFlow.Current) };
|
||||
loadingContainer.Padding = new MarginPadding { Top = (float)Math.Max(0, Header.Height - ScrollFlow.Current) };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
public ScrollBackButton Button { get; private set; }
|
||||
|
||||
private readonly Bindable<float?> lastScrollTarget = new Bindable<float?>();
|
||||
private readonly Bindable<double?> lastScrollTarget = new Bindable<double?>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -63,7 +63,7 @@ namespace osu.Game.Overlays
|
||||
Button.State = Target > button_scroll_position || lastScrollTarget.Value != null ? Visibility.Visible : Visibility.Hidden;
|
||||
}
|
||||
|
||||
protected override void OnUserScroll(float value, bool animated = true, double? distanceDecay = default)
|
||||
protected override void OnUserScroll(double value, bool animated = true, double? distanceDecay = default)
|
||||
{
|
||||
base.OnUserScroll(value, animated, distanceDecay);
|
||||
|
||||
@ -112,7 +112,7 @@ namespace osu.Game.Overlays
|
||||
private readonly Box background;
|
||||
private readonly SpriteIcon spriteIcon;
|
||||
|
||||
public Bindable<float?> LastScrollTarget = new Bindable<float?>();
|
||||
public Bindable<double?> LastScrollTarget = new Bindable<double?>();
|
||||
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
||||
|
||||
|
@ -29,6 +29,12 @@ namespace osu.Game.Overlays.Settings.Sections.Online
|
||||
Current = config.GetBindable<bool>(OsuSetting.NotifyOnPrivateMessage)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = OnlineSettingsStrings.NotifyOnFriendPresenceChange,
|
||||
TooltipText = OnlineSettingsStrings.NotifyOnFriendPresenceChangeTooltip,
|
||||
Current = config.GetBindable<bool>(OsuSetting.NotifyOnFriendPresenceChange),
|
||||
},
|
||||
new SettingsCheckbox
|
||||
{
|
||||
LabelText = OnlineSettingsStrings.HideCountryFlags,
|
||||
Current = config.GetBindable<bool>(OsuSetting.HideCountryFlags)
|
||||
|
@ -5,6 +5,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
|
||||
namespace osu.Game.Overlays.Settings
|
||||
{
|
||||
@ -66,7 +67,10 @@ namespace osu.Game.Overlays.Settings
|
||||
|
||||
private partial class OutlinedNumberBox : OutlinedTextBox
|
||||
{
|
||||
protected override bool AllowIme => false;
|
||||
public OutlinedNumberBox()
|
||||
{
|
||||
InputProperties = new TextInputProperties(TextInputType.Number, false);
|
||||
}
|
||||
|
||||
protected override bool CanAddCharacter(char character) => char.IsAsciiDigit(character);
|
||||
|
||||
|
@ -100,7 +100,7 @@ namespace osu.Game.Overlays
|
||||
if (articlePage != null)
|
||||
{
|
||||
articlePage.SidebarContainer.Height = DrawHeight;
|
||||
articlePage.SidebarContainer.Y = Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0));
|
||||
articlePage.SidebarContainer.Y = (float)Math.Clamp(ScrollFlow.Current - Header.DrawHeight, 0, Math.Max(ScrollFlow.ScrollContent.DrawHeight - DrawHeight - Header.DrawHeight, 0));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -55,7 +55,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
/// <summary>
|
||||
/// The timeline's scroll position in the last frame.
|
||||
/// </summary>
|
||||
private float lastScrollPosition;
|
||||
private double lastScrollPosition;
|
||||
|
||||
/// <summary>
|
||||
/// The track time in the last frame.
|
||||
@ -322,7 +322,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
/// </summary>
|
||||
public double VisibleRange => editorClock.TrackLength / Zoom;
|
||||
|
||||
public double TimeAtPosition(float x)
|
||||
public double TimeAtPosition(double x)
|
||||
{
|
||||
return x / Content.DrawWidth * editorClock.TrackLength;
|
||||
}
|
||||
|
@ -182,7 +182,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
||||
}
|
||||
|
||||
private void transformZoomTo(float newZoom, float focusPoint, double duration = 0, Easing easing = Easing.None)
|
||||
=> this.TransformTo(this.PopulateTransform(new TransformZoom(focusPoint, zoomedContent.DrawWidth, Current), newZoom, duration, easing));
|
||||
=> this.TransformTo(this.PopulateTransform(new TransformZoom(focusPoint, zoomedContent.DrawWidth, (float)Current), newZoom, duration, easing));
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when <see cref="Zoom"/> has changed.
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
@ -136,7 +137,10 @@ namespace osu.Game.Screens.Edit.Setup
|
||||
|
||||
private partial class RomanisedTextBox : InnerTextBox
|
||||
{
|
||||
protected override bool AllowIme => false;
|
||||
public RomanisedTextBox()
|
||||
{
|
||||
InputProperties = new TextInputProperties(TextInputType.Text, false);
|
||||
}
|
||||
|
||||
protected override bool CanAddCharacter(char character)
|
||||
=> MetadataUtils.IsRomanised(character);
|
||||
|
@ -114,15 +114,15 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
if (requiresScroll && TrackedScore != null)
|
||||
{
|
||||
float scrollTarget = scroll.GetChildPosInContent(TrackedScore) + TrackedScore.DrawHeight / 2 - scroll.DrawHeight / 2;
|
||||
double scrollTarget = scroll.GetChildPosInContent(TrackedScore) + TrackedScore.DrawHeight / 2 - scroll.DrawHeight / 2;
|
||||
|
||||
scroll.ScrollTo(scrollTarget);
|
||||
}
|
||||
|
||||
const float panel_height = GameplayLeaderboardScore.PANEL_HEIGHT;
|
||||
|
||||
float fadeBottom = scroll.Current + scroll.DrawHeight;
|
||||
float fadeTop = scroll.Current + panel_height;
|
||||
float fadeBottom = (float)(scroll.Current + scroll.DrawHeight);
|
||||
float fadeTop = (float)(scroll.Current + panel_height);
|
||||
|
||||
if (scroll.IsScrolledToStart()) fadeTop -= panel_height;
|
||||
if (!scroll.IsScrolledToEnd()) fadeBottom -= panel_height;
|
||||
|
@ -334,7 +334,7 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
private partial class Scroll : OsuScrollContainer
|
||||
{
|
||||
public new float Target => base.Target;
|
||||
public new double Target => base.Target;
|
||||
|
||||
public Scroll()
|
||||
: base(Direction.Horizontal)
|
||||
@ -344,7 +344,7 @@ namespace osu.Game.Screens.Ranking
|
||||
/// <summary>
|
||||
/// The target that will be scrolled to instantaneously next frame.
|
||||
/// </summary>
|
||||
public float? InstantScrollTarget;
|
||||
public double? InstantScrollTarget;
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
|
@ -611,12 +611,12 @@ namespace osu.Game.Screens.Select
|
||||
/// <summary>
|
||||
/// The position of the lower visible bound with respect to the current scroll position.
|
||||
/// </summary>
|
||||
private float visibleBottomBound => Scroll.Current + DrawHeight + BleedBottom;
|
||||
private float visibleBottomBound => (float)(Scroll.Current + DrawHeight + BleedBottom);
|
||||
|
||||
/// <summary>
|
||||
/// The position of the upper visible bound with respect to the current scroll position.
|
||||
/// </summary>
|
||||
private float visibleUpperBound => Scroll.Current - BleedTop;
|
||||
private float visibleUpperBound => (float)(Scroll.Current - BleedTop);
|
||||
|
||||
public void FlushPendingFilterOperations()
|
||||
{
|
||||
@ -1006,7 +1006,7 @@ namespace osu.Game.Screens.Select
|
||||
// we take the difference in scroll height and apply to all visible panels.
|
||||
// this avoids edge cases like when the visible panels is reduced suddenly, causing ScrollContainer
|
||||
// to enter clamp-special-case mode where it animates completely differently to normal.
|
||||
float scrollChange = scrollTarget.Value - Scroll.Current;
|
||||
float scrollChange = (float)(scrollTarget.Value - Scroll.Current);
|
||||
Scroll.ScrollTo(scrollTarget.Value, false);
|
||||
foreach (var i in Scroll)
|
||||
i.Y += scrollChange;
|
||||
@ -1217,12 +1217,12 @@ namespace osu.Game.Screens.Select
|
||||
private const float top_padding = 10;
|
||||
private const float bottom_padding = 70;
|
||||
|
||||
protected override float ToScrollbarPosition(float scrollPosition)
|
||||
protected override float ToScrollbarPosition(double scrollPosition)
|
||||
{
|
||||
if (Precision.AlmostEquals(0, ScrollableExtent))
|
||||
return 0;
|
||||
|
||||
return top_padding + (ScrollbarMovementExtent - (top_padding + bottom_padding)) * (scrollPosition / ScrollableExtent);
|
||||
return (float)(top_padding + (ScrollbarMovementExtent - (top_padding + bottom_padding)) * (scrollPosition / ScrollableExtent));
|
||||
}
|
||||
|
||||
protected override float FromScrollbarPosition(float scrollbarPosition)
|
||||
@ -1230,7 +1230,7 @@ namespace osu.Game.Screens.Select
|
||||
if (Precision.AlmostEquals(0, ScrollbarMovementExtent))
|
||||
return 0;
|
||||
|
||||
return ScrollableExtent * ((scrollbarPosition - top_padding) / (ScrollbarMovementExtent - (top_padding + bottom_padding)));
|
||||
return (float)(ScrollableExtent * ((scrollbarPosition - top_padding) / (ScrollbarMovementExtent - (top_padding + bottom_padding))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,9 @@ namespace osu.Game.Tests.Visual.Metadata
|
||||
public override IBindableDictionary<int, UserPresence> UserStates => userStates;
|
||||
private readonly BindableDictionary<int, UserPresence> userStates = new BindableDictionary<int, UserPresence>();
|
||||
|
||||
public override IBindableDictionary<int, UserPresence> FriendStates => friendStates;
|
||||
private readonly BindableDictionary<int, UserPresence> friendStates = new BindableDictionary<int, UserPresence>();
|
||||
|
||||
public override Bindable<DailyChallengeInfo?> DailyChallengeInfo => dailyChallengeInfo;
|
||||
private readonly Bindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();
|
||||
|
||||
@ -77,6 +80,16 @@ namespace osu.Game.Tests.Visual.Metadata
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task FriendPresenceUpdated(int userId, UserPresence? presence)
|
||||
{
|
||||
if (presence.HasValue)
|
||||
friendStates[userId] = presence.Value;
|
||||
else
|
||||
friendStates.Remove(userId);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public override Task<BeatmapUpdates> GetChangesSince(int queueId)
|
||||
=> Task.FromResult(new BeatmapUpdates(Array.Empty<int>(), queueId));
|
||||
|
||||
|
@ -35,7 +35,7 @@
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="20.1.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.1224.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2025.114.1" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.1224.0" />
|
||||
<PackageReference Include="Sentry" Version="5.0.0" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
|
@ -17,6 +17,6 @@
|
||||
<MtouchInterpreter>-all</MtouchInterpreter>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.1224.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2025.114.1" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
Loading…
Reference in New Issue
Block a user