mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 04:05:35 +08:00
Merge github.com:ppy/osu into patch/wiki-locale
This commit is contained in:
commit
64468bce6d
@ -21,7 +21,7 @@
|
||||
]
|
||||
},
|
||||
"ppy.localisationanalyser.tools": {
|
||||
"version": "2024.517.0",
|
||||
"version": "2024.802.0",
|
||||
"commands": [
|
||||
"localisation"
|
||||
]
|
||||
|
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
@ -121,9 +121,7 @@ jobs:
|
||||
|
||||
build-only-ios:
|
||||
name: Build only (iOS)
|
||||
# `macos-13` is required, because the newest Microsoft.iOS.Sdk versions require Xcode 14.3.
|
||||
# TODO: can be changed to `macos-latest` once `macos-13` becomes latest (currently in beta: https://github.com/actions/runner-images/tree/main#available-images)
|
||||
runs-on: macos-13
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -137,8 +135,5 @@ jobs:
|
||||
- name: Install .NET Workloads
|
||||
run: dotnet workload install maui-ios
|
||||
|
||||
- name: Select Xcode 15.2
|
||||
run: sudo xcode-select -s /Applications/Xcode_15.2.app/Contents/Developer
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c Debug osu.iOS
|
||||
|
@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.720.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2024.809.2" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
@ -4,7 +4,6 @@
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Skinning;
|
||||
@ -19,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
protected override Ruleset CreatePlayerRuleset() => new CatchRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestLegacyHUDComboCounterHidden([Values] bool withModifiedSkin)
|
||||
public void TestLegacyHUDComboCounterNotExistent([Values] bool withModifiedSkin)
|
||||
{
|
||||
if (withModifiedSkin)
|
||||
{
|
||||
@ -29,10 +28,7 @@ namespace osu.Game.Rulesets.Catch.Tests
|
||||
CreateTest();
|
||||
}
|
||||
|
||||
AddAssert("legacy HUD combo counter hidden", () =>
|
||||
{
|
||||
return Player.ChildrenOfType<LegacyComboCounter>().All(c => c.ChildrenOfType<Container>().Single().Alpha == 0f);
|
||||
});
|
||||
AddAssert("legacy HUD combo counter not added", () => !Player.ChildrenOfType<LegacyComboCounter>().Any());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
@ -28,27 +29,44 @@ namespace osu.Game.Rulesets.Catch.Skinning.Legacy
|
||||
|
||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||
{
|
||||
if (lookup is SkinComponentsContainerLookup containerLookup)
|
||||
switch (lookup)
|
||||
{
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
var components = base.GetDrawableComponent(lookup) as Container;
|
||||
case SkinComponentsContainerLookup containerLookup:
|
||||
if (containerLookup.Target != SkinComponentsContainerLookup.TargetArea.MainHUDComponents)
|
||||
return base.GetDrawableComponent(lookup);
|
||||
|
||||
if (providesComboCounter && components != null)
|
||||
{
|
||||
// catch may provide its own combo counter; hide the default.
|
||||
// todo: this should be done in an elegant way per ruleset, defining which HUD skin components should be displayed.
|
||||
foreach (var legacyComboCounter in components.OfType<LegacyComboCounter>())
|
||||
legacyComboCounter.HiddenByRulesetImplementation = false;
|
||||
}
|
||||
// Modifications for global components.
|
||||
if (containerLookup.Ruleset == null)
|
||||
return base.GetDrawableComponent(lookup) as Container;
|
||||
|
||||
return components;
|
||||
}
|
||||
}
|
||||
// Skin has configuration.
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||
return d;
|
||||
|
||||
if (lookup is CatchSkinComponentLookup catchSkinComponent)
|
||||
// Our own ruleset components default.
|
||||
// todo: remove CatchSkinComponents.CatchComboCounter and refactor LegacyCatchComboCounter to be added here instead.
|
||||
return new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
||||
|
||||
if (keyCounter != null)
|
||||
{
|
||||
// set the anchor to top right so that it won't squash to the return button to the top
|
||||
keyCounter.Anchor = Anchor.CentreRight;
|
||||
keyCounter.Origin = Anchor.CentreRight;
|
||||
keyCounter.X = 0;
|
||||
// 340px is the default height inherit from stable
|
||||
keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y;
|
||||
}
|
||||
})
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new LegacyKeyCounterDisplay(),
|
||||
}
|
||||
};
|
||||
|
||||
case CatchSkinComponentLookup catchSkinComponent:
|
||||
switch (catchSkinComponent.Component)
|
||||
{
|
||||
case CatchSkinComponents.Fruit:
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
|
||||
moveMouseToHitObject(1);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems?.Any(o => o.Text.Value == "Merge selection") == true);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection"));
|
||||
|
||||
mergeSelection();
|
||||
|
||||
@ -198,7 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
|
||||
moveMouseToHitObject(1);
|
||||
AddAssert("merge option not available", () => selectionHandler.ContextMenuItems?.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection"));
|
||||
AddAssert("merge option not available", () => selectionHandler.ContextMenuItems.Length > 0 && selectionHandler.ContextMenuItems.All(o => o.Text.Value != "Merge selection"));
|
||||
mergeSelection();
|
||||
AddAssert("circles not merged", () => circle1 is not null && circle2 is not null
|
||||
&& EditorBeatmap.HitObjects.Contains(circle1) && EditorBeatmap.HitObjects.Contains(circle2));
|
||||
@ -222,7 +222,7 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
|
||||
});
|
||||
|
||||
moveMouseToHitObject(1);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems?.Any(o => o.Text.Value == "Merge selection") == true);
|
||||
AddAssert("merge option available", () => selectionHandler.ContextMenuItems.Any(o => o.Text.Value == "Merge selection"));
|
||||
|
||||
mergeSelection();
|
||||
|
||||
|
@ -42,7 +42,12 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
{
|
||||
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)
|
||||
{
|
||||
@ -50,14 +55,14 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
|
||||
foreach (var h in hitObjects)
|
||||
h.StackHeight = 0;
|
||||
|
||||
if (Beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
||||
applyStacking(Beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
|
||||
if (beatmap.BeatmapInfo.BeatmapVersion >= 6)
|
||||
applyStacking(beatmap.BeatmapInfo, hitObjects, 0, hitObjects.Count - 1);
|
||||
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.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++)
|
||||
{
|
||||
|
@ -61,12 +61,12 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
flashlightRating *= 0.7;
|
||||
}
|
||||
|
||||
double baseAimPerformance = Math.Pow(5 * Math.Max(1, aimRating / 0.0675) - 4, 3) / 100000;
|
||||
double baseSpeedPerformance = Math.Pow(5 * Math.Max(1, speedRating / 0.0675) - 4, 3) / 100000;
|
||||
double baseAimPerformance = OsuStrainSkill.DifficultyToPerformance(aimRating);
|
||||
double baseSpeedPerformance = OsuStrainSkill.DifficultyToPerformance(speedRating);
|
||||
double baseFlashlightPerformance = 0.0;
|
||||
|
||||
if (mods.Any(h => h is OsuModFlashlight))
|
||||
baseFlashlightPerformance = Math.Pow(flashlightRating, 2.0) * 25.0;
|
||||
baseFlashlightPerformance = Flashlight.DifficultyToPerformance(flashlightRating);
|
||||
|
||||
double basePerformance =
|
||||
Math.Pow(
|
||||
|
@ -5,6 +5,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Osu.Difficulty.Skills;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Scoring;
|
||||
@ -86,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
|
||||
private double computeAimValue(ScoreInfo score, OsuDifficultyAttributes attributes)
|
||||
{
|
||||
double aimValue = Math.Pow(5.0 * Math.Max(1.0, attributes.AimDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
double aimValue = OsuStrainSkill.DifficultyToPerformance(attributes.AimDifficulty);
|
||||
|
||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
@ -139,7 +140,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (score.Mods.Any(h => h is OsuModRelax))
|
||||
return 0.0;
|
||||
|
||||
double speedValue = Math.Pow(5.0 * Math.Max(1.0, attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
double speedValue = OsuStrainSkill.DifficultyToPerformance(attributes.SpeedDifficulty);
|
||||
|
||||
double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) +
|
||||
(totalHits > 2000 ? Math.Log10(totalHits / 2000.0) * 0.5 : 0.0);
|
||||
@ -226,7 +227,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty
|
||||
if (!score.Mods.Any(h => h is OsuModFlashlight))
|
||||
return 0.0;
|
||||
|
||||
double flashlightValue = Math.Pow(attributes.FlashlightDifficulty, 2.0) * 25.0;
|
||||
double flashlightValue = Flashlight.DifficultyToPerformance(attributes.FlashlightDifficulty);
|
||||
|
||||
// Penalize misses by assessing # of misses relative to the total # of objects. Default a 3% reduction for any # of misses.
|
||||
if (effectiveMissCount > 0)
|
||||
|
@ -42,5 +42,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
}
|
||||
|
||||
public override double DifficultyValue() => GetCurrentStrainPeaks().Sum() * OsuStrainSkill.DEFAULT_DIFFICULTY_MULTIPLIER;
|
||||
|
||||
public static double DifficultyToPerformance(double difficulty) => 25 * Math.Pow(difficulty, 2);
|
||||
}
|
||||
}
|
||||
|
@ -67,5 +67,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills
|
||||
|
||||
return difficulty * DifficultyMultiplier;
|
||||
}
|
||||
|
||||
public static double DifficultyToPerformance(double difficulty) => Math.Pow(5.0 * Math.Max(1.0, difficulty / 0.0675) - 4.0, 3.0) / 100000.0;
|
||||
}
|
||||
}
|
||||
|
@ -295,6 +295,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
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
|
||||
snapResult = new SnapResult(closestSnapPosition, null, playfield);
|
||||
return true;
|
||||
|
@ -11,14 +11,14 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public partial class OsuHitObjectInspector : HitObjectInspector
|
||||
{
|
||||
protected override void AddInspectorValues()
|
||||
protected override void AddInspectorValues(HitObject[] objects)
|
||||
{
|
||||
base.AddInspectorValues();
|
||||
base.AddInspectorValues(objects);
|
||||
|
||||
if (EditorBeatmap.SelectedHitObjects.Count > 0)
|
||||
if (objects.Length > 0)
|
||||
{
|
||||
var firstInSelection = (OsuHitObject)EditorBeatmap.SelectedHitObjects.MinBy(ho => ho.StartTime)!;
|
||||
var lastInSelection = (OsuHitObject)EditorBeatmap.SelectedHitObjects.MaxBy(ho => ho.GetEndTime())!;
|
||||
var firstInSelection = (OsuHitObject)objects.MinBy(ho => ho.StartTime)!;
|
||||
var lastInSelection = (OsuHitObject)objects.MaxBy(ho => ho.GetEndTime())!;
|
||||
|
||||
Debug.Assert(firstInSelection != null && lastInSelection != null);
|
||||
|
||||
|
@ -13,6 +13,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Edit;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Screens.Edit.Compose.Components;
|
||||
@ -50,12 +51,33 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
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...
|
||||
foreach (var h in hitObjects)
|
||||
h.Position += this.ScreenSpaceDeltaToParentSpace(moveEvent.ScreenSpaceDelta);
|
||||
h.Position += localDelta;
|
||||
|
||||
// but this will be corrected.
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@ -41,8 +42,47 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
|
||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||
{
|
||||
if (lookup is OsuSkinComponentLookup osuComponent)
|
||||
switch (lookup)
|
||||
{
|
||||
case SkinComponentsContainerLookup containerLookup:
|
||||
// Only handle per ruleset defaults here.
|
||||
if (containerLookup.Ruleset == null)
|
||||
return base.GetDrawableComponent(lookup);
|
||||
|
||||
// Skin has configuration.
|
||||
if (base.GetDrawableComponent(lookup) is UserConfiguredLayoutContainer d)
|
||||
return d;
|
||||
|
||||
// Our own ruleset components default.
|
||||
switch (containerLookup.Target)
|
||||
{
|
||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||
return new DefaultSkinComponentsContainer(container =>
|
||||
{
|
||||
var keyCounter = container.OfType<LegacyKeyCounterDisplay>().FirstOrDefault();
|
||||
|
||||
if (keyCounter != null)
|
||||
{
|
||||
// set the anchor to top right so that it won't squash to the return button to the top
|
||||
keyCounter.Anchor = Anchor.CentreRight;
|
||||
keyCounter.Origin = Anchor.CentreRight;
|
||||
keyCounter.X = 0;
|
||||
// 340px is the default height inherit from stable
|
||||
keyCounter.Y = container.ToLocalSpace(new Vector2(0, container.ScreenSpaceDrawQuad.Centre.Y - 340f)).Y;
|
||||
}
|
||||
})
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new LegacyComboCounter(),
|
||||
new LegacyKeyCounterDisplay(),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
case OsuSkinComponentLookup osuComponent:
|
||||
switch (osuComponent.Component)
|
||||
{
|
||||
case OsuSkinComponents.FollowPoint:
|
||||
@ -171,10 +211,11 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
||||
default:
|
||||
throw new UnsupportedSkinComponentException(lookup);
|
||||
}
|
||||
}
|
||||
|
||||
default:
|
||||
return base.GetDrawableComponent(lookup);
|
||||
}
|
||||
}
|
||||
|
||||
public override IBindable<TValue>? GetConfig<TLookup, TValue>(TLookup lookup)
|
||||
{
|
||||
|
@ -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;
|
||||
|
||||
|
@ -206,6 +206,15 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
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
|
||||
{
|
||||
public void Add(Drawable proxy) => AddInternal(proxy);
|
||||
|
@ -33,9 +33,30 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
OsuResumeOverlayInputBlocker? inputBlocker = null;
|
||||
|
||||
if (drawableRuleset != null)
|
||||
{
|
||||
var osuPlayfield = (OsuPlayfield)drawableRuleset.Playfield;
|
||||
osuPlayfield.AttachResumeOverlayInputBlocker(inputBlocker = new OsuResumeOverlayInputBlocker());
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
scaleTransitionContainer.ScaleTo(2, TRANSITION_TIME, Easing.OutQuint);
|
||||
|
||||
// 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());
|
||||
ResumeRequested?.Invoke();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -143,5 +161,27 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,9 +14,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
{
|
||||
}
|
||||
|
||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup component)
|
||||
public override Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||
{
|
||||
switch (component)
|
||||
switch (lookup)
|
||||
{
|
||||
case GameplaySkinComponentLookup<HitResult> resultComponent:
|
||||
// This should eventually be moved to a skin setting, when supported.
|
||||
@ -75,7 +75,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Argon
|
||||
break;
|
||||
}
|
||||
|
||||
return base.GetDrawableComponent(component);
|
||||
return base.GetDrawableComponent(lookup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -468,6 +468,40 @@ namespace osu.Game.Tests.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeBeatmapHitObjectCoordinatesLegacy()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder();
|
||||
|
||||
using (var resStream = TestResources.OpenResource("hitobject-coordinates-legacy.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var hitObjects = decoder.Decode(stream).HitObjects;
|
||||
|
||||
var positionData = hitObjects[0] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.AreEqual(new Vector2(256, 256), positionData!.Position);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeBeatmapHitObjectCoordinatesLazer()
|
||||
{
|
||||
var decoder = new LegacyBeatmapDecoder(LegacyBeatmapEncoder.FIRST_LAZER_VERSION);
|
||||
|
||||
using (var resStream = TestResources.OpenResource("hitobject-coordinates-lazer.osu"))
|
||||
using (var stream = new LineBufferedReader(resStream))
|
||||
{
|
||||
var hitObjects = decoder.Decode(stream).HitObjects;
|
||||
|
||||
var positionData = hitObjects[0] as IHasPosition;
|
||||
|
||||
Assert.IsNotNull(positionData);
|
||||
Assert.AreEqual(new Vector2(256.99853f, 256.001f), positionData!.Position);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDecodeBeatmapHitObjects()
|
||||
{
|
||||
|
@ -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));
|
||||
});
|
||||
}
|
||||
|
@ -52,10 +52,7 @@ namespace osu.Game.Tests.Editing
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
composer = new TestHitObjectComposer()
|
||||
};
|
||||
Child = composer = new TestHitObjectComposer();
|
||||
|
||||
BeatDivisor.Value = 1;
|
||||
|
||||
|
BIN
osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/argon-layout-version-0.osk
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/classic-layout-version-0.osk
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/modified-classic-20240724.osk
Normal file
Binary file not shown.
BIN
osu.Game.Tests/Resources/Archives/triangles-layout-version-0.osk
Normal file
BIN
osu.Game.Tests/Resources/Archives/triangles-layout-version-0.osk
Normal file
Binary file not shown.
@ -73,7 +73,12 @@ namespace osu.Game.Tests.Resources
|
||||
|
||||
private static string getTempFilename() => temp_storage.GetFullPath(Guid.NewGuid() + ".osz");
|
||||
|
||||
private static int importId;
|
||||
private static int testId = 1;
|
||||
|
||||
/// <summary>
|
||||
/// Get a unique int value which is incremented each call.
|
||||
/// </summary>
|
||||
public static int GetNextTestID() => Interlocked.Increment(ref testId);
|
||||
|
||||
/// <summary>
|
||||
/// Create a test beatmap set model.
|
||||
@ -88,7 +93,7 @@ namespace osu.Game.Tests.Resources
|
||||
|
||||
RulesetInfo getRuleset() => rulesets?[j++ % rulesets.Length];
|
||||
|
||||
int setId = Interlocked.Increment(ref importId);
|
||||
int setId = GetNextTestID();
|
||||
|
||||
var metadata = new BeatmapMetadata
|
||||
{
|
||||
|
6
osu.Game.Tests/Resources/hitobject-coordinates-lazer.osu
Normal file
6
osu.Game.Tests/Resources/hitobject-coordinates-lazer.osu
Normal file
@ -0,0 +1,6 @@
|
||||
osu file format v128
|
||||
|
||||
[HitObjects]
|
||||
// Coordinates should be preserves in lazer beatmaps.
|
||||
|
||||
256.99853,256.001,1000,49,0,0:0:0:0:
|
@ -0,0 +1,5 @@
|
||||
osu file format v14
|
||||
|
||||
[HitObjects]
|
||||
// Coordinates should be truncated to int values in legacy beatmaps.
|
||||
256.99853,256.001,1000,49,0,0:0:0:0:
|
@ -65,7 +65,9 @@ namespace osu.Game.Tests.Skins
|
||||
// Covers default rank display
|
||||
"Archives/modified-default-20230809.osk",
|
||||
// Covers legacy rank display
|
||||
"Archives/modified-classic-20230809.osk"
|
||||
"Archives/modified-classic-20230809.osk",
|
||||
// Covers legacy key counter
|
||||
"Archives/modified-classic-20240724.osk"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
@ -304,11 +304,6 @@ namespace osu.Game.Tests.Visual.Background
|
||||
{
|
||||
private bool? lastLoadTriggerCausedChange;
|
||||
|
||||
public TestBackgroundScreenDefault()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
public override bool Next()
|
||||
{
|
||||
bool didChange = base.Next();
|
||||
|
@ -0,0 +1,89 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
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;
|
||||
using CreateRoomRequest = osu.Game.Online.Rooms.CreateRoomRequest;
|
||||
|
||||
namespace osu.Game.Tests.Visual.DailyChallenge
|
||||
{
|
||||
public partial class TestSceneDailyChallengeIntro : 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]
|
||||
[Solo]
|
||||
public void TestDailyChallenge()
|
||||
{
|
||||
var room = new Room
|
||||
{
|
||||
RoomID = { Value = 1234 },
|
||||
Name = { Value = "Daily Challenge: June 4, 2024" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem(CreateAPIBeatmapSet().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("push screen", () => LoadScreen(new Screens.OnlinePlay.DailyChallenge.DailyChallengeIntro(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);
|
||||
}
|
||||
}
|
||||
}
|
@ -28,6 +28,74 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
private GlobalActionContainer globalActionContainer => this.ChildrenOfType<GlobalActionContainer>().Single();
|
||||
|
||||
[Test]
|
||||
public void TestDeleteUsingMiddleMouse()
|
||||
{
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
|
||||
AddStep("delete with middle mouse", () => InputManager.Click(MouseButton.Middle));
|
||||
AddAssert("circle removed", () => EditorBeatmap.HitObjects, () => Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeleteUsingShiftRightClick()
|
||||
{
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
|
||||
AddStep("delete with right mouse", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ShiftLeft);
|
||||
InputManager.Click(MouseButton.Right);
|
||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||
});
|
||||
AddAssert("circle removed", () => EditorBeatmap.HitObjects, () => Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestContextMenu()
|
||||
{
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
|
||||
AddStep("delete with right mouse", () =>
|
||||
{
|
||||
InputManager.Click(MouseButton.Right);
|
||||
});
|
||||
AddAssert("circle not removed", () => EditorBeatmap.HitObjects, () => Has.One.Items);
|
||||
AddAssert("circle selected", () => EditorBeatmap.SelectedHitObjects, () => Has.One.Items);
|
||||
}
|
||||
|
||||
[Test]
|
||||
[Solo]
|
||||
public void TestCommitPlacementViaRightClick()
|
||||
{
|
||||
Playfield playfield = null!;
|
||||
|
||||
AddStep("select slider placement tool", () => InputManager.Key(Key.Number3));
|
||||
AddStep("move mouse to top left of playfield", () =>
|
||||
{
|
||||
playfield = this.ChildrenOfType<Playfield>().Single();
|
||||
var location = (3 * playfield.ScreenSpaceDrawQuad.TopLeft + playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
||||
InputManager.MoveMouseTo(location);
|
||||
});
|
||||
AddStep("begin placement", () => InputManager.Click(MouseButton.Left));
|
||||
AddStep("move mouse to bottom right of playfield", () =>
|
||||
{
|
||||
var location = (playfield.ScreenSpaceDrawQuad.TopLeft + 3 * playfield.ScreenSpaceDrawQuad.BottomRight) / 4;
|
||||
InputManager.MoveMouseTo(location);
|
||||
});
|
||||
AddStep("confirm via right click", () => InputManager.Click(MouseButton.Right));
|
||||
AddAssert("slider placed", () => EditorBeatmap.HitObjects.Count, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCommitPlacementViaGlobalAction()
|
||||
{
|
||||
|
@ -9,7 +9,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Track;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@ -45,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
// best way to check without exposing.
|
||||
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
|
||||
private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
||||
private Drawable keyCounterContent => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<Drawable>().Skip(1).First();
|
||||
|
||||
public TestSceneHUDOverlay()
|
||||
{
|
||||
@ -79,7 +78,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
|
||||
|
||||
AddAssert("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
|
||||
AddAssert("key counter flow is visible", () => keyCounterFlow.IsPresent);
|
||||
AddAssert("key counter flow is visible", () => keyCounterContent.IsPresent);
|
||||
AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent);
|
||||
}
|
||||
|
||||
@ -104,7 +103,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
|
||||
|
||||
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
|
||||
AddAssert("key counter flow not affected", () => keyCounterFlow.IsPresent);
|
||||
AddAssert("key counter flow not affected", () => keyCounterContent.IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -150,11 +149,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||
AddUntilStep("key counters hidden", () => !keyCounterFlow.IsPresent);
|
||||
AddUntilStep("key counters hidden", () => !keyCounterContent.IsPresent);
|
||||
|
||||
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
|
||||
AddUntilStep("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
|
||||
AddUntilStep("key counters still hidden", () => !keyCounterFlow.IsPresent);
|
||||
AddUntilStep("key counters still hidden", () => !keyCounterContent.IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -56,6 +57,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
Anchor = Anchor.Centre,
|
||||
Scale = new Vector2(1, -1)
|
||||
},
|
||||
new LegacyKeyCounterDisplay
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
@ -89,6 +95,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
Anchor = Anchor.Centre,
|
||||
Rotation = 90,
|
||||
},
|
||||
new LegacyKeyCounterDisplay
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Rotation = 90,
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
@ -13,6 +14,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mania;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
@ -32,6 +34,23 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Resolved]
|
||||
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) =>
|
||||
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("go to resume cursor", () => InputManager.MoveMouseTo(this.ChildrenOfType<OsuResumeOverlay.OsuClickToResumeCursor>().Single()));
|
||||
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);
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
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]
|
||||
@ -90,30 +107,29 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
KeyCounter counter = null!;
|
||||
|
||||
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);
|
||||
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||
checkKey(() => counter, 1, true);
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("pause", () => Player.Pause());
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||
checkKey(() => counter, 1, false);
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
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);
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||
checkKey(() => counter, 2, false);
|
||||
}
|
||||
|
||||
@ -145,8 +161,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
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));
|
||||
checkKey(() => counterZ, 1, false);
|
||||
checkKey(() => counterZ, 2, true);
|
||||
checkKey(() => counterX, 1, false);
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
checkKey(() => counterZ, 2, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -155,12 +174,12 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
KeyCounter counter = null!;
|
||||
|
||||
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("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||
checkKey(() => counter, 1, true);
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
@ -202,12 +221,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
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));
|
||||
checkKey(() => counterZ, 1, false);
|
||||
checkKey(() => counterZ, 2, true);
|
||||
checkKey(() => counterX, 1, true);
|
||||
|
||||
AddStep("release X", () => InputManager.ReleaseKey(Key.X));
|
||||
checkKey(() => counterZ, 1, false);
|
||||
checkKey(() => counterX, 1, false);
|
||||
|
||||
AddStep("release Z", () => InputManager.ReleaseKey(Key.Z));
|
||||
checkKey(() => counterZ, 2, false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -216,24 +237,50 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
KeyCounter counter = null!;
|
||||
|
||||
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);
|
||||
|
||||
AddStep("pause", () => Player.Pause());
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddStep("press D", () => InputManager.PressKey(Key.D));
|
||||
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||
AddStep("press space", () => InputManager.PressKey(Key.Space));
|
||||
|
||||
AddStep("resume", () => Player.Resume());
|
||||
AddUntilStep("wait for resume", () => Player.GameplayClockContainer.IsRunning);
|
||||
checkKey(() => counter, 1, true);
|
||||
|
||||
AddStep("release D", () => InputManager.ReleaseKey(Key.D));
|
||||
AddStep("release space", () => InputManager.ReleaseKey(Key.Space));
|
||||
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)
|
||||
{
|
||||
AddStep("set ruleset", () => currentRuleset = createRuleset());
|
||||
@ -241,9 +288,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("player loaded", () => Player.IsLoaded && Player.Alpha == 1);
|
||||
AddUntilStep("wait for hud", () => Player.HUDOverlay.ChildrenOfType<SkinComponentsContainer>().All(s => s.ComponentsLoaded));
|
||||
|
||||
AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(20000));
|
||||
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(20000).Within(500));
|
||||
AddStep("seek to gameplay", () => Player.GameplayClockContainer.Seek(0));
|
||||
AddUntilStep("wait for seek to finish", () => Player.DrawableRuleset.FrameStableClock.CurrentTime, () => Is.EqualTo(0).Within(500));
|
||||
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)
|
||||
|
@ -7,21 +7,25 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.SkinEditor;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Skinning.Components;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -39,6 +43,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[Cached]
|
||||
public readonly EditorClipboard Clipboard = new EditorClipboard();
|
||||
|
||||
[Resolved]
|
||||
private SkinManager skins { get; set; } = null!;
|
||||
|
||||
private SkinComponentsContainer targetContainer => Player.ChildrenOfType<SkinComponentsContainer>().First();
|
||||
|
||||
[SetUpSteps]
|
||||
@ -46,6 +53,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("reset skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||
AddUntilStep("wait for hud load", () => targetContainer.ComponentsLoaded);
|
||||
|
||||
AddStep("reload skin editor", () =>
|
||||
@ -369,6 +377,93 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
() => Is.EqualTo(3));
|
||||
}
|
||||
|
||||
private SkinComponentsContainer globalHUDTarget => Player.ChildrenOfType<SkinComponentsContainer>()
|
||||
.Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset == null);
|
||||
|
||||
private SkinComponentsContainer rulesetHUDTarget => Player.ChildrenOfType<SkinComponentsContainer>()
|
||||
.Single(c => c.Lookup.Target == SkinComponentsContainerLookup.TargetArea.MainHUDComponents && c.Lookup.Ruleset != null);
|
||||
|
||||
[Test]
|
||||
public void TestMigrationArgon()
|
||||
{
|
||||
Live<SkinInfo> importedSkin = null!;
|
||||
|
||||
AddStep("import old argon skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"argon-layout-version-0.osk").SkinInfo);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType<ArgonComboCounter>().Any());
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<ArgonComboCounter>().Count() == 1);
|
||||
|
||||
AddStep("add combo to global target", () => globalHUDTarget.Add(new ArgonComboCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(2f),
|
||||
}));
|
||||
AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value));
|
||||
|
||||
AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||
AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType<ArgonComboCounter>().Count() == 1);
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<ArgonComboCounter>().Count() == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMigrationTriangles()
|
||||
{
|
||||
Live<SkinInfo> importedSkin = null!;
|
||||
|
||||
AddStep("import old triangles skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"triangles-layout-version-0.osk").SkinInfo);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType<DefaultComboCounter>().Any());
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<DefaultComboCounter>().Count() == 1);
|
||||
|
||||
AddStep("add combo to global target", () => globalHUDTarget.Add(new DefaultComboCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(2f),
|
||||
}));
|
||||
AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value));
|
||||
|
||||
AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||
AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType<DefaultComboCounter>().Count() == 1);
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<DefaultComboCounter>().Count() == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMigrationLegacy()
|
||||
{
|
||||
Live<SkinInfo> importedSkin = null!;
|
||||
|
||||
AddStep("import old classic skin", () => skins.CurrentSkinInfo.Value = importedSkin = importSkinFromArchives(@"classic-layout-version-0.osk").SkinInfo);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("no combo in global target", () => !globalHUDTarget.Components.OfType<LegacyComboCounter>().Any());
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<LegacyComboCounter>().Count() == 1);
|
||||
|
||||
AddStep("add combo to global target", () => globalHUDTarget.Add(new LegacyComboCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Scale = new Vector2(2f),
|
||||
}));
|
||||
AddStep("save skin", () => skins.Save(skins.CurrentSkin.Value));
|
||||
|
||||
AddStep("select another skin", () => skins.CurrentSkinInfo.SetDefault());
|
||||
AddStep("select skin again", () => skins.CurrentSkinInfo.Value = importedSkin);
|
||||
AddUntilStep("wait for load", () => globalHUDTarget.ComponentsLoaded && rulesetHUDTarget.ComponentsLoaded);
|
||||
AddAssert("combo placed in global target", () => globalHUDTarget.Components.OfType<LegacyComboCounter>().Count() == 1);
|
||||
AddAssert("combo placed in ruleset target", () => rulesetHUDTarget.Components.OfType<LegacyComboCounter>().Count() == 1);
|
||||
}
|
||||
|
||||
private Skin importSkinFromArchives(string filename)
|
||||
{
|
||||
var imported = skins.Import(new ImportTask(TestResources.OpenResource($@"Archives/{filename}"), filename)).GetResultSafely();
|
||||
return imported.PerformRead(skinInfo => skins.GetSkin(skinInfo));
|
||||
}
|
||||
|
||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||
|
||||
private partial class TestSkinEditorChangeHandler : SkinEditorChangeHandler
|
||||
|
@ -4,7 +4,6 @@
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
@ -28,17 +27,5 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddStep("reset combo", () => scoreProcessor.Combo.Value = 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLegacyComboCounterHiddenByRulesetImplementation()
|
||||
{
|
||||
AddToggleStep("toggle legacy hidden by ruleset", visible =>
|
||||
{
|
||||
foreach (var legacyCounter in this.ChildrenOfType<LegacyComboCounter>())
|
||||
legacyCounter.HiddenByRulesetImplementation = visible;
|
||||
});
|
||||
|
||||
AddRepeatStep("increase combo", () => scoreProcessor.Combo.Value++, 10);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,12 +16,13 @@ using osu.Framework.Testing;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Gameplay;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
@ -91,10 +92,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
SetContents(_ =>
|
||||
{
|
||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||
|
||||
// Add any key just to display the key counter visually.
|
||||
hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||
hudOverlay = new HUDOverlay(new DrawableOsuRuleset(new OsuRuleset(), new OsuBeatmap()), Array.Empty<Mod>());
|
||||
|
||||
action?.Invoke(hudOverlay);
|
||||
|
||||
|
@ -0,0 +1,42 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneSkinnableKeyCounter : SkinnableHUDComponentTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly InputCountController controller = new InputCountController();
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
AddStep("create dependencies", () =>
|
||||
{
|
||||
Add(controller);
|
||||
controller.Add(new KeyCounterKeyboardTrigger(Key.Z));
|
||||
controller.Add(new KeyCounterKeyboardTrigger(Key.X));
|
||||
controller.Add(new KeyCounterKeyboardTrigger(Key.C));
|
||||
controller.Add(new KeyCounterKeyboardTrigger(Key.V));
|
||||
|
||||
foreach (var trigger in controller.Triggers)
|
||||
Add(trigger);
|
||||
});
|
||||
base.SetUpSteps();
|
||||
}
|
||||
|
||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||
|
||||
protected override Drawable CreateDefaultImplementation() => new ArgonKeyCounterDisplay();
|
||||
|
||||
protected override Drawable CreateLegacyImplementation() => new LegacyKeyCounterDisplay();
|
||||
}
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Online.API;
|
||||
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.Menu;
|
||||
using osuTK.Input;
|
||||
@ -23,6 +27,49 @@ namespace osu.Game.Tests.Visual.Menus
|
||||
AddStep("disable return to top on idle", () => Game.ChildrenOfType<ButtonSystem>().Single().ReturnToTopOnIdle = false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDailyChallenge()
|
||||
{
|
||||
AddStep("set up API", () => ((DummyAPIAccess)API).HandleRequest = req =>
|
||||
{
|
||||
switch (req)
|
||||
{
|
||||
case GetRoomRequest getRoomRequest:
|
||||
if (getRoomRequest.RoomId != 1234)
|
||||
return false;
|
||||
|
||||
var beatmap = CreateAPIBeatmap();
|
||||
beatmap.OnlineID = 1001;
|
||||
getRoomRequest.TriggerSuccess(new Room
|
||||
{
|
||||
RoomID = { Value = 1234 },
|
||||
Name = { Value = "Aug 8, 2024" },
|
||||
Playlist =
|
||||
{
|
||||
new PlaylistItem(beatmap)
|
||||
},
|
||||
StartDate = { Value = DateTimeOffset.Now.AddMinutes(-30) },
|
||||
EndDate = { Value = DateTimeOffset.Now.AddSeconds(60) }
|
||||
});
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("beatmap of the day active", () => Game.ChildrenOfType<IMetadataClient>().Single().DailyChallengeUpdated(new DailyChallengeInfo
|
||||
{
|
||||
RoomID = 1234,
|
||||
}));
|
||||
|
||||
AddStep("enter menu", () => InputManager.Key(Key.P));
|
||||
AddStep("enter submenu", () => InputManager.Key(Key.P));
|
||||
AddStep("enter daily challenge", () => InputManager.Key(Key.D));
|
||||
|
||||
AddUntilStep("wait for daily challenge screen", () => Game.ScreenStack.CurrentScreen, Is.TypeOf<Screens.OnlinePlay.DailyChallenge.DailyChallenge>);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOnlineMenuBannerTrusted()
|
||||
{
|
||||
|
@ -61,7 +61,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("select difficulty adjust", () => freeModSelectOverlay.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() });
|
||||
AddWaitStep("wait some", 3);
|
||||
AddAssert("customisation area not expanded", () => !this.ChildrenOfType<ModCustomisationPanel>().Single().Expanded.Value);
|
||||
AddAssert("customisation area not expanded",
|
||||
() => this.ChildrenOfType<ModCustomisationPanel>().Single().ExpandedState.Value,
|
||||
() => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -139,8 +139,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
private void addRandomPlayer()
|
||||
{
|
||||
int randomUser = RNG.Next(200000, 500000);
|
||||
multiplayerClient.AddUser(new APIUser { Id = randomUser, Username = $"user {randomUser}" });
|
||||
int id = TestResources.GetNextTestID();
|
||||
multiplayerClient.AddUser(new APIUser { Id = id, Username = $"user {id}" });
|
||||
}
|
||||
|
||||
private void removeLastUser()
|
||||
|
@ -9,13 +9,13 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Chat.ChannelList;
|
||||
using osu.Game.Overlays.Chat.Listing;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
@ -160,7 +160,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private Channel createRandomPublicChannel()
|
||||
{
|
||||
int id = RNG.Next(0, 10000);
|
||||
int id = TestResources.GetNextTestID();
|
||||
return new Channel
|
||||
{
|
||||
Name = $"#channel-{id}",
|
||||
@ -171,7 +171,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private Channel createRandomPrivateChannel()
|
||||
{
|
||||
int id = RNG.Next(0, 10000);
|
||||
int id = TestResources.GetNextTestID();
|
||||
return new Channel(new APIUser
|
||||
{
|
||||
Id = id,
|
||||
@ -181,7 +181,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private Channel createRandomAnnounceChannel()
|
||||
{
|
||||
int id = RNG.Next(0, 10000);
|
||||
int id = TestResources.GetNextTestID();
|
||||
return new Channel
|
||||
{
|
||||
Name = $"Announce {id}",
|
||||
|
@ -19,7 +19,6 @@ using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
@ -33,6 +32,7 @@ using osu.Game.Overlays.Chat.ChannelList;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Tests.Resources;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
@ -122,7 +122,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
return true;
|
||||
|
||||
case PostMessageRequest postMessage:
|
||||
postMessage.TriggerSuccess(new Message(RNG.Next(0, 10000000))
|
||||
postMessage.TriggerSuccess(new Message(TestResources.GetNextTestID())
|
||||
{
|
||||
Content = postMessage.Message.Content,
|
||||
ChannelId = postMessage.Message.ChannelId,
|
||||
@ -719,7 +719,8 @@ namespace osu.Game.Tests.Visual.Online
|
||||
|
||||
private Channel createPrivateChannel()
|
||||
{
|
||||
int id = RNG.Next(0, DummyAPIAccess.DUMMY_USER_ID - 1);
|
||||
int id = TestResources.GetNextTestID();
|
||||
|
||||
return new Channel(new APIUser
|
||||
{
|
||||
Id = id,
|
||||
|
@ -33,6 +33,34 @@ namespace osu.Game.Tests.Visual.Online
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMention()
|
||||
{
|
||||
AddStep("add normal message", () => channel.AddNewMessages(
|
||||
new Message(1)
|
||||
{
|
||||
Sender = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "TestUser2"
|
||||
},
|
||||
Content = "Hello how are you today?",
|
||||
Timestamp = new DateTimeOffset(2021, 12, 11, 13, 33, 24, TimeSpan.Zero)
|
||||
}));
|
||||
|
||||
AddStep("add mention", () => channel.AddNewMessages(
|
||||
new Message(2)
|
||||
{
|
||||
Sender = new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "TestUser2"
|
||||
},
|
||||
Content = $"Hello {API.LocalUser.Value.Username} how are you today?",
|
||||
Timestamp = new DateTimeOffset(2021, 12, 11, 13, 33, 25, TimeSpan.Zero)
|
||||
}));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDaySeparators()
|
||||
{
|
||||
|
@ -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.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>(),
|
||||
|
@ -135,7 +135,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -156,7 +156,7 @@ namespace osu.Game.Tests.Visual.Playlists
|
||||
AddAssert("right loading spinner shown", () => resultsScreen.RightSpinner.State.Value == Visibility.Visible);
|
||||
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);
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -58,6 +58,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
new PlaylistItem(beatmap)
|
||||
},
|
||||
StartDate = { Value = DateTimeOffset.Now.AddMinutes(-5) },
|
||||
EndDate = { Value = DateTimeOffset.Now.AddSeconds(30) }
|
||||
});
|
||||
return true;
|
||||
@ -95,8 +96,13 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
},
|
||||
};
|
||||
});
|
||||
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
|
||||
AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
|
||||
|
||||
AddStep("clear notifications", () =>
|
||||
{
|
||||
foreach (var notification in notificationOverlay.AllNotifications)
|
||||
notification.Close(runFlingAnimation: false);
|
||||
});
|
||||
AddStep("beatmap of the day not active", () => metadataClient.DailyChallengeUpdated(null));
|
||||
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
|
||||
|
||||
@ -105,7 +111,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
RoomID = 1234,
|
||||
}));
|
||||
AddAssert("notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.EqualTo(1));
|
||||
AddAssert("no notification posted", () => notificationOverlay.AllNotifications.Count(), () => Is.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,8 @@ using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osuTK;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
@ -19,10 +22,15 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
private ModCustomisationPanel panel = null!;
|
||||
private ModCustomisationHeader header = null!;
|
||||
private Container content = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
SelectedMods.Value = Array.Empty<Mod>();
|
||||
InputManager.MoveMouseTo(Vector2.One);
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
@ -36,6 +44,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
SelectedMods = { BindTarget = SelectedMods },
|
||||
}
|
||||
};
|
||||
|
||||
header = panel.Children.OfType<ModCustomisationHeader>().First();
|
||||
content = panel.Children.OfType<Container>().First();
|
||||
});
|
||||
|
||||
[Test]
|
||||
@ -44,23 +55,112 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("set DT", () =>
|
||||
{
|
||||
SelectedMods.Value = new[] { new OsuModDoubleTime() };
|
||||
panel.Enabled.Value = panel.Expanded.Value = true;
|
||||
panel.Enabled.Value = true;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded;
|
||||
});
|
||||
AddStep("set DA", () =>
|
||||
{
|
||||
SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() };
|
||||
panel.Enabled.Value = panel.Expanded.Value = true;
|
||||
panel.Enabled.Value = true;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded;
|
||||
});
|
||||
AddStep("set FL+WU+DA+AD", () =>
|
||||
{
|
||||
SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() };
|
||||
panel.Enabled.Value = panel.Expanded.Value = true;
|
||||
panel.Enabled.Value = true;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded;
|
||||
});
|
||||
AddStep("set empty", () =>
|
||||
{
|
||||
SelectedMods.Value = Array.Empty<Mod>();
|
||||
panel.Enabled.Value = panel.Expanded.Value = false;
|
||||
panel.Enabled.Value = false;
|
||||
panel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Collapsed;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoverDoesNotExpandWhenNoCustomisableMods()
|
||||
{
|
||||
AddStep("hover header", () => InputManager.MoveMouseTo(header));
|
||||
|
||||
checkExpanded(false);
|
||||
|
||||
AddStep("hover content", () => InputManager.MoveMouseTo(content));
|
||||
|
||||
checkExpanded(false);
|
||||
|
||||
AddStep("left from content", () => InputManager.MoveMouseTo(Vector2.One));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoverExpandsWithCustomisableMods()
|
||||
{
|
||||
AddStep("add customisable mod", () =>
|
||||
{
|
||||
SelectedMods.Value = new[] { new OsuModDoubleTime() };
|
||||
panel.Enabled.Value = true;
|
||||
});
|
||||
|
||||
AddStep("hover header", () => InputManager.MoveMouseTo(header));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("move to content", () => InputManager.MoveMouseTo(content));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One));
|
||||
checkExpanded(false);
|
||||
|
||||
AddStep("hover header", () => InputManager.MoveMouseTo(header));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One));
|
||||
checkExpanded(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestExpandedStatePersistsWhenClicked()
|
||||
{
|
||||
AddStep("add customisable mod", () =>
|
||||
{
|
||||
SelectedMods.Value = new[] { new OsuModDoubleTime() };
|
||||
panel.Enabled.Value = true;
|
||||
});
|
||||
|
||||
AddStep("hover header", () => InputManager.MoveMouseTo(header));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
checkExpanded(false);
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("move away", () => InputManager.MoveMouseTo(Vector2.One));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
checkExpanded(false);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestHoverExpandsAndCollapsesWhenHeaderClicked()
|
||||
{
|
||||
AddStep("add customisable mod", () =>
|
||||
{
|
||||
SelectedMods.Value = new[] { new OsuModDoubleTime() };
|
||||
panel.Enabled.Value = true;
|
||||
});
|
||||
|
||||
AddStep("hover header", () => InputManager.MoveMouseTo(header));
|
||||
checkExpanded(true);
|
||||
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
checkExpanded(false);
|
||||
}
|
||||
|
||||
private void checkExpanded(bool expanded)
|
||||
{
|
||||
AddUntilStep(expanded ? "is expanded" : "not expanded", () => panel.ExpandedState.Value,
|
||||
() => expanded ? Is.Not.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed) : Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,6 +57,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("reset ruleset", () => Ruleset.Value = rulesetStore.GetRuleset(0));
|
||||
AddStep("reset mods", () => SelectedMods.SetDefault());
|
||||
AddStep("reset config", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true));
|
||||
AddStep("reset mouse", () => InputManager.MoveMouseTo(Vector2.One));
|
||||
AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo));
|
||||
AddStep("set up presets", () =>
|
||||
{
|
||||
@ -998,7 +999,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("press mouse", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddAssert("search still not focused", () => !this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
||||
AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
AddAssert("customisation panel closed by click", () => !this.ChildrenOfType<ModCustomisationPanel>().Single().Expanded.Value);
|
||||
AddAssert("customisation panel closed by click",
|
||||
() => this.ChildrenOfType<ModCustomisationPanel>().Single().ExpandedState.Value,
|
||||
() => Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
|
||||
|
||||
if (textSearchStartsActive)
|
||||
AddAssert("search focused", () => this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
||||
@ -1021,7 +1024,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private void assertCustomisationToggleState(bool disabled, bool active)
|
||||
{
|
||||
AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().Enabled.Value == !disabled);
|
||||
AddAssert($"customisation panel is {(active ? "" : "not ")}active", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().Expanded.Value == active);
|
||||
AddAssert($"customisation panel is {(active ? "" : "not ")}active",
|
||||
() => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().ExpandedState.Value,
|
||||
() => active ? Is.Not.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed) : Is.EqualTo(ModCustomisationPanel.ModCustomisationPanelState.Collapsed));
|
||||
}
|
||||
|
||||
private T getSelectedMod<T>() where T : Mod => SelectedMods.Value.OfType<T>().Single();
|
||||
|
86
osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs
Normal file
86
osu.Game.Tests/Visual/UserInterface/TestSceneOsuTooltip.cs
Normal file
@ -0,0 +1,86 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneOsuTooltip : OsuManualInputManagerTestScene
|
||||
{
|
||||
private TestTooltipContainer container = null!;
|
||||
|
||||
private static readonly string[] test_case_tooltip_string =
|
||||
[
|
||||
"Hello!!",
|
||||
string.Concat(Enumerable.Repeat("Hello ", 100)),
|
||||
|
||||
//TODO: o!f issue: https://github.com/ppy/osu-framework/issues/5007
|
||||
//Enable after o!f fixed
|
||||
// $"H{new string('e', 500)}llo",
|
||||
];
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(100),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = Colour4.Red.Opacity(0.5f),
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
container = new TestTooltipContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new OsuSpriteText
|
||||
{
|
||||
Text = "Hover me!",
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(size: 50)
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
[TestCaseSource(nameof(test_case_tooltip_string))]
|
||||
public void TestTooltipBasic(string text)
|
||||
{
|
||||
AddStep("Set tooltip content", () => container.TooltipText = text);
|
||||
|
||||
AddStep("Move mouse to container", () => InputManager.MoveMouseTo(new Vector2(InputManager.ScreenSpaceDrawQuad.Centre.X, InputManager.ScreenSpaceDrawQuad.Centre.Y)));
|
||||
|
||||
OsuTooltipContainer.OsuTooltip? tooltip = null!;
|
||||
|
||||
AddUntilStep("Wait for the tooltip shown", () =>
|
||||
{
|
||||
tooltip = container.FindClosestParent<OsuTooltipContainer>().ChildrenOfType<OsuTooltipContainer.OsuTooltip>().FirstOrDefault();
|
||||
return tooltip != null && tooltip.Alpha == 1;
|
||||
});
|
||||
|
||||
AddAssert("Check tooltip is under width limit", () => tooltip != null && tooltip.Width <= 500);
|
||||
}
|
||||
|
||||
internal sealed partial class TestTooltipContainer : Container, IHasTooltip
|
||||
{
|
||||
public LocalisableString TooltipText { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -563,7 +563,7 @@ namespace osu.Game.Beatmaps
|
||||
remove => workingBeatmapCache.OnInvalidated -= value;
|
||||
}
|
||||
|
||||
public override bool IsAvailableLocally(BeatmapSetInfo model) => Realm.Run(realm => realm.All<BeatmapSetInfo>().Any(s => s.OnlineID == model.OnlineID));
|
||||
public override bool IsAvailableLocally(BeatmapSetInfo model) => Realm.Run(realm => realm.All<BeatmapSetInfo>().Any(s => s.OnlineID == model.OnlineID && !s.DeletePending));
|
||||
|
||||
#endregion
|
||||
|
||||
|
@ -18,6 +18,12 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
public const int LATEST_VERSION = 14;
|
||||
|
||||
/// <summary>
|
||||
/// The .osu format (beatmap) version.
|
||||
///
|
||||
/// osu!stable's versions end at <see cref="LATEST_VERSION"/>.
|
||||
/// osu!lazer's versions starts at <see cref="LegacyBeatmapEncoder.FIRST_LAZER_VERSION"/>.
|
||||
/// </summary>
|
||||
protected readonly int FormatVersion;
|
||||
|
||||
protected LegacyDecoder(int version)
|
||||
@ -93,14 +99,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
return line;
|
||||
}
|
||||
|
||||
protected void HandleColours<TModel>(TModel output, string line, bool allowAlpha)
|
||||
private Color4 convertSettingStringToColor4(string[] split, bool allowAlpha, KeyValuePair<string, string> pair)
|
||||
{
|
||||
var pair = SplitKeyVal(line);
|
||||
|
||||
bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal);
|
||||
|
||||
string[] split = pair.Value.Split(',');
|
||||
|
||||
if (split.Length != 3 && split.Length != 4)
|
||||
throw new InvalidOperationException($@"Color specified in incorrect format (should be R,G,B or R,G,B,A): {pair.Value}");
|
||||
|
||||
@ -116,6 +116,18 @@ namespace osu.Game.Beatmaps.Formats
|
||||
throw new InvalidOperationException(@"Color must be specified with 8-bit integer components");
|
||||
}
|
||||
|
||||
return colour;
|
||||
}
|
||||
|
||||
protected void HandleColours<TModel>(TModel output, string line, bool allowAlpha)
|
||||
{
|
||||
var pair = SplitKeyVal(line);
|
||||
|
||||
string[] split = pair.Value.Split(',');
|
||||
Color4 colour = convertSettingStringToColor4(split, allowAlpha, pair);
|
||||
|
||||
bool isCombo = pair.Key.StartsWith(@"Combo", StringComparison.Ordinal);
|
||||
|
||||
if (isCombo)
|
||||
{
|
||||
if (!(output is IHasComboColours tHasComboColours)) return;
|
||||
|
@ -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));
|
||||
|
@ -67,13 +67,7 @@ namespace osu.Game.Configuration
|
||||
SetDefault(OsuSetting.Username, string.Empty);
|
||||
SetDefault(OsuSetting.Token, string.Empty);
|
||||
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
// this default set MUST remain despite the setting being deprecated, because `SetDefault()` calls are implicitly used to declare the type returned for the lookup.
|
||||
// if this is removed, the setting will be interpreted as a string, and `Migrate()` will fail due to cast failure.
|
||||
// can be removed 20240618
|
||||
SetDefault(OsuSetting.AutomaticallyDownloadWhenSpectating, false);
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
SetDefault(OsuSetting.AutomaticallyDownloadMissingBeatmaps, false);
|
||||
SetDefault(OsuSetting.AutomaticallyDownloadMissingBeatmaps, true);
|
||||
|
||||
SetDefault(OsuSetting.SavePassword, true).ValueChanged += enabled =>
|
||||
{
|
||||
@ -244,12 +238,6 @@ namespace osu.Game.Configuration
|
||||
|
||||
// migrations can be added here using a condition like:
|
||||
// if (combined < 20220103) { performMigration() }
|
||||
if (combined < 20230918)
|
||||
{
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
SetValue(OsuSetting.AutomaticallyDownloadMissingBeatmaps, Get<bool>(OsuSetting.AutomaticallyDownloadWhenSpectating)); // can be removed 20240618
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
}
|
||||
}
|
||||
|
||||
public override TrackedSettings CreateTrackedSettings()
|
||||
@ -424,9 +412,6 @@ namespace osu.Game.Configuration
|
||||
EditorAutoSeekOnPlacement,
|
||||
DiscordRichPresence,
|
||||
|
||||
[Obsolete($"Use {nameof(AutomaticallyDownloadMissingBeatmaps)} instead.")] // can be removed 20240318
|
||||
AutomaticallyDownloadWhenSpectating,
|
||||
|
||||
ShowOnlineExplicitContent,
|
||||
LastProcessedMetadataId,
|
||||
SafeAreaConsiderations,
|
||||
|
@ -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()
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -8,16 +8,24 @@ using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Graphics.Cursor
|
||||
{
|
||||
[Cached(typeof(OsuContextMenuContainer))]
|
||||
public partial class OsuContextMenuContainer : ContextMenuContainer
|
||||
{
|
||||
[Cached]
|
||||
private OsuContextMenuSamples samples = new OsuContextMenuSamples();
|
||||
|
||||
private OsuContextMenu menu = null!;
|
||||
|
||||
public OsuContextMenuContainer()
|
||||
{
|
||||
AddInternal(samples);
|
||||
}
|
||||
|
||||
protected override Menu CreateMenu() => new OsuContextMenu(true);
|
||||
protected override Menu CreateMenu() => menu = new OsuContextMenu(true);
|
||||
|
||||
public void CloseMenu()
|
||||
{
|
||||
menu.Close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Graphics.Cursor
|
||||
{
|
||||
@ -27,15 +27,20 @@ namespace osu.Game.Graphics.Cursor
|
||||
|
||||
public partial class OsuTooltip : Tooltip
|
||||
{
|
||||
private const float max_width = 500;
|
||||
|
||||
private readonly Box background;
|
||||
private readonly OsuSpriteText text;
|
||||
private readonly TextFlowContainer text;
|
||||
private bool instantMovement = true;
|
||||
|
||||
public override void SetContent(LocalisableString contentString)
|
||||
{
|
||||
if (contentString == text.Text) return;
|
||||
private LocalisableString lastContent;
|
||||
|
||||
text.Text = contentString;
|
||||
public override void SetContent(LocalisableString content)
|
||||
{
|
||||
if (content.Equals(lastContent))
|
||||
return;
|
||||
|
||||
text.Text = content;
|
||||
|
||||
if (IsPresent)
|
||||
{
|
||||
@ -44,6 +49,8 @@ namespace osu.Game.Graphics.Cursor
|
||||
}
|
||||
else
|
||||
AutoSizeDuration = 0;
|
||||
|
||||
lastContent = content;
|
||||
}
|
||||
|
||||
public OsuTooltip()
|
||||
@ -65,10 +72,14 @@ namespace osu.Game.Graphics.Cursor
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.9f,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
text = new TextFlowContainer(f =>
|
||||
{
|
||||
Padding = new MarginPadding(5),
|
||||
Font = OsuFont.GetFont(weight: FontWeight.Regular)
|
||||
f.Font = OsuFont.GetFont(weight: FontWeight.Regular);
|
||||
})
|
||||
{
|
||||
Margin = new MarginPadding(5),
|
||||
AutoSizeAxes = Axes.Both,
|
||||
MaximumSize = new Vector2(max_width, float.PositiveInfinity),
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -24,6 +24,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString MentionUser => new TranslatableString(getKey(@"mention_user"), @"Mention");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
/// <summary>
|
||||
/// "press enter to chat..."
|
||||
/// </summary>
|
||||
public static LocalisableString InGameInputPlaceholder => new TranslatableString(getKey(@"in_game_input_placeholder"), @"press enter to chat...");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -19,6 +19,16 @@ namespace osu.Game.Localisation.HUD
|
||||
/// </summary>
|
||||
public static LocalisableString ShowGraphDescription => new TranslatableString(getKey(@"show_graph_description"), "Whether a graph displaying difficulty throughout the beatmap should be shown");
|
||||
|
||||
/// <summary>
|
||||
/// "Show time"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowTime => new TranslatableString(getKey(@"show_time"), "Show time");
|
||||
|
||||
/// <summary>
|
||||
/// "Whether the passed and remaining time should be shown"
|
||||
/// </summary>
|
||||
public static LocalisableString ShowTimeDescription => new TranslatableString(getKey(@"show_time_description"), "Whether the passed and remaining time should be shown");
|
||||
|
||||
private static string getKey(string key) => $"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
@ -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>
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -128,6 +128,9 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
public partial class ChatTextBox : HistoryTextBox
|
||||
{
|
||||
public Action Focus;
|
||||
public Action FocusLost;
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
// Chat text boxes are generally used in places where they retain focus, but shouldn't block interaction with other
|
||||
@ -153,13 +156,17 @@ namespace osu.Game.Online.Chat
|
||||
BackgroundFocused = new Color4(10, 10, 10, 255);
|
||||
}
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
base.OnFocus(e);
|
||||
Focus?.Invoke();
|
||||
}
|
||||
|
||||
protected override void OnFocusLost(FocusLostEvent e)
|
||||
{
|
||||
base.OnFocusLost(e);
|
||||
FocusLost?.Invoke();
|
||||
}
|
||||
|
||||
public Action FocusLost;
|
||||
}
|
||||
|
||||
public partial class StandAloneDrawableChannel : DrawableChannel
|
||||
|
@ -782,7 +782,21 @@ namespace osu.Game.Online.Multiplayer
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new AggregateException($"Item: {JsonConvert.SerializeObject(createPlaylistItem(item))}\n\nRoom:{JsonConvert.SerializeObject(APIRoom)}", ex);
|
||||
// Temporary code to attempt to figure out long-term failing tests.
|
||||
bool success = true;
|
||||
int indexOf = -1234;
|
||||
|
||||
try
|
||||
{
|
||||
indexOf = Room.Playlist.IndexOf(Room.Playlist.Single(existing => existing.ID == item.ID));
|
||||
Room.Playlist[indexOf] = item;
|
||||
}
|
||||
catch
|
||||
{
|
||||
success = false;
|
||||
}
|
||||
|
||||
throw new AggregateException($"Index: {indexOf} Length: {Room.Playlist.Count} Retry success: {success} Item: {JsonConvert.SerializeObject(createPlaylistItem(item))}\n\nRoom:{JsonConvert.SerializeObject(APIRoom)}", ex);
|
||||
}
|
||||
|
||||
ItemChanged?.Invoke(item);
|
||||
|
@ -642,10 +642,10 @@ namespace osu.Game
|
||||
Live<BeatmapSetInfo> databasedSet = null;
|
||||
|
||||
if (beatmap.OnlineID > 0)
|
||||
databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID);
|
||||
databasedSet = BeatmapManager.QueryBeatmapSet(s => s.OnlineID == beatmap.OnlineID && !s.DeletePending);
|
||||
|
||||
if (beatmap is BeatmapSetInfo localBeatmap)
|
||||
databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash);
|
||||
databasedSet ??= BeatmapManager.QueryBeatmapSet(s => s.Hash == localBeatmap.Hash && !s.DeletePending);
|
||||
|
||||
if (databasedSet == null)
|
||||
{
|
||||
|
@ -18,6 +18,7 @@ using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osu.Game.Online.Chat;
|
||||
using osuTK;
|
||||
@ -104,6 +105,8 @@ namespace osu.Game.Overlays.Chat
|
||||
}
|
||||
}
|
||||
|
||||
private bool isMention;
|
||||
|
||||
/// <summary>
|
||||
/// The colour used to paint the author's username.
|
||||
/// </summary>
|
||||
@ -255,12 +258,21 @@ namespace osu.Game.Overlays.Chat
|
||||
private void styleMessageContent(SpriteText text)
|
||||
{
|
||||
text.Shadow = false;
|
||||
text.Font = text.Font.With(size: FontSize, italics: Message.IsAction);
|
||||
text.Font = text.Font.With(size: FontSize, italics: Message.IsAction, weight: isMention ? FontWeight.SemiBold : FontWeight.Medium);
|
||||
|
||||
bool messageHasColour = Message.IsAction && !string.IsNullOrEmpty(message.Sender.Colour);
|
||||
text.Colour = messageHasColour ? Color4Extensions.FromHex(message.Sender.Colour) : colourProvider?.Content1 ?? Colour4.White;
|
||||
Color4 messageColour = colourProvider?.Content1 ?? Colour4.White;
|
||||
|
||||
if (isMention)
|
||||
messageColour = colourProvider?.Highlight1 ?? Color4.Orange;
|
||||
else if (Message.IsAction && !string.IsNullOrEmpty(message.Sender.Colour))
|
||||
messageColour = Color4Extensions.FromHex(message.Sender.Colour);
|
||||
|
||||
text.Colour = messageColour;
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; } = null!;
|
||||
|
||||
private void updateMessageContent()
|
||||
{
|
||||
this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint);
|
||||
@ -280,6 +292,8 @@ namespace osu.Game.Overlays.Chat
|
||||
// remove non-existent channels from the link list
|
||||
message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument.ToString()) != true);
|
||||
|
||||
isMention = MessageNotifier.CheckContainsUsername(message.DisplayContent, api.LocalUser.Value.Username);
|
||||
|
||||
drawableContentFlow.Clear();
|
||||
drawableContentFlow.AddLinks(message.DisplayContent, message.Links);
|
||||
}
|
||||
|
@ -9,12 +9,14 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Localisation;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using static osu.Game.Overlays.Mods.ModCustomisationPanel;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
@ -29,11 +31,13 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
protected override IEnumerable<Drawable> EffectTargets => new[] { background };
|
||||
|
||||
public readonly BindableBool Expanded = new BindableBool();
|
||||
public readonly Bindable<ModCustomisationPanelState> ExpandedState = new Bindable<ModCustomisationPanelState>(ModCustomisationPanelState.Collapsed);
|
||||
|
||||
public ModCustomisationHeader()
|
||||
private readonly ModCustomisationPanel panel;
|
||||
|
||||
public ModCustomisationHeader(ModCustomisationPanel panel)
|
||||
{
|
||||
Action = Expanded.Toggle;
|
||||
this.panel = panel;
|
||||
Enabled.Value = false;
|
||||
}
|
||||
|
||||
@ -54,6 +58,7 @@ namespace osu.Game.Overlays.Mods
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.White.Opacity(0.4f),
|
||||
Blending = BlendingParameters.Additive,
|
||||
Alpha = 0,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
@ -101,10 +106,48 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
}, true);
|
||||
|
||||
Expanded.BindValueChanged(v =>
|
||||
ExpandedState.BindValueChanged(v =>
|
||||
{
|
||||
icon.ScaleTo(v.NewValue ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint);
|
||||
icon.ScaleTo(v.NewValue > ModCustomisationPanelState.Collapsed ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint);
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (Enabled.Value)
|
||||
{
|
||||
ExpandedState.Value = ExpandedState.Value switch
|
||||
{
|
||||
ModCustomisationPanelState.Collapsed => ModCustomisationPanelState.Expanded,
|
||||
_ => ModCustomisationPanelState.Collapsed
|
||||
};
|
||||
}
|
||||
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
private bool touchedThisFrame;
|
||||
|
||||
protected override bool OnTouchDown(TouchDownEvent e)
|
||||
{
|
||||
if (Enabled.Value)
|
||||
{
|
||||
touchedThisFrame = true;
|
||||
Schedule(() => touchedThisFrame = false);
|
||||
}
|
||||
|
||||
return base.OnTouchDown(e);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
if (Enabled.Value)
|
||||
{
|
||||
if (!touchedThisFrame && panel.ExpandedState.Value == ModCustomisationPanelState.Collapsed)
|
||||
panel.ExpandedState.Value = ModCustomisationPanelState.ExpandedByHover;
|
||||
}
|
||||
|
||||
return base.OnHover(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
public readonly BindableBool Enabled = new BindableBool();
|
||||
|
||||
public readonly BindableBool Expanded = new BindableBool();
|
||||
public readonly Bindable<ModCustomisationPanelState> ExpandedState = new Bindable<ModCustomisationPanelState>();
|
||||
|
||||
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
|
||||
@ -46,9 +46,9 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
// Handle{Non}PositionalInput controls whether the panel should act as a blocking layer on the screen. only block when the panel is expanded.
|
||||
// These properties are used because they correctly handle blocking/unblocking hover when mouse is pointing at a drawable outside
|
||||
// (returning Expanded.Value to OnHover or overriding Block{Non}PositionalInput doesn't work).
|
||||
public override bool HandlePositionalInput => Expanded.Value;
|
||||
public override bool HandleNonPositionalInput => Expanded.Value;
|
||||
// (handling OnHover or overriding Block{Non}PositionalInput doesn't work).
|
||||
public override bool HandlePositionalInput => ExpandedState.Value != ModCustomisationPanelState.Collapsed;
|
||||
public override bool HandleNonPositionalInput => ExpandedState.Value != ModCustomisationPanelState.Collapsed;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -57,15 +57,15 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new ModCustomisationHeader
|
||||
new ModCustomisationHeader(this)
|
||||
{
|
||||
Depth = float.MinValue,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = header_height,
|
||||
Enabled = { BindTarget = Enabled },
|
||||
Expanded = { BindTarget = Expanded },
|
||||
ExpandedState = { BindTarget = ExpandedState },
|
||||
},
|
||||
content = new FocusGrabbingContainer
|
||||
content = new FocusGrabbingContainer(this)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
BorderColour = colourProvider.Dark3,
|
||||
@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Mods
|
||||
Roundness = 5f,
|
||||
Colour = Color4.Black.Opacity(0.25f),
|
||||
},
|
||||
Expanded = { BindTarget = Expanded },
|
||||
ExpandedState = { BindTarget = ExpandedState },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
@ -122,7 +122,7 @@ namespace osu.Game.Overlays.Mods
|
||||
this.FadeColour(OsuColour.Gray(e.NewValue ? 1f : 0.6f), 300, Easing.OutQuint);
|
||||
}, true);
|
||||
|
||||
Expanded.BindValueChanged(_ => updateDisplay(), true);
|
||||
ExpandedState.BindValueChanged(_ => updateDisplay(), true);
|
||||
SelectedMods.BindValueChanged(_ => updateMods(), true);
|
||||
|
||||
FinishTransforms(true);
|
||||
@ -134,7 +134,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
Expanded.Value = false;
|
||||
ExpandedState.Value = ModCustomisationPanelState.Collapsed;
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
@ -147,7 +147,7 @@ namespace osu.Game.Overlays.Mods
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.Back:
|
||||
Expanded.Value = false;
|
||||
ExpandedState.Value = ModCustomisationPanelState.Collapsed;
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -162,7 +162,7 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
content.ClearTransforms();
|
||||
|
||||
if (Expanded.Value)
|
||||
if (ExpandedState.Value != ModCustomisationPanelState.Collapsed)
|
||||
{
|
||||
content.AutoSizeDuration = 400;
|
||||
content.AutoSizeEasing = Easing.OutQuint;
|
||||
@ -179,7 +179,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private void updateMods()
|
||||
{
|
||||
Expanded.Value = false;
|
||||
ExpandedState.Value = ModCustomisationPanelState.Collapsed;
|
||||
sectionsFlow.Clear();
|
||||
|
||||
// Importantly, the selected mods bindable is already ordered by the mod select overlay (following the order of mod columns and panels).
|
||||
@ -202,10 +202,35 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private partial class FocusGrabbingContainer : InputBlockingContainer
|
||||
{
|
||||
public IBindable<bool> Expanded { get; } = new BindableBool();
|
||||
public readonly Bindable<ModCustomisationPanelState> ExpandedState = new Bindable<ModCustomisationPanelState>();
|
||||
|
||||
public override bool RequestsFocus => Expanded.Value;
|
||||
public override bool AcceptsFocus => Expanded.Value;
|
||||
public override bool RequestsFocus => panel.ExpandedState.Value != ModCustomisationPanelState.Collapsed;
|
||||
public override bool AcceptsFocus => panel.ExpandedState.Value != ModCustomisationPanelState.Collapsed;
|
||||
|
||||
private readonly ModCustomisationPanel panel;
|
||||
|
||||
public FocusGrabbingContainer(ModCustomisationPanel panel)
|
||||
{
|
||||
this.panel = panel;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
if (ExpandedState.Value is ModCustomisationPanelState.ExpandedByHover
|
||||
&& !ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
|
||||
{
|
||||
ExpandedState.Value = ModCustomisationPanelState.Collapsed;
|
||||
}
|
||||
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
}
|
||||
|
||||
public enum ModCustomisationPanelState
|
||||
{
|
||||
Collapsed = 0,
|
||||
ExpandedByHover = 1,
|
||||
Expanded = 2,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,6 +138,7 @@ namespace osu.Game.Overlays.Mods
|
||||
},
|
||||
new GridContainer
|
||||
{
|
||||
Padding = new MarginPadding { Top = 1, Bottom = 3 },
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
|
@ -237,7 +237,7 @@ namespace osu.Game.Overlays.Mods
|
||||
ActiveMods.Value = ComputeActiveMods();
|
||||
}, true);
|
||||
|
||||
customisationPanel.Expanded.BindValueChanged(_ => updateCustomisationVisualState(), true);
|
||||
customisationPanel.ExpandedState.BindValueChanged(_ => updateCustomisationVisualState(), true);
|
||||
|
||||
SearchTextBox.Current.BindValueChanged(query =>
|
||||
{
|
||||
@ -368,18 +368,18 @@ namespace osu.Game.Overlays.Mods
|
||||
customisationPanel.Enabled.Value = true;
|
||||
|
||||
if (anyModPendingConfiguration)
|
||||
customisationPanel.Expanded.Value = true;
|
||||
customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Expanded;
|
||||
}
|
||||
else
|
||||
{
|
||||
customisationPanel.Expanded.Value = false;
|
||||
customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Collapsed;
|
||||
customisationPanel.Enabled.Value = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCustomisationVisualState()
|
||||
{
|
||||
if (customisationPanel.Expanded.Value)
|
||||
if (customisationPanel.ExpandedState.Value != ModCustomisationPanel.ModCustomisationPanelState.Collapsed)
|
||||
{
|
||||
columnScroll.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
|
||||
SearchTextBox.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
|
||||
@ -544,7 +544,7 @@ namespace osu.Game.Overlays.Mods
|
||||
nonFilteredColumnCount += 1;
|
||||
}
|
||||
|
||||
customisationPanel.Expanded.Value = false;
|
||||
customisationPanel.ExpandedState.Value = ModCustomisationPanel.ModCustomisationPanelState.Collapsed;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -571,7 +571,7 @@ namespace osu.Game.Overlays.Mods
|
||||
// wherein activating the binding will both change the contents of the search text box and deselect all mods.
|
||||
case GlobalAction.DeselectAllMods:
|
||||
{
|
||||
if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value)
|
||||
if (!SearchTextBox.HasFocus && customisationPanel.ExpandedState.Value == ModCustomisationPanel.ModCustomisationPanelState.Collapsed)
|
||||
{
|
||||
DisplayedFooterContent?.DeselectAllModsButton?.TriggerClick();
|
||||
return true;
|
||||
@ -637,7 +637,7 @@ namespace osu.Game.Overlays.Mods
|
||||
if (e.Repeat || e.Key != Key.Tab)
|
||||
return false;
|
||||
|
||||
if (customisationPanel.Expanded.Value)
|
||||
if (customisationPanel.ExpandedState.Value != ModCustomisationPanel.ModCustomisationPanelState.Collapsed)
|
||||
return true;
|
||||
|
||||
// TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`)
|
||||
@ -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)
|
||||
{
|
||||
|
@ -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),
|
||||
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[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.Absolute, 20),
|
||||
new Dimension(),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new[]
|
||||
{
|
||||
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
|
||||
|
@ -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>
|
||||
|
54
osu.Game/Overlays/Volume/MasterVolumeMeter.cs
Normal file
54
osu.Game/Overlays/Volume/MasterVolumeMeter.cs
Normal file
@ -0,0 +1,54 @@
|
||||
// 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.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
public partial class MasterVolumeMeter : VolumeMeter
|
||||
{
|
||||
private MuteButton muteButton = null!;
|
||||
|
||||
public Bindable<bool> IsMuted { get; } = new Bindable<bool>();
|
||||
|
||||
private readonly BindableDouble muteAdjustment = new BindableDouble();
|
||||
|
||||
[Resolved]
|
||||
private VolumeOverlay volumeOverlay { get; set; } = null!;
|
||||
|
||||
public MasterVolumeMeter(string name, float circleSize, Color4 meterColour)
|
||||
: base(name, circleSize, meterColour)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
IsMuted.BindValueChanged(muted =>
|
||||
{
|
||||
if (muted.NewValue)
|
||||
audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment);
|
||||
else
|
||||
audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment);
|
||||
});
|
||||
|
||||
Add(muteButton = new MuteButton
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.Centre,
|
||||
Blending = BlendingParameters.Additive,
|
||||
X = CircleSize / 2,
|
||||
Y = CircleSize * 0.23f,
|
||||
Current = { BindTarget = IsMuted }
|
||||
});
|
||||
|
||||
muteButton.Current.ValueChanged += _ => volumeOverlay.Show();
|
||||
}
|
||||
|
||||
public void ToggleMute() => muteButton.Current.Value = !muteButton.Current.Value;
|
||||
}
|
||||
}
|
@ -7,13 +7,13 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
@ -33,18 +33,18 @@ namespace osu.Game.Overlays.Volume
|
||||
}
|
||||
}
|
||||
|
||||
private Color4 hoveredColour, unhoveredColour;
|
||||
|
||||
private const float width = 100;
|
||||
public const float HEIGHT = 35;
|
||||
private ColourInfo hoveredBorderColour;
|
||||
private ColourInfo unhoveredBorderColour;
|
||||
private CompositeDrawable border = null!;
|
||||
|
||||
public MuteButton()
|
||||
{
|
||||
Content.BorderThickness = 3;
|
||||
Content.CornerRadius = HEIGHT / 2;
|
||||
Content.CornerExponent = 2;
|
||||
const float width = 30;
|
||||
const float height = 30;
|
||||
|
||||
Size = new Vector2(width, HEIGHT);
|
||||
Size = new Vector2(width, height);
|
||||
Content.CornerRadius = height / 2;
|
||||
Content.CornerExponent = 2;
|
||||
|
||||
Action = () => Current.Value = !Current.Value;
|
||||
}
|
||||
@ -52,10 +52,9 @@ namespace osu.Game.Overlays.Volume
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
hoveredColour = colours.YellowDark;
|
||||
|
||||
Content.BorderColour = unhoveredColour = colours.Gray1;
|
||||
BackgroundColour = colours.Gray1;
|
||||
hoveredBorderColour = colours.PinkLight;
|
||||
unhoveredBorderColour = colours.Gray1;
|
||||
|
||||
SpriteIcon icon;
|
||||
|
||||
@ -65,26 +64,39 @@ namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
},
|
||||
border = new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
BorderThickness = 3,
|
||||
BorderColour = unhoveredBorderColour,
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
AlwaysPresent = true
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Current.BindValueChanged(muted =>
|
||||
{
|
||||
icon.Icon = muted.NewValue ? FontAwesome.Solid.VolumeMute : FontAwesome.Solid.VolumeUp;
|
||||
icon.Size = new Vector2(muted.NewValue ? 18 : 20);
|
||||
icon.Size = new Vector2(muted.NewValue ? 12 : 16);
|
||||
icon.Margin = new MarginPadding { Right = muted.NewValue ? 2 : 0 };
|
||||
}, true);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
Content.TransformTo<Container<Drawable>, ColourInfo>("BorderColour", hoveredColour, 500, Easing.OutQuint);
|
||||
border.TransformTo(nameof(BorderColour), hoveredBorderColour, 500, Easing.OutQuint);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
Content.TransformTo<Container<Drawable>, ColourInfo>("BorderColour", unhoveredColour, 500, Easing.OutQuint);
|
||||
border.TransformTo(nameof(BorderColour), unhoveredBorderColour, 500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
|
@ -35,8 +35,12 @@ namespace osu.Game.Overlays.Volume
|
||||
private CircularProgress volumeCircle;
|
||||
private CircularProgress volumeCircleGlow;
|
||||
|
||||
protected static readonly Vector2 LABEL_SIZE = new Vector2(120, 20);
|
||||
|
||||
public BindableDouble Bindable { get; } = new BindableDouble { MinValue = 0, MaxValue = 1, Precision = 0.01 };
|
||||
private readonly float circleSize;
|
||||
|
||||
protected readonly float CircleSize;
|
||||
|
||||
private readonly Color4 meterColour;
|
||||
private readonly string name;
|
||||
|
||||
@ -73,7 +77,7 @@ namespace osu.Game.Overlays.Volume
|
||||
|
||||
public VolumeMeter(string name, float circleSize, Color4 meterColour)
|
||||
{
|
||||
this.circleSize = circleSize;
|
||||
CircleSize = circleSize;
|
||||
this.meterColour = meterColour;
|
||||
this.name = name;
|
||||
|
||||
@ -101,7 +105,7 @@ namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
new Container
|
||||
{
|
||||
Size = new Vector2(circleSize),
|
||||
Size = new Vector2(CircleSize),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BufferedContainer
|
||||
@ -199,7 +203,7 @@ namespace osu.Game.Overlays.Volume
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.Numeric.With(size: 0.16f * circleSize)
|
||||
Font = OsuFont.Numeric.With(size: 0.16f * CircleSize)
|
||||
}).WithEffect(new GlowEffect
|
||||
{
|
||||
Colour = Color4.Transparent,
|
||||
@ -209,10 +213,10 @@ namespace osu.Game.Overlays.Volume
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Size = new Vector2(120, 20),
|
||||
Size = LABEL_SIZE,
|
||||
CornerRadius = 10,
|
||||
Masking = true,
|
||||
Margin = new MarginPadding { Left = circleSize + 10 },
|
||||
Margin = new MarginPadding { Left = CircleSize + 10 },
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Children = new Drawable[]
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
@ -20,21 +21,19 @@ using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
[Cached]
|
||||
public partial class VolumeOverlay : VisibilityContainer
|
||||
{
|
||||
public Bindable<bool> IsMuted { get; } = new Bindable<bool>();
|
||||
|
||||
private const float offset = 10;
|
||||
|
||||
private VolumeMeter volumeMeterMaster = null!;
|
||||
private VolumeMeter volumeMeterEffect = null!;
|
||||
private VolumeMeter volumeMeterMusic = null!;
|
||||
private MuteButton muteButton = null!;
|
||||
|
||||
private SelectionCycleFillFlowContainer<VolumeMeter> volumeMeters = null!;
|
||||
|
||||
private readonly BindableDouble muteAdjustment = new BindableDouble();
|
||||
|
||||
public Bindable<bool> IsMuted { get; } = new Bindable<bool>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio, OsuColour colours)
|
||||
{
|
||||
@ -49,14 +48,7 @@ namespace osu.Game.Overlays
|
||||
Width = 300,
|
||||
Colour = ColourInfo.GradientHorizontal(Color4.Black.Opacity(0.75f), Color4.Black.Opacity(0))
|
||||
},
|
||||
muteButton = new MuteButton
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding(10),
|
||||
Current = { BindTarget = IsMuted }
|
||||
},
|
||||
volumeMeters = new SelectionCycleFillFlowContainer<VolumeMeter>
|
||||
new FillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
@ -64,26 +56,29 @@ namespace osu.Game.Overlays
|
||||
Origin = Anchor.CentreLeft,
|
||||
Spacing = new Vector2(0, offset),
|
||||
Margin = new MarginPadding { Left = offset },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
volumeMeters = new SelectionCycleFillFlowContainer<VolumeMeter>
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Spacing = new Vector2(0, offset),
|
||||
Children = new[]
|
||||
{
|
||||
volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker),
|
||||
volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker),
|
||||
volumeMeterMaster = new MasterVolumeMeter("MASTER", 150, colours.PinkDarker) { IsMuted = { BindTarget = IsMuted }, },
|
||||
volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker),
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
volumeMeterMaster.Bindable.BindTo(audio.Volume);
|
||||
volumeMeterEffect.Bindable.BindTo(audio.VolumeSample);
|
||||
volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack);
|
||||
|
||||
IsMuted.BindValueChanged(muted =>
|
||||
{
|
||||
if (muted.NewValue)
|
||||
audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment);
|
||||
else
|
||||
audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -92,8 +87,6 @@ namespace osu.Game.Overlays
|
||||
|
||||
foreach (var volumeMeter in volumeMeters)
|
||||
volumeMeter.Bindable.ValueChanged += _ => Show();
|
||||
|
||||
muteButton.Current.ValueChanged += _ => Show();
|
||||
}
|
||||
|
||||
public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false)
|
||||
@ -130,7 +123,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
case GlobalAction.ToggleMute:
|
||||
Show();
|
||||
muteButton.Current.Value = !muteButton.Current.Value;
|
||||
volumeMeters.OfType<MasterVolumeMeter>().First().ToggleMute();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -209,9 +209,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
case MouseButtonEvent mouse:
|
||||
// placement blueprints should generally block mouse from reaching underlying components (ie. performing clicks on interface buttons).
|
||||
// for now, the one exception we want to allow is when using a non-main mouse button when shift is pressed, which is used to trigger object deletion
|
||||
// while in placement mode.
|
||||
return mouse.Button == MouseButton.Left || !mouse.ShiftPressed;
|
||||
return mouse.Button == MouseButton.Left || PlacementActive == PlacementState.Active;
|
||||
|
||||
default:
|
||||
return false;
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
protected readonly double Offset;
|
||||
|
||||
/// <summary>
|
||||
/// The beatmap version.
|
||||
/// The .osu format (beatmap) version.
|
||||
/// </summary>
|
||||
protected readonly int FormatVersion;
|
||||
|
||||
@ -48,7 +48,10 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
{
|
||||
string[] split = text.Split(',');
|
||||
|
||||
Vector2 pos = new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE));
|
||||
Vector2 pos =
|
||||
FormatVersion >= LegacyBeatmapEncoder.FIRST_LAZER_VERSION
|
||||
? new Vector2(Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE))
|
||||
: new Vector2((int)Parsing.ParseFloat(split[0], Parsing.MAX_COORDINATE_VALUE), (int)Parsing.ParseFloat(split[1], Parsing.MAX_COORDINATE_VALUE));
|
||||
|
||||
double startTime = Parsing.ParseDouble(split[2]) + Offset;
|
||||
|
||||
|
@ -17,13 +17,12 @@ namespace osu.Game.Screens
|
||||
|
||||
private const float x_movement_amount = 50;
|
||||
|
||||
private readonly bool animateOnEnter;
|
||||
|
||||
public override bool IsPresent => base.IsPresent || Scheduler.HasPendingTasks;
|
||||
|
||||
protected BackgroundScreen(bool animateOnEnter = true)
|
||||
public bool AnimateEntry { get; set; } = true;
|
||||
|
||||
protected BackgroundScreen()
|
||||
{
|
||||
this.animateOnEnter = animateOnEnter;
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
@ -53,12 +52,11 @@ namespace osu.Game.Screens
|
||||
|
||||
public override void OnEntering(ScreenTransitionEvent e)
|
||||
{
|
||||
if (animateOnEnter)
|
||||
if (AnimateEntry)
|
||||
{
|
||||
this.FadeOut();
|
||||
this.MoveToX(x_movement_amount);
|
||||
|
||||
this.FadeIn(TRANSITION_LENGTH, Easing.InOutQuart);
|
||||
this.MoveToX(x_movement_amount);
|
||||
this.MoveToX(0, TRANSITION_LENGTH, Easing.InOutQuart);
|
||||
}
|
||||
|
||||
|
@ -27,10 +27,14 @@ namespace osu.Game.Screens
|
||||
if (screen == null)
|
||||
return false;
|
||||
|
||||
if (EqualityComparer<BackgroundScreen>.Default.Equals((BackgroundScreen)CurrentScreen, screen))
|
||||
bool isFirstScreen = CurrentScreen == null;
|
||||
screen.AnimateEntry = !isFirstScreen;
|
||||
|
||||
if (EqualityComparer<BackgroundScreen>.Default.Equals((BackgroundScreen?)CurrentScreen, screen))
|
||||
return false;
|
||||
|
||||
base.Push(screen);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -42,11 +42,6 @@ namespace osu.Game.Screens.Backgrounds
|
||||
|
||||
protected virtual bool AllowStoryboardBackground => true;
|
||||
|
||||
public BackgroundScreenDefault(bool animateOnEnter = true)
|
||||
: base(animateOnEnter)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(IAPIProvider api, SkinManager skinManager, OsuConfigManager config)
|
||||
{
|
||||
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Extensions.TypeExtensions;
|
||||
using osu.Framework.Threading;
|
||||
@ -16,6 +17,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
base.LoadComplete();
|
||||
|
||||
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, _) => updateInspectorText();
|
||||
EditorBeatmap.PlacementObject.BindValueChanged(_ => updateInspectorText());
|
||||
EditorBeatmap.TransactionBegan += updateInspectorText;
|
||||
EditorBeatmap.TransactionEnded += updateInspectorText;
|
||||
updateInspectorText();
|
||||
@ -29,24 +31,33 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
rollingTextUpdate?.Cancel();
|
||||
rollingTextUpdate = null;
|
||||
|
||||
AddInspectorValues();
|
||||
HitObject[] objects;
|
||||
|
||||
if (EditorBeatmap.SelectedHitObjects.Count > 0)
|
||||
objects = EditorBeatmap.SelectedHitObjects.ToArray();
|
||||
else if (EditorBeatmap.PlacementObject.Value != null)
|
||||
objects = new[] { EditorBeatmap.PlacementObject.Value };
|
||||
else
|
||||
objects = Array.Empty<HitObject>();
|
||||
|
||||
AddInspectorValues(objects);
|
||||
|
||||
// I'd hope there's a better way to do this, but I don't want to bind to each and every property above to watch for changes.
|
||||
// This is a good middle-ground for the time being.
|
||||
if (EditorBeatmap.SelectedHitObjects.Count > 0)
|
||||
if (objects.Length > 0)
|
||||
rollingTextUpdate ??= Scheduler.AddDelayed(updateInspectorText, 250);
|
||||
}
|
||||
|
||||
protected virtual void AddInspectorValues()
|
||||
protected virtual void AddInspectorValues(HitObject[] objects)
|
||||
{
|
||||
switch (EditorBeatmap.SelectedHitObjects.Count)
|
||||
switch (objects.Length)
|
||||
{
|
||||
case 0:
|
||||
AddValue("No selection");
|
||||
break;
|
||||
|
||||
case 1:
|
||||
var selected = EditorBeatmap.SelectedHitObjects.Single();
|
||||
var selected = objects.Single();
|
||||
|
||||
AddHeader("Type");
|
||||
AddValue($"{selected.GetType().ReadableName()}");
|
||||
@ -105,13 +116,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
default:
|
||||
AddHeader("Selected Objects");
|
||||
AddValue($"{EditorBeatmap.SelectedHitObjects.Count:#,0.##}");
|
||||
AddValue($"{objects.Length:#,0.##}");
|
||||
|
||||
AddHeader("Start Time");
|
||||
AddValue($"{EditorBeatmap.SelectedHitObjects.Min(o => o.StartTime):#,0.##}ms");
|
||||
AddValue($"{objects.Min(o => o.StartTime):#,0.##}ms");
|
||||
|
||||
AddHeader("End Time");
|
||||
AddValue($"{EditorBeatmap.SelectedHitObjects.Max(o => o.GetEndTime()):#,0.##}ms");
|
||||
AddValue($"{objects.Max(o => o.GetEndTime()):#,0.##}ms");
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -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 System.Collections.Generic;
|
||||
using System.Linq;
|
||||
@ -16,6 +14,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
@ -50,14 +49,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
|
||||
private readonly List<SelectionBlueprint<T>> selectedBlueprints;
|
||||
|
||||
protected SelectionBox SelectionBox { get; private set; }
|
||||
protected SelectionBox SelectionBox { get; private set; } = null!;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
protected IEditorChangeHandler ChangeHandler { get; private set; }
|
||||
protected IEditorChangeHandler? ChangeHandler { get; private set; }
|
||||
|
||||
public SelectionRotationHandler RotationHandler { get; private set; }
|
||||
public SelectionRotationHandler RotationHandler { get; private set; } = null!;
|
||||
|
||||
public SelectionScaleHandler ScaleHandler { get; private set; }
|
||||
public SelectionScaleHandler ScaleHandler { get; private set; } = null!;
|
||||
|
||||
[Resolved(CanBeNull = true)]
|
||||
protected OsuContextMenuContainer? ContextMenuContainer { get; private set; }
|
||||
|
||||
protected SelectionHandler()
|
||||
{
|
||||
@ -230,7 +232,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// <summary>
|
||||
/// Deselect all selected items.
|
||||
/// </summary>
|
||||
protected void DeselectAll() => SelectedItems.Clear();
|
||||
protected void DeselectAll()
|
||||
{
|
||||
SelectedItems.Clear();
|
||||
ContextMenuContainer?.CloseMenu();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Handle a blueprint becoming selected.
|
||||
@ -243,6 +249,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
SelectedItems.Add(blueprint.Item);
|
||||
|
||||
selectedBlueprints.Add(blueprint);
|
||||
|
||||
ContextMenuContainer?.CloseMenu();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -263,7 +271,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
/// <returns>Whether a selection was performed.</returns>
|
||||
internal virtual bool MouseDownSelectionRequested(SelectionBlueprint<T> blueprint, MouseButtonEvent e)
|
||||
{
|
||||
if (e.ShiftPressed && e.Button == MouseButton.Right)
|
||||
if (e.Button == MouseButton.Middle || (e.ShiftPressed && e.Button == MouseButton.Right))
|
||||
{
|
||||
handleQuickDeletion(blueprint);
|
||||
return true;
|
||||
|
@ -69,19 +69,24 @@ namespace osu.Game.Screens.Edit.Compose
|
||||
if (ruleset == null || composer == null)
|
||||
return base.CreateTimelineContent();
|
||||
|
||||
return wrapSkinnableContent(new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TimelineBlueprintContainer(composer),
|
||||
new TimelineBreakDisplay
|
||||
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[]
|
||||
{
|
||||
// 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),
|
||||
breakDisplay
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -6,6 +6,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework;
|
||||
@ -159,7 +160,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
private string lastSavedHash;
|
||||
|
||||
private Container<EditorScreen> screenContainer;
|
||||
private ScreenContainer screenContainer;
|
||||
|
||||
[CanBeNull]
|
||||
private readonly EditorLoader loader;
|
||||
@ -329,7 +330,7 @@ namespace osu.Game.Screens.Edit
|
||||
Name = "Screen container",
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = 40, Bottom = 50 },
|
||||
Child = screenContainer = new Container<EditorScreen>
|
||||
Child = screenContainer = new ScreenContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
@ -422,6 +423,7 @@ namespace osu.Game.Screens.Edit
|
||||
MutationTracker,
|
||||
}
|
||||
});
|
||||
|
||||
changeHandler?.CanUndo.BindValueChanged(v => undoMenuItem.Action.Disabled = !v.NewValue, true);
|
||||
changeHandler?.CanRedo.BindValueChanged(v => redoMenuItem.Action.Disabled = !v.NewValue, true);
|
||||
|
||||
@ -1007,7 +1009,7 @@ namespace osu.Game.Screens.Edit
|
||||
throw new InvalidOperationException("Editor menu bar switched to an unsupported mode");
|
||||
}
|
||||
|
||||
LoadComponentAsync(currentScreen, newScreen =>
|
||||
screenContainer.LoadComponentAsync(currentScreen, newScreen =>
|
||||
{
|
||||
if (newScreen == currentScreen)
|
||||
{
|
||||
@ -1385,5 +1387,12 @@ namespace osu.Game.Screens.Edit
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private partial class ScreenContainer : Container<EditorScreen>
|
||||
{
|
||||
public new Task LoadComponentAsync<TLoadable>([NotNull] TLoadable component, Action<TLoadable> onLoaded = null, CancellationToken cancellation = default, Scheduler scheduler = null)
|
||||
where TLoadable : Drawable
|
||||
=> base.LoadComponentAsync(component, onLoaded, cancellation, scheduler);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ namespace osu.Game.Screens.Menu
|
||||
});
|
||||
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Multi, @"button-default-select", OsuIcon.Online, new Color4(94, 63, 186, 255), onMultiplayer, Key.M));
|
||||
buttonsPlay.Add(new MainMenuButton(ButtonSystemStrings.Playlists, @"button-default-select", OsuIcon.Tournament, new Color4(94, 63, 186, 255), onPlaylists, Key.L));
|
||||
buttonsPlay.Add(new DailyChallengeButton(@"button-default-select", new Color4(94, 63, 186, 255), onDailyChallenge, Key.D));
|
||||
buttonsPlay.Add(new DailyChallengeButton(@"button-daily-select", new Color4(94, 63, 186, 255), onDailyChallenge, Key.D));
|
||||
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
|
||||
|
||||
buttonsEdit.Add(new MainMenuButton(EditorStrings.BeatmapEditor.ToLower(), @"button-default-select", OsuIcon.Beatmap, new Color4(238, 170, 0, 255), _ => OnEditBeatmap?.Invoke(), Key.B, Key.E)
|
||||
|
@ -104,8 +104,7 @@ namespace osu.Game.Screens.Menu
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
info.BindValueChanged(_ => dailyChallengeChanged(postNotification: true));
|
||||
dailyChallengeChanged(postNotification: false);
|
||||
info.BindValueChanged(dailyChallengeChanged, true);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -131,7 +130,9 @@ namespace osu.Game.Screens.Menu
|
||||
}
|
||||
}
|
||||
|
||||
private void dailyChallengeChanged(bool postNotification)
|
||||
private long? lastNotifiedDailyChallengeRoomId;
|
||||
|
||||
private void dailyChallengeChanged(ValueChangedEvent<DailyChallengeInfo?> _)
|
||||
{
|
||||
UpdateState();
|
||||
|
||||
@ -152,8 +153,14 @@ namespace osu.Game.Screens.Menu
|
||||
Room = room;
|
||||
cover.OnlineInfo = TooltipContent = room.Playlist.FirstOrDefault()?.Beatmap.BeatmapSet as APIBeatmapSet;
|
||||
|
||||
if (postNotification)
|
||||
// We only want to notify the user if a new challenge recently went live.
|
||||
if (room.StartDate.Value != null
|
||||
&& Math.Abs((DateTimeOffset.Now - room.StartDate.Value!.Value).TotalSeconds) < 1800
|
||||
&& room.RoomID.Value != lastNotifiedDailyChallengeRoomId)
|
||||
{
|
||||
lastNotifiedDailyChallengeRoomId = room.RoomID.Value;
|
||||
notificationOverlay?.Post(new NewDailyChallengeNotification(room));
|
||||
}
|
||||
|
||||
updateCountdown();
|
||||
Scheduler.AddDelayed(updateCountdown, 1000, true);
|
||||
|
@ -90,7 +90,7 @@ namespace osu.Game.Screens.Menu
|
||||
/// </summary>
|
||||
protected bool UsingThemedIntro { get; private set; }
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault(false)
|
||||
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault
|
||||
{
|
||||
Colour = Color4.Black
|
||||
};
|
||||
|
@ -150,7 +150,7 @@ namespace osu.Game.Screens.Menu
|
||||
OnPlaylists = () => this.Push(new Playlists()),
|
||||
OnDailyChallenge = room =>
|
||||
{
|
||||
this.Push(new DailyChallenge(room));
|
||||
this.Push(new DailyChallengeIntro(room));
|
||||
},
|
||||
OnExit = () =>
|
||||
{
|
||||
|
@ -21,7 +21,6 @@ namespace osu.Game.Screens.OnlinePlay.Components
|
||||
private PlaylistItemBackground? background;
|
||||
|
||||
protected OnlinePlayBackgroundScreen()
|
||||
: base(false)
|
||||
{
|
||||
AddInternal(new Box
|
||||
{
|
||||
|
@ -4,6 +4,7 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
@ -44,7 +45,7 @@ using osuTK;
|
||||
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
{
|
||||
[Cached(typeof(IPreviewTrackOwner))]
|
||||
public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner
|
||||
public partial class DailyChallenge : OsuScreen, IPreviewTrackOwner, IHandlePresentBeatmap
|
||||
{
|
||||
private readonly Room room;
|
||||
private readonly PlaylistItem playlistItem;
|
||||
@ -76,6 +77,9 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
[Cached]
|
||||
private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
|
||||
|
||||
[Resolved]
|
||||
private OsuGame? game { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmapManager { get; set; } = null!;
|
||||
|
||||
@ -386,7 +390,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
base.LoadComplete();
|
||||
|
||||
beatmapAvailabilityTracker.SelectedItem.Value = playlistItem;
|
||||
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => trySetDailyChallengeBeatmap(), true);
|
||||
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem), true);
|
||||
|
||||
userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay);
|
||||
userModsSelectOverlay.SelectedItem.Value = playlistItem;
|
||||
@ -398,15 +402,6 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
dailyChallengeInfo.BindValueChanged(dailyChallengeChanged);
|
||||
}
|
||||
|
||||
private void trySetDailyChallengeBeatmap()
|
||||
{
|
||||
var beatmap = beatmapManager.QueryBeatmap(b => b.OnlineID == playlistItem.Beatmap.OnlineID);
|
||||
Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally.
|
||||
Ruleset.Value = rulesets.GetRuleset(playlistItem.RulesetID);
|
||||
|
||||
applyLoopingToTrack();
|
||||
}
|
||||
|
||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
||||
{
|
||||
if (state.NewValue != APIState.Online)
|
||||
@ -440,7 +435,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
|
||||
waves.Show();
|
||||
roomManager.JoinRoom(room);
|
||||
applyLoopingToTrack();
|
||||
startLoopingTrack(this, musicController);
|
||||
|
||||
metadataClient.BeginWatchingMultiplayerRoom(room.RoomID.Value!.Value).ContinueWith(t =>
|
||||
{
|
||||
@ -452,6 +447,7 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
|
||||
MultiplayerPlaylistItemStats[] stats = t.GetResultSafely();
|
||||
var itemStats = stats.SingleOrDefault(item => item.PlaylistItemID == playlistItem.ID);
|
||||
|
||||
if (itemStats == null) return;
|
||||
|
||||
Schedule(() =>
|
||||
@ -459,17 +455,17 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
breakdown.SetInitialCounts(itemStats.TotalScoreDistribution);
|
||||
totals.SetInitialCounts(itemStats.TotalScoreDistribution.Sum(c => c), itemStats.CumulativeScore);
|
||||
});
|
||||
});
|
||||
}, TaskContinuationOptions.OnlyOnRanToCompletion);
|
||||
|
||||
beatmapAvailabilityTracker.SelectedItem.Value = playlistItem;
|
||||
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => trySetDailyChallengeBeatmap(), true);
|
||||
userModsSelectOverlay.SelectedItem.Value = playlistItem;
|
||||
|
||||
TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, playlistItem);
|
||||
}
|
||||
|
||||
public override void OnResuming(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnResuming(e);
|
||||
applyLoopingToTrack();
|
||||
startLoopingTrack(this, musicController);
|
||||
// re-apply mods as they may have been changed by a child screen
|
||||
// (one known instance of this is showing a replay).
|
||||
updateMods();
|
||||
@ -498,17 +494,30 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
return base.OnExiting(e);
|
||||
}
|
||||
|
||||
private void applyLoopingToTrack()
|
||||
public static void TrySetDailyChallengeBeatmap(OsuScreen screen, BeatmapManager beatmaps, RulesetStore rulesets, MusicController music, PlaylistItem item)
|
||||
{
|
||||
if (!this.IsCurrentScreen())
|
||||
if (!screen.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
var track = Beatmap.Value?.Track;
|
||||
var beatmap = beatmaps.QueryBeatmap(b => b.OnlineID == item.Beatmap.OnlineID);
|
||||
|
||||
screen.Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap); // this will gracefully fall back to dummy beatmap if missing locally.
|
||||
screen.Ruleset.Value = rulesets.GetRuleset(item.RulesetID);
|
||||
|
||||
startLoopingTrack(screen, music);
|
||||
}
|
||||
|
||||
private static void startLoopingTrack(OsuScreen screen, MusicController music)
|
||||
{
|
||||
if (!screen.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
var track = screen.Beatmap.Value?.Track;
|
||||
|
||||
if (track != null)
|
||||
{
|
||||
Beatmap.Value?.PrepareTrackForPreview(true);
|
||||
musicController.EnsurePlayingSomething();
|
||||
screen.Beatmap.Value?.PrepareTrackForPreview(true);
|
||||
music.EnsurePlayingSomething();
|
||||
}
|
||||
}
|
||||
|
||||
@ -546,5 +555,21 @@ namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
if (metadataClient.IsNotNull())
|
||||
metadataClient.MultiplayerRoomScoreSet -= onRoomScoreSet;
|
||||
}
|
||||
|
||||
public void PresentBeatmap(WorkingBeatmap beatmap, RulesetInfo ruleset)
|
||||
{
|
||||
if (!this.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
// We can only handle the current daily challenge beatmap.
|
||||
// If the import was for a different beatmap, pass the duty off to global handling.
|
||||
if (beatmap.BeatmapSetInfo.OnlineID != playlistItem.Beatmap.BeatmapSet!.OnlineID)
|
||||
{
|
||||
this.Exit();
|
||||
game?.PresentBeatmap(beatmap.BeatmapSetInfo, b => b.ID == beatmap.BeatmapInfo.ID);
|
||||
}
|
||||
|
||||
// And if we're handling, we don't really have much to do here.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,534 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Screens.OnlinePlay.Match;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.DailyChallenge
|
||||
{
|
||||
public partial class DailyChallengeIntro : OsuScreen
|
||||
{
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
public override bool? ApplyModTrackAdjustments => true;
|
||||
|
||||
private readonly Room room;
|
||||
private readonly PlaylistItem item;
|
||||
|
||||
private Container introContent = null!;
|
||||
private Container topTitleDisplay = null!;
|
||||
private Container bottomDateDisplay = null!;
|
||||
private Container beatmapBackground = null!;
|
||||
private Box flash = null!;
|
||||
|
||||
private FillFlowContainer beatmapContent = null!;
|
||||
|
||||
private Container titleContainer = null!;
|
||||
|
||||
private bool beatmapBackgroundLoaded;
|
||||
|
||||
private bool animationBegan;
|
||||
|
||||
private IBindable<StarDifficulty?> starDifficulty = null!;
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Plum);
|
||||
|
||||
[Cached]
|
||||
private readonly OnlinePlayBeatmapAvailabilityTracker beatmapAvailabilityTracker = new OnlinePlayBeatmapAvailabilityTracker();
|
||||
|
||||
private bool shouldBePlayingMusic;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmapManager { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private MusicController musicController { get; set; } = null!;
|
||||
|
||||
private Sample? dateWindupSample;
|
||||
private Sample? dateImpactSample;
|
||||
private Sample? beatmapWindupSample;
|
||||
private Sample? beatmapImpactSample;
|
||||
|
||||
private SampleChannel? dateWindupChannel;
|
||||
private SampleChannel? dateImpactChannel;
|
||||
private SampleChannel? beatmapWindupChannel;
|
||||
private SampleChannel? beatmapImpactChannel;
|
||||
|
||||
private IDisposable? duckOperation;
|
||||
|
||||
public DailyChallengeIntro(Room room)
|
||||
{
|
||||
this.room = room;
|
||||
item = room.Playlist.Single();
|
||||
|
||||
ValidForResume = false;
|
||||
}
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new DailyChallengeIntroBackgroundScreen(colourProvider);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(BeatmapDifficultyCache difficultyCache, BeatmapModelDownloader beatmapDownloader, OsuConfigManager config, AudioManager audio)
|
||||
{
|
||||
const float horizontal_info_size = 500f;
|
||||
|
||||
Ruleset ruleset = Ruleset.Value.CreateInstance();
|
||||
|
||||
StarRatingDisplay starRatingDisplay;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
beatmapAvailabilityTracker,
|
||||
introContent = new Container
|
||||
{
|
||||
Alpha = 0f,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
titleContainer = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
topTitleDisplay = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
CornerRadius = 10f,
|
||||
Masking = true,
|
||||
X = -10,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colourProvider.Background3,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = "Today's Challenge",
|
||||
Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f },
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate),
|
||||
},
|
||||
}
|
||||
},
|
||||
bottomDateDisplay = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
CornerRadius = 10f,
|
||||
Masking = true,
|
||||
X = 10,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colourProvider.Background3,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Text = room.Name.Value.Split(':', StringSplitOptions.TrimEntries).Last(),
|
||||
Margin = new MarginPadding { Horizontal = 10f, Vertical = 5f },
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Font = OsuFont.GetFont(size: 32, weight: FontWeight.Light, typeface: Typeface.TorusAlternate),
|
||||
},
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
beatmapContent = new FillFlowContainer
|
||||
{
|
||||
AlwaysPresent = true, // so we can get the size ahead of time
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Alpha = 0,
|
||||
Scale = new Vector2(0.001f),
|
||||
Spacing = new Vector2(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
beatmapBackground = new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Size = new Vector2(horizontal_info_size, 150f),
|
||||
CornerRadius = 20f,
|
||||
BorderColour = colourProvider.Content2,
|
||||
BorderThickness = 3f,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colourProvider.Background3,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
flash = new Box
|
||||
{
|
||||
Colour = Color4.White,
|
||||
Blending = BlendingParameters.Additive,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Depth = float.MinValue,
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Width = horizontal_info_size,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
CornerRadius = 10f,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colourProvider.Background3,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Direction = FillDirection.Vertical,
|
||||
Padding = new MarginPadding(5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new TruncatingSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
MaxWidth = horizontal_info_size,
|
||||
Text = item.Beatmap.BeatmapSet!.Metadata.GetDisplayTitleRomanisable(false),
|
||||
Padding = new MarginPadding { Horizontal = 5f },
|
||||
Font = OsuFont.GetFont(size: 26),
|
||||
},
|
||||
new TruncatingSpriteText
|
||||
{
|
||||
Text = $"Difficulty: {item.Beatmap.DifficultyName}",
|
||||
Font = OsuFont.GetFont(size: 20, italics: true),
|
||||
MaxWidth = horizontal_info_size,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
},
|
||||
new TruncatingSpriteText
|
||||
{
|
||||
Text = $"by {item.Beatmap.Metadata.Author.Username}",
|
||||
Font = OsuFont.GetFont(size: 16, italics: true),
|
||||
MaxWidth = horizontal_info_size,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
},
|
||||
starRatingDisplay = new StarRatingDisplay(default)
|
||||
{
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Margin = new MarginPadding(5),
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Width = horizontal_info_size,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
CornerRadius = 10f,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Colour = colourProvider.Background3,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new ModFlowDisplay
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
Current =
|
||||
{
|
||||
Value = item.RequiredMods.Select(m => m.ToMod(ruleset)).ToArray()
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
starDifficulty = difficultyCache.GetBindableDifficulty(item.Beatmap);
|
||||
starDifficulty.BindValueChanged(star =>
|
||||
{
|
||||
if (star.NewValue != null)
|
||||
starRatingDisplay.Current.Value = star.NewValue.Value;
|
||||
}, true);
|
||||
|
||||
LoadComponentAsync(new OnlineBeatmapSetCover(item.Beatmap.BeatmapSet as IBeatmapSetOnlineInfo)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
FillMode = FillMode.Fit,
|
||||
Scale = new Vector2(1.2f),
|
||||
Shear = new Vector2(-OsuGame.SHEAR, 0f),
|
||||
}, c =>
|
||||
{
|
||||
beatmapBackground.Add(c);
|
||||
|
||||
beatmapBackgroundLoaded = true;
|
||||
updateAnimationState();
|
||||
});
|
||||
|
||||
if (config.Get<bool>(OsuSetting.AutomaticallyDownloadMissingBeatmaps))
|
||||
{
|
||||
if (!beatmapManager.IsAvailableLocally(new BeatmapSetInfo { OnlineID = item.Beatmap.BeatmapSet!.OnlineID }))
|
||||
beatmapDownloader.Download(item.Beatmap.BeatmapSet!, config.Get<bool>(OsuSetting.PreferNoVideo));
|
||||
}
|
||||
|
||||
dateWindupSample = audio.Samples.Get(@"DailyChallenge/date-windup");
|
||||
dateImpactSample = audio.Samples.Get(@"DailyChallenge/date-impact");
|
||||
beatmapWindupSample = audio.Samples.Get(@"DailyChallenge/beatmap-windup");
|
||||
beatmapImpactSample = audio.Samples.Get(@"DailyChallenge/beatmap-impact");
|
||||
}
|
||||
|
||||
public override void OnEntering(ScreenTransitionEvent e)
|
||||
{
|
||||
base.OnEntering(e);
|
||||
|
||||
beatmapAvailabilityTracker.SelectedItem.Value = item;
|
||||
beatmapAvailabilityTracker.Availability.BindValueChanged(availability =>
|
||||
{
|
||||
if (shouldBePlayingMusic && availability.NewValue.State == DownloadState.LocallyAvailable)
|
||||
DailyChallenge.TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, item);
|
||||
}, true);
|
||||
|
||||
this.FadeInFromZero(400, Easing.OutQuint);
|
||||
updateAnimationState();
|
||||
|
||||
playDateWindupSample();
|
||||
}
|
||||
|
||||
public override void OnSuspending(ScreenTransitionEvent e)
|
||||
{
|
||||
this.FadeOut(800, Easing.OutQuint);
|
||||
base.OnSuspending(e);
|
||||
}
|
||||
|
||||
private void updateAnimationState()
|
||||
{
|
||||
if (!beatmapBackgroundLoaded || !this.IsCurrentScreen())
|
||||
return;
|
||||
|
||||
if (animationBegan)
|
||||
return;
|
||||
|
||||
beginAnimation();
|
||||
animationBegan = true;
|
||||
}
|
||||
|
||||
private void beginAnimation()
|
||||
{
|
||||
using (BeginDelayedSequence(200))
|
||||
{
|
||||
introContent.Show();
|
||||
|
||||
const float y_offset_start = 260;
|
||||
const float y_offset_end = 20;
|
||||
|
||||
topTitleDisplay
|
||||
.FadeInFromZero(400, Easing.OutQuint);
|
||||
|
||||
topTitleDisplay.MoveToY(-y_offset_start)
|
||||
.MoveToY(-y_offset_end, 300, Easing.OutQuint)
|
||||
.Then()
|
||||
.MoveToY(0, 4000);
|
||||
|
||||
bottomDateDisplay.MoveToY(y_offset_start)
|
||||
.MoveToY(y_offset_end, 300, Easing.OutQuint)
|
||||
.Then()
|
||||
.MoveToY(0, 4000);
|
||||
|
||||
using (BeginDelayedSequence(150))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
playDateImpactSample();
|
||||
playBeatmapWindupSample();
|
||||
|
||||
duckOperation?.Dispose();
|
||||
duckOperation = musicController.Duck(new DuckParameters
|
||||
{
|
||||
RestoreDuration = 1500f,
|
||||
});
|
||||
});
|
||||
|
||||
using (BeginDelayedSequence(2750))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
duckOperation?.Dispose();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
using (BeginDelayedSequence(1000))
|
||||
{
|
||||
beatmapContent
|
||||
.ScaleTo(3)
|
||||
.ScaleTo(1f, 500, Easing.In)
|
||||
.Then()
|
||||
.ScaleTo(1.1f, 4000);
|
||||
|
||||
using (BeginDelayedSequence(100))
|
||||
{
|
||||
titleContainer
|
||||
.ScaleTo(0.4f, 400, Easing.In)
|
||||
.FadeOut(500, Easing.OutQuint);
|
||||
}
|
||||
|
||||
using (BeginDelayedSequence(240))
|
||||
{
|
||||
beatmapContent.FadeInFromZero(280, Easing.InQuad);
|
||||
|
||||
using (BeginDelayedSequence(300))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
shouldBePlayingMusic = true;
|
||||
DailyChallenge.TrySetDailyChallengeBeatmap(this, beatmapManager, rulesets, musicController, item);
|
||||
ApplyToBackground(bs => ((RoomBackgroundScreen)bs).SelectedItem.Value = item);
|
||||
playBeatmapImpactSample();
|
||||
});
|
||||
}
|
||||
|
||||
using (BeginDelayedSequence(400))
|
||||
flash.FadeOutFromOne(5000, Easing.OutQuint);
|
||||
|
||||
using (BeginDelayedSequence(2600))
|
||||
{
|
||||
Schedule(() =>
|
||||
{
|
||||
if (this.IsCurrentScreen())
|
||||
this.Push(new DailyChallenge(room));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void playDateWindupSample()
|
||||
{
|
||||
dateWindupChannel = dateWindupSample?.GetChannel();
|
||||
dateWindupChannel?.Play();
|
||||
}
|
||||
|
||||
private void playDateImpactSample()
|
||||
{
|
||||
dateImpactChannel = dateImpactSample?.GetChannel();
|
||||
dateImpactChannel?.Play();
|
||||
}
|
||||
|
||||
private void playBeatmapWindupSample()
|
||||
{
|
||||
beatmapWindupChannel = beatmapWindupSample?.GetChannel();
|
||||
beatmapWindupChannel?.Play();
|
||||
}
|
||||
|
||||
private void playBeatmapImpactSample()
|
||||
{
|
||||
beatmapImpactChannel = beatmapImpactSample?.GetChannel();
|
||||
beatmapImpactChannel?.Play();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
resetAudio();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
||||
private void resetAudio()
|
||||
{
|
||||
dateWindupChannel?.Stop();
|
||||
dateImpactChannel?.Stop();
|
||||
beatmapWindupChannel?.Stop();
|
||||
beatmapImpactChannel?.Stop();
|
||||
duckOperation?.Dispose();
|
||||
}
|
||||
|
||||
private partial class DailyChallengeIntroBackgroundScreen : RoomBackgroundScreen
|
||||
{
|
||||
private readonly OverlayColourProvider colourProvider;
|
||||
|
||||
public DailyChallengeIntroBackgroundScreen(OverlayColourProvider colourProvider)
|
||||
: base(null)
|
||||
{
|
||||
this.colourProvider = colourProvider;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AddInternal(new Box
|
||||
{
|
||||
Depth = float.MinValue,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5.Opacity(0.6f),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Match.Components;
|
||||
using osu.Game.Screens.Play;
|
||||
@ -41,7 +42,13 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
|
||||
|
||||
Background.Alpha = 0.2f;
|
||||
|
||||
TextBox.FocusLost = () => expandedFromTextBoxFocus.Value = false;
|
||||
TextBox.PlaceholderText = ChatStrings.InGameInputPlaceholder;
|
||||
TextBox.Focus = () => TextBox.PlaceholderText = Resources.Localisation.Web.ChatStrings.InputPlaceholder;
|
||||
TextBox.FocusLost = () =>
|
||||
{
|
||||
TextBox.PlaceholderText = ChatStrings.InGameInputPlaceholder;
|
||||
expandedFromTextBoxFocus.Value = false;
|
||||
};
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e) => true; // use UI mouse cursor.
|
||||
|
@ -1,7 +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 osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
|
@ -14,11 +14,10 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
public ArgonKeyCounterDisplay()
|
||||
{
|
||||
InternalChild = KeyFlow = new FillFlowContainer<KeyCounter>
|
||||
Child = KeyFlow = new FillFlowContainer<KeyCounter>
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Alpha = 0,
|
||||
Spacing = new Vector2(2),
|
||||
};
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user