1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-12 17:43:05 +08:00

Merge branch 'master' into fix-pause-in-osu-again

This commit is contained in:
Dean Herbert 2024-08-05 12:00:21 +09:00 committed by GitHub
commit 419d5a76ce
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
31 changed files with 931 additions and 111 deletions

View File

@ -21,7 +21,7 @@
]
},
"ppy.localisationanalyser.tools": {
"version": "2024.517.0",
"version": "2024.802.0",
"commands": [
"localisation"
]

View File

@ -16,7 +16,6 @@ using osu.Framework.Graphics.Shaders.Types;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Input.Events;
using osu.Framework.Layout;
using osu.Framework.Timing;
using osuTK;
using osuTK.Graphics;
@ -63,8 +62,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
// -1 signals that the part is unusable, and should not be drawn
parts[i].InvalidationID = -1;
}
AddLayout(partSizeCache);
}
[BackgroundDependencyLoader]
@ -95,12 +92,6 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
}
private readonly LayoutValue<Vector2> partSizeCache = new LayoutValue<Vector2>(Invalidation.DrawInfo | Invalidation.RequiredParentSizeToFit | Invalidation.Presence);
private Vector2 partSize => partSizeCache.IsValid
? partSizeCache.Value
: (partSizeCache.Value = new Vector2(Texture.DisplayWidth, Texture.DisplayHeight) * DrawInfo.Matrix.ExtractScale().Xy);
/// <summary>
/// The amount of time to fade the cursor trail pieces.
/// </summary>
@ -156,6 +147,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected void AddTrail(Vector2 position)
{
position = ToLocalSpace(position);
if (InterpolateMovements)
{
if (!lastPosition.HasValue)
@ -174,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
float distance = diff.Length;
Vector2 direction = diff / distance;
float interval = partSize.X / 2.5f * IntervalMultiplier;
float interval = Texture.DisplayWidth / 2.5f * IntervalMultiplier;
float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0);
for (float d = interval; d < stopAt; d += interval)
@ -191,9 +184,9 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
}
}
private void addPart(Vector2 screenSpacePosition)
private void addPart(Vector2 localSpacePosition)
{
parts[currentIndex].Position = ToLocalSpace(screenSpacePosition);
parts[currentIndex].Position = localSpacePosition;
parts[currentIndex].Time = time + 1;
++parts[currentIndex].InvalidationID;

View File

@ -259,6 +259,44 @@ namespace osu.Game.Tests.Database
});
}
[Test]
public void TestNoChangesAfterDelete()
{
RunTestWithRealmAsync(async (realm, storage) =>
{
var importer = new BeatmapImporter(storage, realm);
using var rulesets = new RealmRulesetStore(realm, storage);
using var __ = getBeatmapArchive(out string pathOriginal);
using var _ = getBeatmapArchive(out string pathOriginalSecond);
var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
importBeforeUpdate!.PerformWrite(s => s.DeletePending = true);
var dateBefore = importBeforeUpdate.Value.DateAdded;
Assert.That(importBeforeUpdate, Is.Not.Null);
Debug.Assert(importBeforeUpdate != null);
var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value);
realm.Run(r => r.Refresh());
Assert.That(importAfterUpdate, Is.Not.Null);
Debug.Assert(importAfterUpdate != null);
checkCount<BeatmapSetInfo>(realm, 1);
checkCount<BeatmapInfo>(realm, count_beatmaps);
checkCount<BeatmapMetadata>(realm, count_beatmaps);
Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1));
Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(dateBefore));
Assert.That(importAfterUpdate.Value.DateAdded, Is.EqualTo(dateBefore));
Assert.That(importBeforeUpdate.ID, Is.EqualTo(importAfterUpdate.ID));
});
}
[Test]
public void TestNoChanges()
{
@ -272,21 +310,25 @@ namespace osu.Game.Tests.Database
var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
var dateBefore = importBeforeUpdate!.Value.DateAdded;
Assert.That(importBeforeUpdate, Is.Not.Null);
Debug.Assert(importBeforeUpdate != null);
var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value);
realm.Run(r => r.Refresh());
Assert.That(importAfterUpdate, Is.Not.Null);
Debug.Assert(importAfterUpdate != null);
realm.Run(r => r.Refresh());
checkCount<BeatmapSetInfo>(realm, 1);
checkCount<BeatmapInfo>(realm, count_beatmaps);
checkCount<BeatmapMetadata>(realm, count_beatmaps);
Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1));
Assert.That(importBeforeUpdate.Value.DateAdded, Is.EqualTo(dateBefore));
Assert.That(importAfterUpdate.Value.DateAdded, Is.EqualTo(dateBefore));
Assert.That(importBeforeUpdate.ID, Is.EqualTo(importAfterUpdate.ID));
});
}

View File

@ -4,16 +4,34 @@
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Online.API;
using osu.Game.Online.Metadata;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual.Metadata;
using osu.Game.Tests.Visual.OnlinePlay;
namespace osu.Game.Tests.Visual.DailyChallenge
{
public partial class TestSceneDailyChallenge : OnlinePlayTestScene
{
[Cached(typeof(MetadataClient))]
private TestMetadataClient metadataClient = new TestMetadataClient();
[Cached(typeof(INotificationOverlay))]
private NotificationOverlay notificationOverlay = new NotificationOverlay();
[BackgroundDependencyLoader]
private void load()
{
base.Content.Add(notificationOverlay);
base.Content.Add(metadataClient);
}
[Test]
public void TestDailyChallenge()
{
@ -36,5 +54,33 @@ namespace osu.Game.Tests.Visual.DailyChallenge
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
AddStep("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
}
[Test]
public void TestNotifications()
{
var room = new Room
{
RoomID = { Value = 1234 },
Name = { Value = "Daily Challenge: June 4, 2024" },
Playlist =
{
new PlaylistItem(TestResources.CreateTestBeatmapSetInfo().Beatmaps.First())
{
RequiredMods = [new APIMod(new OsuModTraceable())],
AllowedMods = [new APIMod(new OsuModDoubleTime())]
}
},
EndDate = { Value = DateTimeOffset.Now.AddHours(12) },
Category = { Value = RoomCategory.DailyChallenge }
};
AddStep("add room", () => API.Perform(new CreateRoomRequest(room)));
AddStep("set daily challenge info", () => metadataClient.DailyChallengeInfo.Value = new DailyChallengeInfo { RoomID = 1234 });
Screens.OnlinePlay.DailyChallenge.DailyChallenge screen = null!;
AddStep("push screen", () => LoadScreen(screen = new Screens.OnlinePlay.DailyChallenge.DailyChallenge(room)));
AddUntilStep("wait for screen", () => screen.IsCurrentScreen());
AddStep("daily challenge ended", () => metadataClient.DailyChallengeInfo.Value = null);
}
}
}

View File

@ -0,0 +1,64 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Profile;
using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Rulesets.Osu;
using osuTK;
namespace osu.Game.Tests.Visual.Online
{
public partial class TestSceneUserProfileDailyChallenge : OsuManualInputManagerTestScene
{
[Cached]
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>(new UserProfileData(new APIUser(), new OsuRuleset().RulesetInfo));
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
protected override void LoadComplete()
{
base.LoadComplete();
DailyChallengeStatsDisplay display = null!;
AddSliderStep("daily", 0, 999, 2, v => update(s => s.DailyStreakCurrent = v));
AddSliderStep("daily best", 0, 999, 2, v => update(s => s.DailyStreakBest = v));
AddSliderStep("weekly", 0, 250, 1, v => update(s => s.WeeklyStreakCurrent = v));
AddSliderStep("weekly best", 0, 250, 1, v => update(s => s.WeeklyStreakBest = v));
AddSliderStep("top 10%", 0, 999, 0, v => update(s => s.Top10PercentPlacements = v));
AddSliderStep("top 50%", 0, 999, 0, v => update(s => s.Top50PercentPlacements = v));
AddSliderStep("playcount", 0, 999, 0, v => update(s => s.PlayCount = v));
AddStep("create", () =>
{
Clear();
Add(new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background2,
});
Add(display = new DailyChallengeStatsDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(1f),
User = { BindTarget = User },
});
});
AddStep("hover", () => InputManager.MoveMouseTo(display));
}
private void update(Action<APIUserDailyChallengeStatistics> change)
{
change.Invoke(User.Value!.User.DailyChallengeStatistics);
User.Value = new UserProfileData(User.Value.User, User.Value.Ruleset);
}
}
}

View File

@ -4,6 +4,7 @@
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Online.API;
@ -24,7 +25,17 @@ namespace osu.Game.Tests.Visual.Online
[SetUpSteps]
public void SetUp()
{
AddStep("create profile overlay", () => Child = profile = new UserProfileOverlay());
AddStep("create profile overlay", () =>
{
profile = new UserProfileOverlay();
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[] { (typeof(UserProfileOverlay), profile) },
Child = profile,
};
});
}
[Test]
@ -131,6 +142,7 @@ namespace osu.Game.Tests.Visual.Online
CountryCode = CountryCode.JP,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
ProfileHue = hue,
PlayMode = "osu",
});
return true;
}
@ -174,6 +186,7 @@ namespace osu.Game.Tests.Visual.Online
CountryCode = CountryCode.JP,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
ProfileHue = hue,
PlayMode = "osu",
}));
int hue2 = 0;
@ -189,6 +202,7 @@ namespace osu.Game.Tests.Visual.Online
CountryCode = CountryCode.JP,
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
ProfileHue = hue2,
PlayMode = "osu",
}));
}
@ -282,6 +296,15 @@ namespace osu.Game.Tests.Visual.Online
ImageUrlLowRes = "https://assets.ppy.sh/profile-badges/contributor.png",
},
},
DailyChallengeStatistics = new APIUserDailyChallengeStatistics
{
DailyStreakCurrent = 231,
WeeklyStreakCurrent = 18,
DailyStreakBest = 370,
WeeklyStreakBest = 51,
Top10PercentPlacements = 345,
Top50PercentPlacements = 427,
},
Title = "osu!volunteer",
Colour = "ff0000",
Achievements = Array.Empty<APIUserAchievement>(),

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
@ -10,6 +11,7 @@ using osu.Game.Localisation;
using osu.Game.Online.API;
using osu.Game.Online.Metadata;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.Menu;
using osuTK.Input;
using Color4 = osuTK.Graphics.Color4;
@ -39,8 +41,6 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestDailyChallengeButton()
{
AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null));
AddStep("set up API", () => dummyAPI.HandleRequest = req =>
{
switch (req)
@ -67,17 +67,45 @@ namespace osu.Game.Tests.Visual.UserInterface
}
});
AddStep("add button", () => Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ButtonSystemState = ButtonSystemState.TopLevel,
});
NotificationOverlay notificationOverlay = null!;
DependencyProvidingContainer buttonContainer = null!;
AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo
{
RoomID = 1234,
}));
AddStep("add content", () =>
{
notificationOverlay = new NotificationOverlay();
Children = new Drawable[]
{
notificationOverlay,
buttonContainer = new DependencyProvidingContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
CachedDependencies = [(typeof(INotificationOverlay), notificationOverlay)],
Child = new DailyChallengeButton(@"button-default-select", new Color4(102, 68, 204, 255), _ => { }, 0, Key.D)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
ButtonSystemState = ButtonSystemState.TopLevel,
},
},
};
});
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null));
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
AddStep("hide button's parent", () => buttonContainer.Hide());
AddStep("beatmap of the day active", () => metadataClient.DailyChallengeUpdated(new DailyChallengeInfo
{
RoomID = 1234,
}));
AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
}
}
}

View File

@ -43,6 +43,8 @@ namespace osu.Game.Beatmaps
public override async Task<Live<BeatmapSetInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original)
{
var originalDateAdded = original.DateAdded;
Guid originalId = original.ID;
var imported = await Import(notification, new[] { importTask }).ConfigureAwait(false);
@ -57,8 +59,11 @@ namespace osu.Game.Beatmaps
// If there were no changes, ensure we don't accidentally nuke ourselves.
if (first.ID == originalId)
{
first.PerformRead(s =>
first.PerformWrite(s =>
{
// Transfer local values which should be persisted across a beatmap update.
s.DateAdded = originalDateAdded;
// Re-run processing even in this case. We might have outdated metadata.
ProcessBeatmap?.Invoke(s, MetadataLookupScope.OnlineFirst);
});
@ -79,7 +84,7 @@ namespace osu.Game.Beatmaps
original.DeletePending = true;
// Transfer local values which should be persisted across a beatmap update.
updated.DateAdded = original.DateAdded;
updated.DateAdded = originalDateAdded;
transferCollectionReferences(realm, original, updated);
@ -278,6 +283,9 @@ namespace osu.Game.Beatmaps
protected override void UndeleteForReuse(BeatmapSetInfo existing)
{
if (!existing.DeletePending)
return;
base.UndeleteForReuse(existing);
existing.DateAdded = DateTimeOffset.UtcNow;
}

View File

@ -43,6 +43,9 @@ namespace osu.Game.Collections
//
// if we want to support user sorting (but changes will need to be made to realm to persist).
ShowDragHandle.Value = false;
Masking = true;
CornerRadius = item_height / 2;
}
protected override Drawable CreateContent() => new ItemContent(Model);
@ -50,7 +53,7 @@ namespace osu.Game.Collections
/// <summary>
/// The main content of the <see cref="DrawableCollectionListItem"/>.
/// </summary>
private partial class ItemContent : CircularContainer
private partial class ItemContent : CompositeDrawable
{
private readonly Live<BeatmapCollection> collection;
@ -65,13 +68,12 @@ namespace osu.Game.Collections
RelativeSizeAxes = Axes.X;
Height = item_height;
Masking = true;
}
[BackgroundDependencyLoader]
private void load()
{
Children = new[]
InternalChildren = new[]
{
collection.IsManaged
? new DeleteButton(collection)
@ -132,7 +134,7 @@ namespace osu.Game.Collections
}
}
public partial class DeleteButton : CompositeDrawable
public partial class DeleteButton : OsuClickableContainer
{
public Func<Vector2, bool> IsTextBoxHovered = null!;
@ -155,7 +157,7 @@ namespace osu.Game.Collections
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
InternalChild = fadeContainer = new Container
Child = fadeContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.1f,
@ -176,6 +178,14 @@ namespace osu.Game.Collections
}
}
};
Action = () =>
{
if (collection.PerformRead(c => c.BeatmapMD5Hashes.Count) == 0)
deleteCollection();
else
dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection));
};
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos);
@ -195,12 +205,7 @@ namespace osu.Game.Collections
{
background.FlashColour(Color4.White, 150);
if (collection.PerformRead(c => c.BeatmapMD5Hashes.Count) == 0)
deleteCollection();
else
dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection));
return true;
return base.OnClick(e);
}
private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c));

View File

@ -64,6 +64,7 @@ namespace osu.Game.Graphics.Containers
{
InternalChildren = new Drawable[]
{
new HoverClickSounds(),
new GridContainer
{
RelativeSizeAxes = Axes.X,
@ -92,7 +93,6 @@ namespace osu.Game.Graphics.Containers
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
},
new HoverClickSounds()
};
}

View File

@ -0,0 +1,29 @@
// 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.Localisation;
namespace osu.Game.Localisation
{
public static class DailyChallengeStrings
{
private const string prefix = @"osu.Game.Resources.Localisation.DailyChallenge";
/// <summary>
/// "Today&#39;s daily challenge has concluded thanks for playing!
///
/// Tomorrow&#39;s challenge is now being prepared and will appear soon."
/// </summary>
public static LocalisableString ChallengeEndedNotification => new TranslatableString(getKey(@"todays_daily_challenge_has_concluded"),
@"Today's daily challenge has concluded thanks for playing!
Tomorrow's challenge is now being prepared and will appear soon.");
/// <summary>
/// "Today&#39;s daily challenge is now live! Click here to play."
/// </summary>
public static LocalisableString ChallengeLiveNotification => new TranslatableString(getKey(@"todays_daily_challenge_is_now"), @"Today's daily challenge is now live! Click here to play.");
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -272,6 +272,9 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty("groups")]
public APIUserGroup[] Groups;
[JsonProperty("daily_challenge_user_stats")]
public APIUserDailyChallengeStatistics DailyChallengeStatistics = new APIUserDailyChallengeStatistics();
public override string ToString() => Username;
/// <summary>

View File

@ -0,0 +1,41 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using Newtonsoft.Json;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIUserDailyChallengeStatistics
{
[JsonProperty("user_id")]
public int UserID;
[JsonProperty("daily_streak_best")]
public int DailyStreakBest;
[JsonProperty("daily_streak_current")]
public int DailyStreakCurrent;
[JsonProperty("weekly_streak_best")]
public int WeeklyStreakBest;
[JsonProperty("weekly_streak_current")]
public int WeeklyStreakCurrent;
[JsonProperty("top_10p_placements")]
public int Top10PercentPlacements;
[JsonProperty("top_50p_placements")]
public int Top50PercentPlacements;
[JsonProperty("playcount")]
public int PlayCount;
[JsonProperty("last_update")]
public DateTimeOffset? LastUpdate;
[JsonProperty("last_weekly_streak")]
public DateTimeOffset? LastWeeklyStreak;
}
}

View File

@ -4,6 +4,7 @@
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
@ -11,14 +12,16 @@ using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
using osu.Game.Localisation;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Mods
{
public partial class ModCustomisationHeader : OsuHoverContainer
{
private Box background = null!;
private Box backgroundFlash = null!;
private SpriteIcon icon = null!;
[Resolved]
@ -46,6 +49,13 @@ namespace osu.Game.Overlays.Mods
{
RelativeSizeAxes = Axes.Both,
},
backgroundFlash = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White.Opacity(0.4f),
Blending = BlendingParameters.Additive,
Alpha = 0,
},
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
@ -84,6 +94,12 @@ namespace osu.Game.Overlays.Mods
TooltipText = e.NewValue
? string.Empty
: ModSelectOverlayStrings.CustomisationPanelDisabledReason;
if (e.NewValue)
{
backgroundFlash.FadeInFromZero(150, Easing.OutQuad).Then()
.FadeOutFromOne(350, Easing.OutQuad);
}
}, true);
Expanded.BindValueChanged(v =>

View File

@ -138,6 +138,7 @@ namespace osu.Game.Overlays.Mods
},
new GridContainer
{
Padding = new MarginPadding { Top = 1, Bottom = 3 },
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
{

View File

@ -668,6 +668,8 @@ namespace osu.Game.Overlays.Mods
[Cached]
internal partial class ColumnScrollContainer : OsuScrollContainer<ColumnFlowContainer>
{
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
public ColumnScrollContainer()
: base(Direction.Horizontal)
{

View File

@ -0,0 +1,121 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Scoring;
namespace osu.Game.Overlays.Profile.Header.Components
{
public partial class DailyChallengeStatsDisplay : CompositeDrawable, IHasCustomTooltip<DailyChallengeTooltipData>
{
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
public DailyChallengeTooltipData? TooltipContent { get; private set; }
private OsuSpriteText dailyPlayCount = null!;
[Resolved]
private OsuColour colours { get; set; } = null!;
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Both;
CornerRadius = 5;
Masking = true;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4,
},
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding(5f),
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12))
{
AutoSizeAxes = Axes.Both,
// can't use this because osu-web does weird stuff with \\n.
// Text = UsersStrings.ShowDailyChallengeTitle.,
Text = "Daily\nChallenge",
Margin = new MarginPadding { Horizontal = 5f, Bottom = 2f },
},
new Container
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
CornerRadius = 5f,
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background6,
},
dailyPlayCount = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
UseFullGlyphHeight = false,
Colour = colourProvider.Content2,
Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f },
},
}
},
}
},
};
}
protected override void LoadComplete()
{
base.LoadComplete();
User.BindValueChanged(_ => updateDisplay(), true);
}
private void updateDisplay()
{
if (User.Value == null || User.Value.Ruleset.OnlineID != 0)
{
Hide();
return;
}
APIUserDailyChallengeStatistics stats = User.Value.User.DailyChallengeStatistics;
dailyPlayCount.Text = UsersStrings.ShowDailyChallengeUnitDay(stats.PlayCount.ToLocalisableString("N0"));
dailyPlayCount.Colour = colours.ForRankingTier(tierForPlayCount(stats.PlayCount));
TooltipContent = new DailyChallengeTooltipData(colourProvider, stats);
Show();
static RankingTier tierForPlayCount(int playCount) => DailyChallengeStatsTooltip.TierForDaily(playCount / 3);
}
public ITooltip<DailyChallengeTooltipData> GetCustomTooltip() => new DailyChallengeStatsTooltip();
}
}

View File

@ -0,0 +1,241 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Scoring;
using osuTK;
using Box = osu.Framework.Graphics.Shapes.Box;
using Color4 = osuTK.Graphics.Color4;
namespace osu.Game.Overlays.Profile.Header.Components
{
public partial class DailyChallengeStatsTooltip : VisibilityContainer, ITooltip<DailyChallengeTooltipData>
{
private StreakPiece currentDaily = null!;
private StreakPiece currentWeekly = null!;
private StatisticsPiece bestDaily = null!;
private StatisticsPiece bestWeekly = null!;
private StatisticsPiece topTen = null!;
private StatisticsPiece topFifty = null!;
private Box topBackground = null!;
private Box background = null!;
[Resolved]
private OsuColour colours { get; set; } = null!;
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Both;
CornerRadius = 20f;
Masking = true;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.25f),
Radius = 30f,
};
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
topBackground = new Box
{
RelativeSizeAxes = Axes.Both,
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Padding = new MarginPadding(15f),
Spacing = new Vector2(30f),
Children = new[]
{
currentDaily = new StreakPiece(UsersStrings.ShowDailyChallengeDailyStreakCurrent),
currentWeekly = new StreakPiece(UsersStrings.ShowDailyChallengeWeeklyStreakCurrent),
}
},
}
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(15f),
Spacing = new Vector2(10f),
Children = new[]
{
bestDaily = new StatisticsPiece(UsersStrings.ShowDailyChallengeDailyStreakBest),
bestWeekly = new StatisticsPiece(UsersStrings.ShowDailyChallengeWeeklyStreakBest),
topTen = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop10pPlacements),
topFifty = new StatisticsPiece(UsersStrings.ShowDailyChallengeTop50pPlacements),
}
},
}
}
};
}
public void SetContent(DailyChallengeTooltipData content)
{
var statistics = content.Statistics;
var colourProvider = content.ColourProvider;
background.Colour = colourProvider.Background4;
topBackground.Colour = colourProvider.Background5;
currentDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(content.Statistics.DailyStreakCurrent.ToLocalisableString(@"N0"));
currentDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakCurrent));
currentWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakCurrent.ToLocalisableString(@"N0"));
currentWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakCurrent));
bestDaily.Value = UsersStrings.ShowDailyChallengeUnitDay(statistics.DailyStreakBest.ToLocalisableString(@"N0"));
bestDaily.ValueColour = colours.ForRankingTier(TierForDaily(statistics.DailyStreakBest));
bestWeekly.Value = UsersStrings.ShowDailyChallengeUnitWeek(statistics.WeeklyStreakBest.ToLocalisableString(@"N0"));
bestWeekly.ValueColour = colours.ForRankingTier(TierForWeekly(statistics.WeeklyStreakBest));
topTen.Value = statistics.Top10PercentPlacements.ToLocalisableString(@"N0");
topTen.ValueColour = colourProvider.Content2;
topFifty.Value = statistics.Top50PercentPlacements.ToLocalisableString(@"N0");
topFifty.ValueColour = colourProvider.Content2;
}
// reference: https://github.com/ppy/osu-web/blob/8206e0e91eeea80ccf92f0586561346dd40e085e/resources/js/profile-page/daily-challenge.tsx#L13-L43
public static RankingTier TierForDaily(int daily)
{
if (daily > 360)
return RankingTier.Lustrous;
if (daily > 240)
return RankingTier.Radiant;
if (daily > 120)
return RankingTier.Rhodium;
if (daily > 60)
return RankingTier.Platinum;
if (daily > 30)
return RankingTier.Gold;
if (daily > 10)
return RankingTier.Silver;
if (daily > 5)
return RankingTier.Bronze;
return RankingTier.Iron;
}
public static RankingTier TierForWeekly(int weekly) => TierForDaily((weekly - 1) * 7);
protected override void PopIn() => this.FadeIn(200, Easing.OutQuint);
protected override void PopOut() => this.FadeOut(200, Easing.OutQuint);
public void Move(Vector2 pos) => Position = pos;
private partial class StreakPiece : FillFlowContainer
{
private readonly OsuSpriteText valueText;
public LocalisableString Value
{
set => valueText.Text = value;
}
public ColourInfo ValueColour
{
set => valueText.Colour = value;
}
public StreakPiece(LocalisableString title)
{
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Vertical;
Children = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12),
Text = title,
},
valueText = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 40, weight: FontWeight.Light),
}
};
}
}
private partial class StatisticsPiece : CompositeDrawable
{
private readonly OsuSpriteText valueText;
public LocalisableString Value
{
set => valueText.Text = value;
}
public ColourInfo ValueColour
{
set => valueText.Colour = value;
}
public StatisticsPiece(LocalisableString title)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12),
Text = title,
},
valueText = new OsuSpriteText
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Font = OsuFont.GetFont(size: 12),
}
};
}
}
}
public record DailyChallengeTooltipData(OverlayColourProvider ColourProvider, APIUserDailyChallengeStatistics Statistics);
}

View File

@ -44,22 +44,41 @@ namespace osu.Game.Overlays.Profile.Header.Components
Spacing = new Vector2(0, 15),
Children = new Drawable[]
{
new FillFlowContainer
new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(20),
Children = new Drawable[]
ColumnDimensions = new[]
{
detailGlobalRank = new ProfileValueDisplay(true)
new Dimension(GridSizeMode.AutoSize),
new Dimension(GridSizeMode.Absolute, 20),
new Dimension(),
new Dimension(GridSizeMode.AutoSize),
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
},
Content = new[]
{
new[]
{
Title = UsersStrings.ShowRankGlobalSimple,
},
detailCountryRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankCountrySimple,
},
detailGlobalRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankGlobalSimple,
},
Empty(),
detailCountryRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankCountrySimple,
},
new DailyChallengeStatsDisplay
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
User = { BindTarget = User },
}
}
}
},
new Container

View File

@ -2,7 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Localisation;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Settings
@ -10,6 +14,8 @@ namespace osu.Game.Overlays.Settings
public partial class SettingsEnumDropdown<T> : SettingsDropdown<T>
where T : struct, Enum
{
public override IEnumerable<LocalisableString> FilterTerms => base.FilterTerms.Concat(Control.Items.Select(i => i.GetLocalisableDescription()));
protected override OsuDropdown<T> CreateDropdown() => new DropdownControl();
protected new partial class DropdownControl : OsuEnumDropdown<T>

View File

@ -1,8 +1,6 @@
// 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.
#nullable disable
using System;
using osu.Framework.Allocation;
using osu.Framework.Audio.Track;
@ -30,11 +28,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
private readonly Drawable userContent;
[Resolved]
private EditorClock editorClock { get; set; }
private bool alwaysShowControlPoints;
public bool AlwaysShowControlPoints
{
get => alwaysShowControlPoints;
set
{
if (value == alwaysShowControlPoints)
return;
alwaysShowControlPoints = value;
controlPointsVisible.TriggerChange();
}
}
[Resolved]
private EditorBeatmap editorBeatmap { get; set; }
private EditorClock editorClock { get; set; } = null!;
[Resolved]
private EditorBeatmap editorBeatmap { get; set; } = null!;
/// <summary>
/// The timeline's scroll position in the last frame.
@ -61,6 +74,22 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
/// </summary>
private float defaultTimelineZoom;
private WaveformGraph waveform = null!;
private TimelineTickDisplay ticks = null!;
private TimelineControlPointDisplay controlPoints = null!;
private Container mainContent = null!;
private Bindable<float> waveformOpacity = null!;
private Bindable<bool> controlPointsVisible = null!;
private Bindable<bool> ticksVisible = null!;
private double trackLengthForZoom;
private readonly IBindable<Track> track = new Bindable<Track>();
public Timeline(Drawable userContent)
{
this.userContent = userContent;
@ -73,22 +102,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
ScrollbarVisible = false;
}
private WaveformGraph waveform;
private TimelineTickDisplay ticks;
private TimelineControlPointDisplay controlPoints;
private Container mainContent;
private Bindable<float> waveformOpacity;
private Bindable<bool> controlPointsVisible;
private Bindable<bool> ticksVisible;
private double trackLengthForZoom;
private readonly IBindable<Track> track = new Bindable<Track>();
[BackgroundDependencyLoader]
private void load(IBindable<WorkingBeatmap> beatmap, OsuColour colours, OsuConfigManager config)
{
@ -178,7 +191,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
controlPointsVisible.BindValueChanged(visible =>
{
if (visible.NewValue)
if (visible.NewValue || alwaysShowControlPoints)
{
this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint);
mainContent.MoveToY(15, 200, Easing.OutQuint);
@ -318,7 +331,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
}
[Resolved]
private IBeatSnapProvider beatSnapProvider { get; set; }
private IBeatSnapProvider beatSnapProvider { get; set; } = null!;
/// <summary>
/// The total amount of time visible on the timeline.

View File

@ -69,19 +69,24 @@ namespace osu.Game.Screens.Edit.Compose
if (ruleset == null || composer == null)
return base.CreateTimelineContent();
TimelineBreakDisplay breakDisplay = new TimelineBreakDisplay
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Height = 0.75f,
};
return wrapSkinnableContent(new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
Children = new[]
{
// We want to display this below hitobjects to better expose placement objects visually.
// It needs to be above the blueprint container to handle drags on breaks though.
breakDisplay.CreateProxy(),
new TimelineBlueprintContainer(composer),
new TimelineBreakDisplay
{
RelativeSizeAxes = Axes.Both,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Height = 0.75f,
},
breakDisplay
}
});
}

View File

@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
namespace osu.Game.Screens.Edit
@ -26,7 +25,7 @@ namespace osu.Game.Screens.Edit
}
[BackgroundDependencyLoader(true)]
private void load(OverlayColourProvider colourProvider)
private void load()
{
// Grid with only two rows.
// First is the timeline area, which should be allowed to expand as required.
@ -107,10 +106,18 @@ namespace osu.Game.Screens.Edit
MainContent.Add(content);
content.FadeInFromZero(300, Easing.OutQuint);
LoadComponentAsync(TimelineArea = new TimelineArea(CreateTimelineContent()), timelineContent.Add);
LoadComponentAsync(TimelineArea = new TimelineArea(CreateTimelineContent()), timeline =>
{
ConfigureTimeline(timeline);
timelineContent.Add(timeline);
});
});
}
protected virtual void ConfigureTimeline(TimelineArea timelineArea)
{
}
protected abstract Drawable CreateMainContent();
protected virtual Drawable CreateTimelineContent() => new Container();

View File

@ -6,6 +6,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
namespace osu.Game.Screens.Edit.Timing
{
@ -53,5 +54,12 @@ namespace osu.Game.Screens.Edit.Timing
SelectedGroup.Value = EditorBeatmap.ControlPointInfo.GroupAt(nearestTimingPoint.Time);
}
}
protected override void ConfigureTimeline(TimelineArea timelineArea)
{
base.ConfigureTimeline(timelineArea);
timelineArea.Timeline.AlwaysShowControlPoints = true;
}
}
}

View File

@ -23,6 +23,7 @@ using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Metadata;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Screens.OnlinePlay.DailyChallenge;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
@ -44,6 +45,9 @@ namespace osu.Game.Screens.Menu
[Resolved]
private IAPIProvider api { get; set; } = null!;
[Resolved]
private INotificationOverlay? notificationOverlay { get; set; }
public DailyChallengeButton(string sampleName, Color4 colour, Action<MainMenuButton>? clickAction = null, params Key[] triggerKeys)
: base(ButtonSystemStrings.DailyChallenge, sampleName, OsuIcon.DailyChallenge, colour, clickAction, triggerKeys)
{
@ -100,7 +104,8 @@ namespace osu.Game.Screens.Menu
{
base.LoadComplete();
info.BindValueChanged(updateDisplay, true);
info.BindValueChanged(_ => dailyChallengeChanged(postNotification: true));
dailyChallengeChanged(postNotification: false);
}
protected override void Update()
@ -126,27 +131,30 @@ namespace osu.Game.Screens.Menu
}
}
private void updateDisplay(ValueChangedEvent<DailyChallengeInfo?> info)
private void dailyChallengeChanged(bool postNotification)
{
UpdateState();
scheduledCountdownUpdate?.Cancel();
scheduledCountdownUpdate = null;
if (info.NewValue == null)
if (info.Value == null)
{
Room = null;
cover.OnlineInfo = TooltipContent = null;
}
else
{
var roomRequest = new GetRoomRequest(info.NewValue.Value.RoomID);
var roomRequest = new GetRoomRequest(info.Value.Value.RoomID);
roomRequest.Success += room =>
{
Room = room;
cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet;
if (postNotification)
notificationOverlay?.Post(new NewDailyChallengeNotification(room));
updateCountdown();
Scheduler.AddDelayed(updateCountdown, 1000, true);
};

View File

@ -30,6 +30,7 @@ using osu.Game.Online.Metadata;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.OnlinePlay.Components;
@ -54,6 +55,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
private readonly Bindable<IReadOnlyList<Mod>> userMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
private readonly IBindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();
private OnlinePlayScreenWaveContainer waves = null!;
private DailyChallengeLeaderboard leaderboard = null!;
@ -98,6 +100,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; } = null!;
[Resolved]
private INotificationOverlay? notificationOverlay { get; set; }
public override bool DisallowExternalBeatmapRulesetChanges => true;
public override bool? ApplyModTrackAdjustments => true;
@ -336,6 +341,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
}
metadataClient.MultiplayerRoomScoreSet += onRoomScoreSet;
dailyChallengeInfo.BindTo(metadataClient.DailyChallengeInfo);
((IBindable<MultiplayerScore?>)breakdown.UserBestScore).BindTo(leaderboard.UserBestScore);
}
@ -388,6 +394,8 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
apiState.BindTo(API.State);
apiState.BindValueChanged(onlineStateChanged, true);
dailyChallengeInfo.BindValueChanged(dailyChallengeChanged);
}
private void trySetDailyChallengeBeatmap()
@ -405,9 +413,17 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
Schedule(forcefullyExit);
});
private void dailyChallengeChanged(ValueChangedEvent<DailyChallengeInfo?> change)
{
if (change.OldValue?.RoomID == room.RoomID.Value && change.NewValue == null)
{
notificationOverlay?.Post(new SimpleNotification { Text = DailyChallengeStrings.ChallengeEndedNotification });
}
}
private void forcefullyExit()
{
Logger.Log($"{this} forcefully exiting due to loss of API connection");
Logger.Log(@$"{this} forcefully exiting due to loss of API connection");
// This is temporary since we don't currently have a way to force screens to be exited
// See also: `OnlinePlayScreen.forcefullyExit()`

View File

@ -0,0 +1,45 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Beatmaps.Drawables.Cards;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Rooms;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens.Menu;
using osu.Game.Localisation;
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
{
public partial class NewDailyChallengeNotification : SimpleNotification
{
private readonly Room room;
private BeatmapCardNano card = null!;
public NewDailyChallengeNotification(Room room)
{
this.room = room;
}
[BackgroundDependencyLoader]
private void load(OsuGame? game)
{
Text = DailyChallengeStrings.ChallengeLiveNotification;
Content.Add(card = new BeatmapCardNano((APIBeatmapSet)room.Playlist.Single().Beatmap.BeatmapSet!));
Activated = () =>
{
game?.PerformFromScreen(s => s.Push(new DailyChallenge(room)), [typeof(MainMenu)]);
return true;
};
}
protected override void Update()
{
base.Update();
card.Width = Content.DrawWidth;
}
}
}

View File

@ -40,6 +40,7 @@ namespace osu.Game.Screens.Select
{
case "star":
case "stars":
case "sr":
return TryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0.01d / 2);
case "ar":

View File

@ -15,7 +15,6 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Framework.Layout;
using osu.Framework.Localisation;
using osu.Game.Configuration;
using osu.Game.Extensions;
@ -38,6 +37,7 @@ using osu.Game.Users.Drawables;
using osu.Game.Utils;
using osuTK;
using osuTK.Graphics;
using CommonStrings = osu.Game.Localisation.CommonStrings;
namespace osu.Game.Screens.SelectV2.Leaderboards
{
@ -61,7 +61,6 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
private const float statistics_regular_min_width = 175;
private const float statistics_compact_min_width = 100;
private const float rank_label_width = 65;
private const float rank_label_visibility_width_cutoff = rank_label_width + height + username_min_width + statistics_regular_min_width + expanded_right_content_width;
private readonly ScoreInfo score;
private readonly bool sheared;
@ -560,33 +559,34 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
background.FadeColour(IsHovered ? backgroundColour.Lighten(0.2f) : backgroundColour, transition_duration, Easing.OutQuint);
totalScoreBackground.FadeColour(IsHovered ? lightenedGradient : totalScoreBackgroundGradient, transition_duration, Easing.OutQuint);
if (DrawWidth < rank_label_visibility_width_cutoff && IsHovered)
if (IsHovered && currentMode != DisplayMode.Full)
rankLabelOverlay.FadeIn(transition_duration, Easing.OutQuint);
else
rankLabelOverlay.FadeOut(transition_duration, Easing.OutQuint);
}
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
{
Scheduler.AddOnce(() =>
{
// when width decreases
// - hide rank and show rank overlay on avatar when hovered, then
// - compact statistics, then
// - hide statistics
private DisplayMode? currentMode;
if (DrawWidth >= rank_label_visibility_width_cutoff)
protected override void Update()
{
base.Update();
DisplayMode mode = getCurrentDisplayMode();
if (currentMode != mode)
{
if (mode >= DisplayMode.Full)
rankLabel.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint);
else
rankLabel.FadeOut(transition_duration, Easing.OutQuint).MoveToX(-rankLabel.DrawWidth, transition_duration, Easing.OutQuint);
if (DrawWidth >= height + username_min_width + statistics_regular_min_width + expanded_right_content_width)
if (mode >= DisplayMode.Regular)
{
statisticsContainer.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint);
statisticsContainer.Direction = FillDirection.Horizontal;
statisticsContainer.ScaleTo(1, transition_duration, Easing.OutQuint);
}
else if (DrawWidth >= height + username_min_width + statistics_compact_min_width + expanded_right_content_width)
else if (mode >= DisplayMode.Compact)
{
statisticsContainer.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint);
statisticsContainer.Direction = FillDirection.Vertical;
@ -594,13 +594,35 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
}
else
statisticsContainer.FadeOut(transition_duration, Easing.OutQuint).MoveToX(statisticsContainer.DrawWidth, transition_duration, Easing.OutQuint);
});
return base.OnInvalidate(invalidation, source);
currentMode = mode;
}
}
private DisplayMode getCurrentDisplayMode()
{
if (DrawWidth >= height + username_min_width + statistics_regular_min_width + expanded_right_content_width + rank_label_width)
return DisplayMode.Full;
if (DrawWidth >= height + username_min_width + statistics_regular_min_width + expanded_right_content_width)
return DisplayMode.Regular;
if (DrawWidth >= height + username_min_width + statistics_compact_min_width + expanded_right_content_width)
return DisplayMode.Compact;
return DisplayMode.Minimal;
}
#region Subclasses
private enum DisplayMode
{
Minimal,
Compact,
Regular,
Full
}
private partial class DateLabel : DrawableDate
{
public DateLabel(DateTimeOffset date)
@ -749,8 +771,8 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
if (score.Files.Count <= 0) return items.ToArray();
items.Add(new OsuMenuItem(Localisation.CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(score)));
items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score))));
items.Add(new OsuMenuItem(CommonStrings.Export, MenuItemType.Standard, () => scoreManager.Export(score)));
items.Add(new OsuMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score))));
return items.ToArray();
}

View File

@ -21,7 +21,7 @@ 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 IBindable<DailyChallengeInfo?> DailyChallengeInfo => dailyChallengeInfo;
public override Bindable<DailyChallengeInfo?> DailyChallengeInfo => dailyChallengeInfo;
private readonly Bindable<DailyChallengeInfo?> dailyChallengeInfo = new Bindable<DailyChallengeInfo?>();
[Resolved]
@ -88,7 +88,14 @@ namespace osu.Game.Tests.Visual.Metadata
}
public override Task<MultiplayerPlaylistItemStats[]> BeginWatchingMultiplayerRoom(long id)
=> Task.FromResult(new MultiplayerPlaylistItemStats[MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS]);
{
var stats = new MultiplayerPlaylistItemStats[MultiplayerPlaylistItemStats.TOTAL_SCORE_DISTRIBUTION_BINS];
for (int i = 0; i < stats.Length; i++)
stats[i] = new MultiplayerPlaylistItemStats { PlaylistItemID = i };
return Task.FromResult(stats);
}
public override Task EndWatchingMultiplayerRoom(long id) => Task.CompletedTask;
}

View File

@ -30,13 +30,13 @@
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="ppy.LocalisationAnalyser" Version="2024.517.0">
<PackageReference Include="ppy.LocalisationAnalyser" Version="2024.802.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Realm" Version="11.5.0" />
<PackageReference Include="ppy.osu.Framework" Version="2024.802.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.713.0" />
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.802.0" />
<PackageReference Include="Sentry" Version="4.3.0" />
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
<PackageReference Include="SharpCompress" Version="0.36.0" />