mirror of
https://github.com/ppy/osu.git
synced 2025-01-12 15:22:55 +08:00
Merge branch 'master' into bugfix/visual/long-commet-tooltip-overflow
This commit is contained in:
commit
17eb134797
@ -21,7 +21,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ppy.localisationanalyser.tools": {
|
"ppy.localisationanalyser.tools": {
|
||||||
"version": "2024.517.0",
|
"version": "2024.802.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"localisation"
|
"localisation"
|
||||||
]
|
]
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.720.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.802.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||||
|
@ -42,7 +42,12 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
{
|
{
|
||||||
base.PostProcess();
|
base.PostProcess();
|
||||||
|
|
||||||
var hitObjects = Beatmap.HitObjects as List<OsuHitObject> ?? Beatmap.HitObjects.OfType<OsuHitObject>().ToList();
|
ApplyStacking(Beatmap);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static void ApplyStacking(IBeatmap beatmap)
|
||||||
|
{
|
||||||
|
var hitObjects = beatmap.HitObjects as List<OsuHitObject> ?? beatmap.HitObjects.OfType<OsuHitObject>().ToList();
|
||||||
|
|
||||||
if (hitObjects.Count > 0)
|
if (hitObjects.Count > 0)
|
||||||
{
|
{
|
||||||
@ -50,14 +55,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
foreach (var h in hitObjects)
|
foreach (var h in hitObjects)
|
||||||
h.StackHeight = 0;
|
h.StackHeight = 0;
|
||||||
|
|
||||||
if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
if (beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
||||||
applyStacking(Beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
|
applyStacking(beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
|
||||||
else
|
else
|
||||||
applyStackingOld(Beatmap.BeatmapInfo, hitObjects);
|
applyStackingOld(beatmap.BeatmapInfo, hitObjects);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyStacking(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects, int startIndex, int endIndex)
|
private static void applyStacking(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects, int startIndex, int endIndex)
|
||||||
{
|
{
|
||||||
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex);
|
ArgumentOutOfRangeException.ThrowIfGreaterThan(startIndex, endIndex);
|
||||||
ArgumentOutOfRangeException.ThrowIfNegative(startIndex);
|
ArgumentOutOfRangeException.ThrowIfNegative(startIndex);
|
||||||
@ -209,7 +214,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyStackingOld(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects)
|
private static void applyStackingOld(BeatmapInfo beatmapInfo, List<OsuHitObject> hitObjects)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < hitObjects.Count; i++)
|
for (int i = 0; i < hitObjects.Count; i++)
|
||||||
{
|
{
|
||||||
|
@ -295,6 +295,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
|
|
||||||
if (Vector2.Distance(closestSnapPosition, screenSpacePosition) < snapRadius)
|
if (Vector2.Distance(closestSnapPosition, screenSpacePosition) < snapRadius)
|
||||||
{
|
{
|
||||||
|
// if the snap target is a stacked object, snap to its unstacked position rather than its stacked position.
|
||||||
|
// this is intended to make working with stacks easier (because thanks to this, you can drag an object to any
|
||||||
|
// of the items on the stack to add an object to it, rather than having to drag to the position of the *first* object on it at all times).
|
||||||
|
if (b.Item is OsuHitObject osuObject && osuObject.StackOffset != Vector2.Zero)
|
||||||
|
closestSnapPosition = b.ToScreenSpace(b.ToLocalSpace(closestSnapPosition) - osuObject.StackOffset);
|
||||||
|
|
||||||
// only return distance portion, since time is not really valid
|
// only return distance portion, since time is not really valid
|
||||||
snapResult = new SnapResult(closestSnapPosition, null, playfield);
|
snapResult = new SnapResult(closestSnapPosition, null, playfield);
|
||||||
return true;
|
return true;
|
||||||
|
@ -13,6 +13,7 @@ using osu.Game.Graphics.UserInterface;
|
|||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Screens.Edit.Compose.Components;
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
@ -50,12 +51,33 @@ namespace osu.Game.Rulesets.Osu.Edit
|
|||||||
{
|
{
|
||||||
var hitObjects = selectedMovableObjects;
|
var hitObjects = selectedMovableObjects;
|
||||||
|
|
||||||
|
var localDelta = this.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta);
|
||||||
|
|
||||||
|
// this conditional is a rather ugly special case for stacks.
|
||||||
|
// as it turns out, adding the `EditorBeatmap.Update()` call at the end of this would cause stacked objects to jitter when moved around
|
||||||
|
// (they would stack and then unstack every frame).
|
||||||
|
// the reason for that is that the selection handling abstractions are not aware of the distinction between "displayed" and "actual" position
|
||||||
|
// which is unique to osu! due to stacking being applied as a post-processing step.
|
||||||
|
// therefore, the following loop would occur:
|
||||||
|
// - on frame 1 the blueprint is snapped to the stack's baseline position. `EditorBeatmap.Update()` applies stacking successfully,
|
||||||
|
// the blueprint moves up the stack from its original drag position.
|
||||||
|
// - on frame 2 the blueprint's position is now the *stacked* position, which is interpreted higher up as *manually performing an unstack*
|
||||||
|
// to the blueprint's unstacked position (as the machinery higher up only cares about differences in screen space position).
|
||||||
|
if (hitObjects.Any(h => Precision.AlmostEquals(localDelta, -h.StackOffset)))
|
||||||
|
return true;
|
||||||
|
|
||||||
// this will potentially move the selection out of bounds...
|
// this will potentially move the selection out of bounds...
|
||||||
foreach (var h in hitObjects)
|
foreach (var h in hitObjects)
|
||||||
h.Position += this.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta);
|
h.Position += localDelta;
|
||||||
|
|
||||||
// but this will be corrected.
|
// but this will be corrected.
|
||||||
moveSelectionInBounds();
|
moveSelectionInBounds();
|
||||||
|
|
||||||
|
// manually update stacking.
|
||||||
|
// this intentionally bypasses the editor `UpdateState()` / beatmap processor flow for performance reasons,
|
||||||
|
// as the entire flow is too expensive to run on every movement.
|
||||||
|
Scheduler.AddOnce(OsuBeatmapProcessor.ApplyStacking, EditorBeatmap);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -16,7 +16,6 @@ using osu.Framework.Graphics.Shaders.Types;
|
|||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Layout;
|
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
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
|
// -1 signals that the part is unusable, and should not be drawn
|
||||||
parts[i].InvalidationID = -1;
|
parts[i].InvalidationID = -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
AddLayout(partSizeCache);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[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>
|
/// <summary>
|
||||||
/// The amount of time to fade the cursor trail pieces.
|
/// The amount of time to fade the cursor trail pieces.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -156,6 +147,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
|
|
||||||
protected void AddTrail(Vector2 position)
|
protected void AddTrail(Vector2 position)
|
||||||
{
|
{
|
||||||
|
position = ToLocalSpace(position);
|
||||||
|
|
||||||
if (InterpolateMovements)
|
if (InterpolateMovements)
|
||||||
{
|
{
|
||||||
if (!lastPosition.HasValue)
|
if (!lastPosition.HasValue)
|
||||||
@ -174,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
|
|||||||
float distance = diff.Length;
|
float distance = diff.Length;
|
||||||
Vector2 direction = diff / distance;
|
Vector2 direction = diff / distance;
|
||||||
|
|
||||||
float interval = partSize.X / 2.5f * IntervalMultiplier;
|
float interval = Texture.DisplayWidth / 2.5f * IntervalMultiplier;
|
||||||
float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0);
|
float stopAt = distance - (AvoidDrawingNearCursor ? interval : 0);
|
||||||
|
|
||||||
for (float d = interval; d < stopAt; d += interval)
|
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].Time = time + 1;
|
||||||
++parts[currentIndex].InvalidationID;
|
++parts[currentIndex].InvalidationID;
|
||||||
|
|
||||||
|
@ -206,6 +206,15 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos);
|
||||||
|
|
||||||
|
private OsuResumeOverlay.OsuResumeOverlayInputBlocker? resumeInputBlocker;
|
||||||
|
|
||||||
|
public void AttachResumeOverlayInputBlocker(OsuResumeOverlay.OsuResumeOverlayInputBlocker resumeInputBlocker)
|
||||||
|
{
|
||||||
|
Debug.Assert(this.resumeInputBlocker == null);
|
||||||
|
this.resumeInputBlocker = resumeInputBlocker;
|
||||||
|
AddInternal(resumeInputBlocker);
|
||||||
|
}
|
||||||
|
|
||||||
private partial class ProxyContainer : LifetimeManagementContainer
|
private partial class ProxyContainer : LifetimeManagementContainer
|
||||||
{
|
{
|
||||||
public void Add(Drawable proxy) => AddInternal(proxy);
|
public void Add(Drawable proxy) => AddInternal(proxy);
|
||||||
|
@ -33,9 +33,30 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
|
OsuResumeOverlayInputBlocker? inputBlocker = null;
|
||||||
|
|
||||||
|
if (drawableRuleset != null)
|
||||||
|
{
|
||||||
|
var osuPlayfield = (OsuPlayfield)drawableRuleset.Playfield;
|
||||||
|
osuPlayfield.AttachResumeOverlayInputBlocker(inputBlocker = new OsuResumeOverlayInputBlocker());
|
||||||
|
}
|
||||||
|
|
||||||
Add(cursorScaleContainer = new Container
|
Add(cursorScaleContainer = new Container
|
||||||
{
|
{
|
||||||
Child = clickToResumeCursor = new OsuClickToResumeCursor { ResumeRequested = Resume }
|
Child = clickToResumeCursor = new OsuClickToResumeCursor
|
||||||
|
{
|
||||||
|
ResumeRequested = () =>
|
||||||
|
{
|
||||||
|
// since the user had to press a button to tap the resume cursor,
|
||||||
|
// block that press event from potentially reaching a hit circle that's behind the cursor.
|
||||||
|
// we cannot do this from OsuClickToResumeCursor directly since we're in a different input manager tree than the gameplay one,
|
||||||
|
// so we rely on a dedicated input blocking component that's implanted in there to do that for us.
|
||||||
|
if (inputBlocker != null)
|
||||||
|
inputBlocker.BlockNextPress = true;
|
||||||
|
|
||||||
|
Resume();
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,10 +136,7 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
return false;
|
return false;
|
||||||
|
|
||||||
scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint);
|
scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint);
|
||||||
|
ResumeRequested?.Invoke();
|
||||||
// When resuming with a button, we do not want the osu! input manager to see this button press and include it in the score.
|
|
||||||
// To ensure that this works correctly, schedule the resume operation one frame forward, since the resume operation enables the input manager to see input events.
|
|
||||||
Schedule(() => ResumeRequested?.Invoke());
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,5 +161,27 @@ namespace osu.Game.Rulesets.Osu.UI
|
|||||||
this.FadeColour(IsHovered ? Color4.White : Color4.Orange, 400, Easing.OutQuint);
|
this.FadeColour(IsHovered ? Color4.White : Color4.Orange, 400, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public partial class OsuResumeOverlayInputBlocker : Drawable, IKeyBindingHandler<OsuAction>
|
||||||
|
{
|
||||||
|
public bool BlockNextPress;
|
||||||
|
|
||||||
|
public OsuResumeOverlayInputBlocker()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
Depth = float.MinValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool OnPressed(KeyBindingPressEvent<OsuAction> e)
|
||||||
|
{
|
||||||
|
bool block = BlockNextPress;
|
||||||
|
BlockNextPress = false;
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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]
|
[Test]
|
||||||
public void TestNoChanges()
|
public void TestNoChanges()
|
||||||
{
|
{
|
||||||
@ -272,21 +310,25 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
|
var importBeforeUpdate = await importer.Import(new ImportTask(pathOriginal));
|
||||||
|
|
||||||
|
var dateBefore = importBeforeUpdate!.Value.DateAdded;
|
||||||
|
|
||||||
Assert.That(importBeforeUpdate, Is.Not.Null);
|
Assert.That(importBeforeUpdate, Is.Not.Null);
|
||||||
Debug.Assert(importBeforeUpdate != null);
|
Debug.Assert(importBeforeUpdate != null);
|
||||||
|
|
||||||
var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value);
|
var importAfterUpdate = await importer.ImportAsUpdate(new ProgressNotification(), new ImportTask(pathOriginalSecond), importBeforeUpdate.Value);
|
||||||
|
|
||||||
|
realm.Run(r => r.Refresh());
|
||||||
|
|
||||||
Assert.That(importAfterUpdate, Is.Not.Null);
|
Assert.That(importAfterUpdate, Is.Not.Null);
|
||||||
Debug.Assert(importAfterUpdate != null);
|
Debug.Assert(importAfterUpdate != null);
|
||||||
|
|
||||||
realm.Run(r => r.Refresh());
|
|
||||||
|
|
||||||
checkCount<BeatmapSetInfo>(realm, 1);
|
checkCount<BeatmapSetInfo>(realm, 1);
|
||||||
checkCount<BeatmapInfo>(realm, count_beatmaps);
|
checkCount<BeatmapInfo>(realm, count_beatmaps);
|
||||||
checkCount<BeatmapMetadata>(realm, count_beatmaps);
|
checkCount<BeatmapMetadata>(realm, count_beatmaps);
|
||||||
|
|
||||||
Assert.That(importBeforeUpdate.Value.Beatmaps.First().OnlineID, Is.GreaterThan(-1));
|
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));
|
Assert.That(importBeforeUpdate.ID, Is.EqualTo(importAfterUpdate.ID));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -13,6 +14,7 @@ using osu.Game.Configuration;
|
|||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Mania;
|
using osu.Game.Rulesets.Mania;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -32,6 +34,23 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private AudioManager audioManager { get; set; } = null!;
|
private AudioManager audioManager { get; set; } = null!;
|
||||||
|
|
||||||
|
protected override IBeatmap CreateBeatmap(RulesetInfo ruleset) => new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
Position = OsuPlayfield.BASE_SIZE / 2,
|
||||||
|
StartTime = 0,
|
||||||
|
},
|
||||||
|
new HitCircle
|
||||||
|
{
|
||||||
|
Position = OsuPlayfield.BASE_SIZE / 2,
|
||||||
|
StartTime = 5000,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) =>
|
protected override WorkingBeatmap CreateWorkingBeatmap(IBeatmap beatmap, Storyboard? storyboard = null) =>
|
||||||
new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
new ClockBackedTestWorkingBeatmap(beatmap, storyboard, new FramedClock(new ManualClock { Rate = 1 }), audioManager);
|
||||||
|
|
||||||
@ -70,18 +89,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("resume", () => Player.Resume());
|
AddStep("resume", () => Player.Resume());
|
||||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||||
|
|
||||||
// Z key was released before pause, resuming should not trigger it
|
|
||||||
checkKey(() => counter, 1, false);
|
|
||||||
|
|
||||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
|
||||||
checkKey(() => counter, 1, false);
|
|
||||||
|
|
||||||
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
|
||||||
checkKey(() => counter, 2, true);
|
checkKey(() => counter, 2, true);
|
||||||
|
|
||||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
checkKey(() => counter, 2, false);
|
checkKey(() => counter, 2, false);
|
||||||
|
|
||||||
|
AddStep("press Z", () => InputManager.PressKey(Key.Z));
|
||||||
|
checkKey(() => counter, 3, true);
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
checkKey(() => counter, 3, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -90,30 +107,29 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
KeyCounter counter = null!;
|
KeyCounter counter = null!;
|
||||||
|
|
||||||
loadPlayer(() => new ManiaRuleset());
|
loadPlayer(() => new ManiaRuleset());
|
||||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key1));
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Special1));
|
||||||
checkKey(() => counter, 0, false);
|
checkKey(() => counter, 0, false);
|
||||||
|
|
||||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||||
checkKey(() => counter, 1, true);
|
checkKey(() => counter, 1, true);
|
||||||
|
|
||||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||||
checkKey(() => counter, 1, false);
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
AddStep("pause", () => Player.Pause());
|
AddStep("pause", () => Player.Pause());
|
||||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||||
checkKey(() => counter, 1, false);
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||||
checkKey(() => counter, 1, false);
|
checkKey(() => counter, 1, false);
|
||||||
|
|
||||||
AddStep("resume", () => Player.Resume());
|
AddStep("resume", () => Player.Resume());
|
||||||
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
||||||
checkKey(() => counter, 1, false);
|
|
||||||
|
|
||||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||||
checkKey(() => counter, 2, true);
|
checkKey(() => counter, 2, true);
|
||||||
|
|
||||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||||
checkKey(() => counter, 2, false);
|
checkKey(() => counter, 2, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -145,8 +161,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("resume", () => Player.Resume());
|
AddStep("resume", () => Player.Resume());
|
||||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||||
checkKey(() => counterZ, 1, false);
|
checkKey(() => counterZ, 2, true);
|
||||||
checkKey(() => counterX, 1, false);
|
checkKey(() => counterX, 1, false);
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
checkKey(() => counterZ, 2, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -155,12 +174,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
KeyCounter counter = null!;
|
KeyCounter counter = null!;
|
||||||
|
|
||||||
loadPlayer(() => new ManiaRuleset());
|
loadPlayer(() => new ManiaRuleset());
|
||||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key1));
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Special1));
|
||||||
|
|
||||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||||
AddStep("pause", () => Player.Pause());
|
AddStep("pause", () => Player.Pause());
|
||||||
|
|
||||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||||
checkKey(() => counter, 1, true);
|
checkKey(() => counter, 1, true);
|
||||||
|
|
||||||
AddStep("resume", () => Player.Resume());
|
AddStep("resume", () => Player.Resume());
|
||||||
@ -202,12 +221,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddStep("resume", () => Player.Resume());
|
AddStep("resume", () => Player.Resume());
|
||||||
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||||
checkKey(() => counterZ, 1, false);
|
checkKey(() => counterZ, 2, true);
|
||||||
checkKey(() => counterX, 1, true);
|
checkKey(() => counterX, 1, true);
|
||||||
|
|
||||||
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||||
checkKey(() => counterZ, 1, false);
|
|
||||||
checkKey(() => counterX, 1, false);
|
checkKey(() => counterX, 1, false);
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
checkKey(() => counterZ, 2, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
@ -216,24 +237,50 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
KeyCounter counter = null!;
|
KeyCounter counter = null!;
|
||||||
|
|
||||||
loadPlayer(() => new ManiaRuleset());
|
loadPlayer(() => new ManiaRuleset());
|
||||||
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Key1));
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<ManiaAction> actionTrigger && actionTrigger.Action == ManiaAction.Special1));
|
||||||
|
|
||||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||||
checkKey(() => counter, 1, true);
|
checkKey(() => counter, 1, true);
|
||||||
|
|
||||||
AddStep("pause", () => Player.Pause());
|
AddStep("pause", () => Player.Pause());
|
||||||
|
|
||||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||||
|
|
||||||
AddStep("resume", () => Player.Resume());
|
AddStep("resume", () => Player.Resume());
|
||||||
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
||||||
checkKey(() => counter, 1, true);
|
checkKey(() => counter, 1, true);
|
||||||
|
|
||||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||||
checkKey(() => counter, 1, false);
|
checkKey(() => counter, 1, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestOsuRegisterInputFromPressingOrangeCursorButPressIsBlocked()
|
||||||
|
{
|
||||||
|
KeyCounter counter = null!;
|
||||||
|
|
||||||
|
loadPlayer(() => new OsuRuleset());
|
||||||
|
AddStep("get key counter", () => counter = this.ChildrenOfType<KeyCounter>().Single(k => k.Trigger is KeyCounterActionTrigger<OsuAction> actionTrigger && actionTrigger.Action == OsuAction.LeftButton));
|
||||||
|
|
||||||
|
AddStep("pause", () => Player.Pause());
|
||||||
|
AddStep("resume", () => Player.Resume());
|
||||||
|
AddStep("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||||
|
AddStep("press Z to resume", () => InputManager.PressKey(Key.Z));
|
||||||
|
|
||||||
|
// ensure the input manager receives the Z button press...
|
||||||
|
checkKey(() => counter, 1, true);
|
||||||
|
AddAssert("button is pressed in kbc", () => Player.DrawableRuleset.Playfield.FindClosestParent<OsuInputManager>()!.PressedActions.Single() == OsuAction.LeftButton);
|
||||||
|
|
||||||
|
// ...but also ensure the hit circle in front of the cursor isn't hit by checking max combo.
|
||||||
|
AddAssert("circle not hit", () => Player.ScoreProcessor.HighestCombo.Value, () => Is.EqualTo(0));
|
||||||
|
|
||||||
|
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||||
|
|
||||||
|
checkKey(() => counter, 1, false);
|
||||||
|
AddAssert("button is released in kbc", () => !Player.DrawableRuleset.Playfield.FindClosestParent<OsuInputManager>()!.PressedActions.Any());
|
||||||
|
}
|
||||||
|
|
||||||
private void loadPlayer(Func<Ruleset> createRuleset)
|
private void loadPlayer(Func<Ruleset> createRuleset)
|
||||||
{
|
{
|
||||||
AddStep("set ruleset", () => currentRuleset = createRuleset());
|
AddStep("set ruleset", () => currentRuleset = createRuleset());
|
||||||
@ -241,9 +288,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||||
AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType<SkinComponentsContainer>().All(s => s.ComponentsLoaded));
|
AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType<SkinComponentsContainer>().All(s => s.ComponentsLoaded));
|
||||||
|
|
||||||
AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(20000));
|
AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0));
|
||||||
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(20000).Within(500));
|
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(0).Within(500));
|
||||||
AddAssert("not in break", () => !Player.IsBreakTime.Value);
|
AddAssert("not in break", () => !Player.IsBreakTime.Value);
|
||||||
|
AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.DrawableRuleset.Playfield));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void checkKey(Func<KeyCounter> counter, int count, bool active)
|
private void checkKey(Func<KeyCounter> counter, int count, bool active)
|
||||||
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
@ -24,7 +25,17 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUp()
|
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]
|
[Test]
|
||||||
@ -131,6 +142,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
CountryCode = CountryCode.JP,
|
CountryCode = CountryCode.JP,
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||||
ProfileHue = hue,
|
ProfileHue = hue,
|
||||||
|
PlayMode = "osu",
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -174,6 +186,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
CountryCode = CountryCode.JP,
|
CountryCode = CountryCode.JP,
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||||
ProfileHue = hue,
|
ProfileHue = hue,
|
||||||
|
PlayMode = "osu",
|
||||||
}));
|
}));
|
||||||
|
|
||||||
int hue2 = 0;
|
int hue2 = 0;
|
||||||
@ -189,6 +202,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
CountryCode = CountryCode.JP,
|
CountryCode = CountryCode.JP,
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
||||||
ProfileHue = hue2,
|
ProfileHue = hue2,
|
||||||
|
PlayMode = "osu",
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -282,6 +296,15 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
ImageUrlLowRes = "https://assets.ppy.sh/profile-badges/contributor.png",
|
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",
|
Title = "osu!volunteer",
|
||||||
Colour = "ff0000",
|
Colour = "ff0000",
|
||||||
Achievements = Array.Empty<APIUserAchievement>(),
|
Achievements = Array.Empty<APIUserAchievement>(),
|
||||||
|
@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||||
waitForDisplay();
|
waitForDisplay();
|
||||||
|
|
||||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() >= beforePanelCount + scores_per_result);
|
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
|
||||||
AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden);
|
AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -156,7 +156,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||||
waitForDisplay();
|
waitForDisplay();
|
||||||
|
|
||||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() >= beforePanelCount + scores_per_result);
|
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
|
||||||
AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden);
|
AddAssert("right loading spinner hidden", () => resultsScreen.RightSpinner.State.Value == Visibility.Hidden);
|
||||||
|
|
||||||
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
|
AddStep("get panel count", () => beforePanelCount = this.ChildrenOfType<ScorePanel>().Count());
|
||||||
@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
AddAssert("left loading spinner shown", () => resultsScreen.LeftSpinner.State.Value == Visibility.Visible);
|
AddAssert("left loading spinner shown", () => resultsScreen.LeftSpinner.State.Value == Visibility.Visible);
|
||||||
waitForDisplay();
|
waitForDisplay();
|
||||||
|
|
||||||
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() >= beforePanelCount + scores_per_result);
|
AddAssert($"count increased by {scores_per_result}", () => this.ChildrenOfType<ScorePanel>().Count() == beforePanelCount + scores_per_result);
|
||||||
AddAssert("left loading spinner hidden", () => resultsScreen.LeftSpinner.State.Value == Visibility.Hidden);
|
AddAssert("left loading spinner hidden", () => resultsScreen.LeftSpinner.State.Value == Visibility.Hidden);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
public override async Task<Live<BeatmapSetInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original)
|
public override async Task<Live<BeatmapSetInfo>?> ImportAsUpdate(ProgressNotification notification, ImportTask importTask, BeatmapSetInfo original)
|
||||||
{
|
{
|
||||||
|
var originalDateAdded = original.DateAdded;
|
||||||
|
|
||||||
Guid originalId = original.ID;
|
Guid originalId = original.ID;
|
||||||
|
|
||||||
var imported = await Import(notification, new[] { importTask }).ConfigureAwait(false);
|
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 there were no changes, ensure we don't accidentally nuke ourselves.
|
||||||
if (first.ID == originalId)
|
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.
|
// Re-run processing even in this case. We might have outdated metadata.
|
||||||
ProcessBeatmap?.Invoke(s, MetadataLookupScope.OnlineFirst);
|
ProcessBeatmap?.Invoke(s, MetadataLookupScope.OnlineFirst);
|
||||||
});
|
});
|
||||||
@ -79,7 +84,7 @@ namespace osu.Game.Beatmaps
|
|||||||
original.DeletePending = true;
|
original.DeletePending = true;
|
||||||
|
|
||||||
// Transfer local values which should be persisted across a beatmap update.
|
// Transfer local values which should be persisted across a beatmap update.
|
||||||
updated.DateAdded = original.DateAdded;
|
updated.DateAdded = originalDateAdded;
|
||||||
|
|
||||||
transferCollectionReferences(realm, original, updated);
|
transferCollectionReferences(realm, original, updated);
|
||||||
|
|
||||||
@ -278,6 +283,9 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override void UndeleteForReuse(BeatmapSetInfo existing)
|
protected override void UndeleteForReuse(BeatmapSetInfo existing)
|
||||||
{
|
{
|
||||||
|
if (!existing.DeletePending)
|
||||||
|
return;
|
||||||
|
|
||||||
base.UndeleteForReuse(existing);
|
base.UndeleteForReuse(existing);
|
||||||
existing.DateAdded = DateTimeOffset.UtcNow;
|
existing.DateAdded = DateTimeOffset.UtcNow;
|
||||||
}
|
}
|
||||||
|
@ -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).
|
// if we want to support user sorting (but changes will need to be made to realm to persist).
|
||||||
ShowDragHandle.Value = false;
|
ShowDragHandle.Value = false;
|
||||||
|
|
||||||
|
Masking = true;
|
||||||
|
CornerRadius = item_height / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Drawable CreateContent() => new ItemContent(Model);
|
protected override Drawable CreateContent() => new ItemContent(Model);
|
||||||
@ -50,7 +53,7 @@ namespace osu.Game.Collections
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The main content of the <see cref="DrawableCollectionListItem"/>.
|
/// The main content of the <see cref="DrawableCollectionListItem"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private partial class ItemContent : CircularContainer
|
private partial class ItemContent : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly Live<BeatmapCollection> collection;
|
private readonly Live<BeatmapCollection> collection;
|
||||||
|
|
||||||
@ -65,13 +68,12 @@ namespace osu.Game.Collections
|
|||||||
|
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
Height = item_height;
|
Height = item_height;
|
||||||
Masking = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Children = new[]
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
collection.IsManaged
|
collection.IsManaged
|
||||||
? new DeleteButton(collection)
|
? 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!;
|
public Func<Vector2, bool> IsTextBoxHovered = null!;
|
||||||
|
|
||||||
@ -155,7 +157,7 @@ namespace osu.Game.Collections
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
InternalChild = fadeContainer = new Container
|
Child = fadeContainer = new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Alpha = 0.1f,
|
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);
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => base.ReceivePositionalInputAt(screenSpacePos) && !IsTextBoxHovered(screenSpacePos);
|
||||||
@ -195,12 +205,7 @@ namespace osu.Game.Collections
|
|||||||
{
|
{
|
||||||
background.FlashColour(Color4.White, 150);
|
background.FlashColour(Color4.White, 150);
|
||||||
|
|
||||||
if (collection.PerformRead(c => c.BeatmapMD5Hashes.Count) == 0)
|
return base.OnClick(e);
|
||||||
deleteCollection();
|
|
||||||
else
|
|
||||||
dialogOverlay?.Push(new DeleteCollectionDialog(collection, deleteCollection));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c));
|
private void deleteCollection() => collection.PerformWrite(c => c.Realm!.Remove(c));
|
||||||
|
@ -64,6 +64,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
|
new HoverClickSounds(),
|
||||||
new GridContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
@ -92,7 +93,6 @@ namespace osu.Game.Graphics.Containers
|
|||||||
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
|
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }
|
||||||
},
|
},
|
||||||
new HoverClickSounds()
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -272,6 +272,9 @@ namespace osu.Game.Online.API.Requests.Responses
|
|||||||
[JsonProperty("groups")]
|
[JsonProperty("groups")]
|
||||||
public APIUserGroup[] Groups;
|
public APIUserGroup[] Groups;
|
||||||
|
|
||||||
|
[JsonProperty("daily_challenge_user_stats")]
|
||||||
|
public APIUserDailyChallengeStatistics DailyChallengeStatistics = new APIUserDailyChallengeStatistics();
|
||||||
|
|
||||||
public override string ToString() => Username;
|
public override string ToString() => Username;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
@ -11,14 +12,16 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osuTK;
|
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
{
|
{
|
||||||
public partial class ModCustomisationHeader : OsuHoverContainer
|
public partial class ModCustomisationHeader : OsuHoverContainer
|
||||||
{
|
{
|
||||||
private Box background = null!;
|
private Box background = null!;
|
||||||
|
private Box backgroundFlash = null!;
|
||||||
private SpriteIcon icon = null!;
|
private SpriteIcon icon = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
@ -46,6 +49,13 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
|
backgroundFlash = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.White.Opacity(0.4f),
|
||||||
|
Blending = BlendingParameters.Additive,
|
||||||
|
Alpha = 0,
|
||||||
|
},
|
||||||
new OsuSpriteText
|
new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
@ -84,6 +94,12 @@ namespace osu.Game.Overlays.Mods
|
|||||||
TooltipText = e.NewValue
|
TooltipText = e.NewValue
|
||||||
? string.Empty
|
? string.Empty
|
||||||
: ModSelectOverlayStrings.CustomisationPanelDisabledReason;
|
: ModSelectOverlayStrings.CustomisationPanelDisabledReason;
|
||||||
|
|
||||||
|
if (e.NewValue)
|
||||||
|
{
|
||||||
|
backgroundFlash.FadeInFromZero(150, Easing.OutQuad).Then()
|
||||||
|
.FadeOutFromOne(350, Easing.OutQuad);
|
||||||
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
Expanded.BindValueChanged(v =>
|
Expanded.BindValueChanged(v =>
|
||||||
|
@ -138,6 +138,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
},
|
},
|
||||||
new GridContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
|
Padding = new MarginPadding { Top = 1, Bottom = 3 },
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
RowDimensions = new[]
|
RowDimensions = new[]
|
||||||
{
|
{
|
||||||
|
@ -668,6 +668,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
[Cached]
|
[Cached]
|
||||||
internal partial class ColumnScrollContainer : OsuScrollContainer<ColumnFlowContainer>
|
internal partial class ColumnScrollContainer : OsuScrollContainer<ColumnFlowContainer>
|
||||||
{
|
{
|
||||||
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||||
|
|
||||||
public ColumnScrollContainer()
|
public ColumnScrollContainer()
|
||||||
: base(Direction.Horizontal)
|
: base(Direction.Horizontal)
|
||||||
{
|
{
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -44,22 +44,41 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
Spacing = new Vector2(0, 15),
|
Spacing = new Vector2(0, 15),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new FillFlowContainer
|
new GridContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Direction = FillDirection.Horizontal,
|
ColumnDimensions = new[]
|
||||||
Spacing = new Vector2(20),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
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,
|
detailGlobalRank = new ProfileValueDisplay(true)
|
||||||
},
|
{
|
||||||
detailCountryRank = new ProfileValueDisplay(true)
|
Title = UsersStrings.ShowRankGlobalSimple,
|
||||||
{
|
},
|
||||||
Title = UsersStrings.ShowRankCountrySimple,
|
Empty(),
|
||||||
},
|
detailCountryRank = new ProfileValueDisplay(true)
|
||||||
|
{
|
||||||
|
Title = UsersStrings.ShowRankCountrySimple,
|
||||||
|
},
|
||||||
|
new DailyChallengeStatsDisplay
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
User = { BindTarget = User },
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
|
@ -2,7 +2,11 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Settings
|
namespace osu.Game.Overlays.Settings
|
||||||
@ -10,6 +14,8 @@ namespace osu.Game.Overlays.Settings
|
|||||||
public partial class SettingsEnumDropdown<T> : SettingsDropdown<T>
|
public partial class SettingsEnumDropdown<T> : SettingsDropdown<T>
|
||||||
where T : struct, Enum
|
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 override OsuDropdown<T> CreateDropdown() => new DropdownControl();
|
||||||
|
|
||||||
protected new partial class DropdownControl : OsuEnumDropdown<T>
|
protected new partial class DropdownControl : OsuEnumDropdown<T>
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio.Track;
|
using osu.Framework.Audio.Track;
|
||||||
@ -30,11 +28,26 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
|
|
||||||
private readonly Drawable userContent;
|
private readonly Drawable userContent;
|
||||||
|
|
||||||
[Resolved]
|
private bool alwaysShowControlPoints;
|
||||||
private EditorClock editorClock { get; set; }
|
|
||||||
|
public bool AlwaysShowControlPoints
|
||||||
|
{
|
||||||
|
get => alwaysShowControlPoints;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
if (value == alwaysShowControlPoints)
|
||||||
|
return;
|
||||||
|
|
||||||
|
alwaysShowControlPoints = value;
|
||||||
|
controlPointsVisible.TriggerChange();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorBeatmap editorBeatmap { get; set; }
|
private EditorClock editorClock { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private EditorBeatmap editorBeatmap { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The timeline's scroll position in the last frame.
|
/// The timeline's scroll position in the last frame.
|
||||||
@ -61,6 +74,22 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private float defaultTimelineZoom;
|
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)
|
public Timeline(Drawable userContent)
|
||||||
{
|
{
|
||||||
this.userContent = userContent;
|
this.userContent = userContent;
|
||||||
@ -73,22 +102,6 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
ScrollbarVisible = false;
|
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]
|
[BackgroundDependencyLoader]
|
||||||
private void load(IBindable<WorkingBeatmap> beatmap, OsuColour colours, OsuConfigManager config)
|
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 =>
|
controlPointsVisible.BindValueChanged(visible =>
|
||||||
{
|
{
|
||||||
if (visible.NewValue)
|
if (visible.NewValue || alwaysShowControlPoints)
|
||||||
{
|
{
|
||||||
this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint);
|
this.ResizeHeightTo(timeline_expanded_height, 200, Easing.OutQuint);
|
||||||
mainContent.MoveToY(15, 200, Easing.OutQuint);
|
mainContent.MoveToY(15, 200, Easing.OutQuint);
|
||||||
@ -318,7 +331,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IBeatSnapProvider beatSnapProvider { get; set; }
|
private IBeatSnapProvider beatSnapProvider { get; set; } = null!;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The total amount of time visible on the timeline.
|
/// The total amount of time visible on the timeline.
|
||||||
|
@ -69,19 +69,24 @@ namespace osu.Game.Screens.Edit.Compose
|
|||||||
if (ruleset == null || composer == null)
|
if (ruleset == null || composer == null)
|
||||||
return base.CreateTimelineContent();
|
return base.CreateTimelineContent();
|
||||||
|
|
||||||
|
TimelineBreakDisplay breakDisplay = new TimelineBreakDisplay
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Height = 0.75f,
|
||||||
|
};
|
||||||
|
|
||||||
return wrapSkinnableContent(new Container
|
return wrapSkinnableContent(new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
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 TimelineBlueprintContainer(composer),
|
||||||
new TimelineBreakDisplay
|
breakDisplay
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Height = 0.75f,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays;
|
|
||||||
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit
|
namespace osu.Game.Screens.Edit
|
||||||
@ -26,7 +25,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(OverlayColourProvider colourProvider)
|
private void load()
|
||||||
{
|
{
|
||||||
// Grid with only two rows.
|
// Grid with only two rows.
|
||||||
// First is the timeline area, which should be allowed to expand as required.
|
// 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);
|
MainContent.Add(content);
|
||||||
content.FadeInFromZero(300, Easing.OutQuint);
|
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 abstract Drawable CreateMainContent();
|
||||||
|
|
||||||
protected virtual Drawable CreateTimelineContent() => new Container();
|
protected virtual Drawable CreateTimelineContent() => new Container();
|
||||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components.Timeline;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Timing
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
{
|
{
|
||||||
@ -53,5 +54,12 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
SelectedGroup.Value = EditorBeatmap.ControlPointInfo.GroupAt(nearestTimingPoint.Time);
|
SelectedGroup.Value = EditorBeatmap.ControlPointInfo.GroupAt(nearestTimingPoint.Time);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void ConfigureTimeline(TimelineArea timelineArea)
|
||||||
|
{
|
||||||
|
base.ConfigureTimeline(timelineArea);
|
||||||
|
|
||||||
|
timelineArea.Timeline.AlwaysShowControlPoints = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -267,7 +267,8 @@ namespace osu.Game.Screens.Ranking
|
|||||||
foreach (var s in scores)
|
foreach (var s in scores)
|
||||||
addScore(s);
|
addScore(s);
|
||||||
|
|
||||||
lastFetchCompleted = true;
|
// allow a frame for scroll container to adjust its dimensions with the added scores before fetching again.
|
||||||
|
Schedule(() => lastFetchCompleted = true);
|
||||||
|
|
||||||
if (ScorePanelList.IsEmpty)
|
if (ScorePanelList.IsEmpty)
|
||||||
{
|
{
|
||||||
|
@ -40,6 +40,7 @@ namespace osu.Game.Screens.Select
|
|||||||
{
|
{
|
||||||
case "star":
|
case "star":
|
||||||
case "stars":
|
case "stars":
|
||||||
|
case "sr":
|
||||||
return TryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0.01d / 2);
|
return TryUpdateCriteriaRange(ref criteria.StarDifficulty, op, value, 0.01d / 2);
|
||||||
|
|
||||||
case "ar":
|
case "ar":
|
||||||
|
@ -15,7 +15,6 @@ using osu.Framework.Graphics.Cursor;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Layout;
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
@ -38,6 +37,7 @@ using osu.Game.Users.Drawables;
|
|||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
using CommonStrings = osu.Game.Localisation.CommonStrings;
|
||||||
|
|
||||||
namespace osu.Game.Screens.SelectV2.Leaderboards
|
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_regular_min_width = 175;
|
||||||
private const float statistics_compact_min_width = 100;
|
private const float statistics_compact_min_width = 100;
|
||||||
private const float rank_label_width = 65;
|
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 ScoreInfo score;
|
||||||
private readonly bool sheared;
|
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);
|
background.FadeColour(IsHovered ? backgroundColour.Lighten(0.2f) : backgroundColour, transition_duration, Easing.OutQuint);
|
||||||
totalScoreBackground.FadeColour(IsHovered ? lightenedGradient : totalScoreBackgroundGradient, 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);
|
rankLabelOverlay.FadeIn(transition_duration, Easing.OutQuint);
|
||||||
else
|
else
|
||||||
rankLabelOverlay.FadeOut(transition_duration, Easing.OutQuint);
|
rankLabelOverlay.FadeOut(transition_duration, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
|
private DisplayMode? currentMode;
|
||||||
{
|
|
||||||
Scheduler.AddOnce(() =>
|
|
||||||
{
|
|
||||||
// when width decreases
|
|
||||||
// - hide rank and show rank overlay on avatar when hovered, then
|
|
||||||
// - compact statistics, then
|
|
||||||
// - hide statistics
|
|
||||||
|
|
||||||
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);
|
rankLabel.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint);
|
||||||
else
|
else
|
||||||
rankLabel.FadeOut(transition_duration, Easing.OutQuint).MoveToX(-rankLabel.DrawWidth, transition_duration, Easing.OutQuint);
|
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.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint);
|
||||||
statisticsContainer.Direction = FillDirection.Horizontal;
|
statisticsContainer.Direction = FillDirection.Horizontal;
|
||||||
statisticsContainer.ScaleTo(1, transition_duration, Easing.OutQuint);
|
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.FadeIn(transition_duration, Easing.OutQuint).MoveToX(0, transition_duration, Easing.OutQuint);
|
||||||
statisticsContainer.Direction = FillDirection.Vertical;
|
statisticsContainer.Direction = FillDirection.Vertical;
|
||||||
@ -594,13 +594,35 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
statisticsContainer.FadeOut(transition_duration, Easing.OutQuint).MoveToX(statisticsContainer.DrawWidth, transition_duration, Easing.OutQuint);
|
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
|
#region Subclasses
|
||||||
|
|
||||||
|
private enum DisplayMode
|
||||||
|
{
|
||||||
|
Minimal,
|
||||||
|
Compact,
|
||||||
|
Regular,
|
||||||
|
Full
|
||||||
|
}
|
||||||
|
|
||||||
private partial class DateLabel : DrawableDate
|
private partial class DateLabel : DrawableDate
|
||||||
{
|
{
|
||||||
public DateLabel(DateTimeOffset date)
|
public DateLabel(DateTimeOffset date)
|
||||||
@ -749,8 +771,8 @@ namespace osu.Game.Screens.SelectV2.Leaderboards
|
|||||||
|
|
||||||
if (score.Files.Count <= 0) return items.ToArray();
|
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.Export, MenuItemType.Standard, () => scoreManager.Export(score)));
|
||||||
items.Add(new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score))));
|
items.Add(new OsuMenuItem(Resources.Localisation.Web.CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new LocalScoreDeleteDialog(score))));
|
||||||
|
|
||||||
return items.ToArray();
|
return items.ToArray();
|
||||||
}
|
}
|
||||||
|
@ -30,13 +30,13 @@
|
|||||||
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="7.0.0" />
|
||||||
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
<PackageReference Include="Microsoft.Toolkit.HighPerformance" Version="7.1.2" />
|
||||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
<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>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Realm" Version="11.5.0" />
|
<PackageReference Include="Realm" Version="11.5.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework" Version="2024.720.0" />
|
<PackageReference Include="ppy.osu.Framework" Version="2024.802.0" />
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.731.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.802.0" />
|
||||||
<PackageReference Include="Sentry" Version="4.3.0" />
|
<PackageReference Include="Sentry" Version="4.3.0" />
|
||||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||||
<PackageReference Include="SharpCompress" Version="0.36.0" />
|
<PackageReference Include="SharpCompress" Version="0.36.0" />
|
||||||
|
@ -23,6 +23,6 @@
|
|||||||
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
<RuntimeIdentifier>iossimulator-x64</RuntimeIdentifier>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.720.0" />
|
<PackageReference Include="ppy.osu.Framework.iOS" Version="2024.802.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
Loading…
Reference in New Issue
Block a user