mirror of
https://github.com/ppy/osu.git
synced 2025-01-13 19:43:22 +08:00
Merge branch 'master' into discord-rpc
This commit is contained in:
commit
794e5434a8
@ -21,7 +21,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"ppy.localisationanalyser.tools": {
|
"ppy.localisationanalyser.tools": {
|
||||||
"version": "2022.417.0",
|
"version": "2022.607.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"localisation"
|
"localisation"
|
||||||
]
|
]
|
||||||
|
2
.github/workflows/sentry-release.yml
vendored
2
.github/workflows/sentry-release.yml
vendored
@ -23,4 +23,4 @@ jobs:
|
|||||||
SENTRY_URL: https://sentry.ppy.sh/
|
SENTRY_URL: https://sentry.ppy.sh/
|
||||||
with:
|
with:
|
||||||
environment: production
|
environment: production
|
||||||
version: ${{ github.ref }}
|
version: osu@${{ github.ref_name }}
|
||||||
|
@ -52,10 +52,10 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" />
|
<PackageReference Include="ppy.osu.Game.Resources" Version="2022.527.0" />
|
||||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.529.0" />
|
<PackageReference Include="ppy.osu.Framework.Android" Version="2022.605.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup Label="Transitive Dependencies">
|
<ItemGroup Label="Transitive Dependencies">
|
||||||
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
<!-- Realm needs to be directly referenced in all Xamarin projects, as it will not pull in its transitive dependencies otherwise. -->
|
||||||
<PackageReference Include="Realm" Version="10.11.2" />
|
<PackageReference Include="Realm" Version="10.14.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
@ -14,11 +14,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
{
|
{
|
||||||
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
protected override string ResourceAssembly => "osu.Game.Rulesets.Mania";
|
||||||
|
|
||||||
[TestCase(2.3449735700206298d, 151, "diffcalc-test")]
|
[TestCase(2.3449735700206298d, 242, "diffcalc-test")]
|
||||||
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
public void Test(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
=> base.Test(expectedStarRating, expectedMaxCombo, name);
|
||||||
|
|
||||||
[TestCase(2.7879104989252959d, 151, "diffcalc-test")]
|
[TestCase(2.7879104989252959d, 242, "diffcalc-test")]
|
||||||
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
public void TestClockRateAdjusted(double expectedStarRating, int expectedMaxCombo, string name)
|
||||||
=> Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime());
|
=> Test(expectedStarRating, expectedMaxCombo, name, new ManiaModDoubleTime());
|
||||||
|
|
||||||
|
@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
foreach (var v in base.ToDatabaseAttributes())
|
foreach (var v in base.ToDatabaseAttributes())
|
||||||
yield return v;
|
yield return v;
|
||||||
|
|
||||||
// Todo: osu!mania doesn't output MaxCombo attribute for some reason.
|
yield return (ATTRIB_ID_MAX_COMBO, MaxCombo);
|
||||||
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
|
yield return (ATTRIB_ID_DIFFICULTY, StarRating);
|
||||||
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow);
|
||||||
yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier);
|
yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier);
|
||||||
@ -39,6 +39,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
{
|
{
|
||||||
base.FromDatabaseAttributes(values);
|
base.FromDatabaseAttributes(values);
|
||||||
|
|
||||||
|
MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO];
|
||||||
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
StarRating = values[ATTRIB_ID_DIFFICULTY];
|
||||||
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
|
GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW];
|
||||||
ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER];
|
ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER];
|
||||||
|
@ -52,10 +52,18 @@ namespace osu.Game.Rulesets.Mania.Difficulty
|
|||||||
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
|
// This is done the way it is to introduce fractional differences in order to match osu-stable for the time being.
|
||||||
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
|
GreatHitWindow = Math.Ceiling((int)(getHitWindow300(mods) * clockRate) / clockRate),
|
||||||
ScoreMultiplier = getScoreMultiplier(mods),
|
ScoreMultiplier = getScoreMultiplier(mods),
|
||||||
MaxCombo = beatmap.HitObjects.Sum(h => h is HoldNote ? 2 : 1),
|
MaxCombo = beatmap.HitObjects.Sum(maxComboForObject)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int maxComboForObject(HitObject hitObject)
|
||||||
|
{
|
||||||
|
if (hitObject is HoldNote hold)
|
||||||
|
return 1 + (int)((hold.EndTime - hold.StartTime) / 100);
|
||||||
|
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
protected override IEnumerable<DifficultyHitObject> CreateDifficultyHitObjects(IBeatmap beatmap, double clockRate)
|
||||||
{
|
{
|
||||||
var sortedObjects = beatmap.HitObjects.ToArray();
|
var sortedObjects = beatmap.HitObjects.ToArray();
|
||||||
|
@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
if (positionInfo == positionInfos.First())
|
if (positionInfo == positionInfos.First())
|
||||||
{
|
{
|
||||||
positionInfo.DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.X / 2);
|
positionInfo.DistanceFromPrevious = (float)(rng.NextDouble() * OsuPlayfield.BASE_SIZE.Y / 2);
|
||||||
positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI);
|
positionInfo.RelativeAngle = (float)(rng.NextDouble() * 2 * Math.PI - Math.PI);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
@ -79,7 +79,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
Result = { BindTarget = SpinsPerMinute },
|
Result = { BindTarget = SpinsPerMinute },
|
||||||
},
|
},
|
||||||
ticks = new Container<DrawableSpinnerTick>(),
|
ticks = new Container<DrawableSpinnerTick>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
new AspectContainer
|
new AspectContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public class DrawableSpinnerTick : DrawableOsuHitObject
|
public class DrawableSpinnerTick : DrawableOsuHitObject
|
||||||
@ -10,13 +12,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
protected DrawableSpinner DrawableSpinner => (DrawableSpinner)ParentHitObject;
|
protected DrawableSpinner DrawableSpinner => (DrawableSpinner)ParentHitObject;
|
||||||
|
|
||||||
public DrawableSpinnerTick()
|
public DrawableSpinnerTick()
|
||||||
: base(null)
|
: this(null)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public DrawableSpinnerTick(SpinnerTick spinnerTick)
|
public DrawableSpinnerTick(SpinnerTick spinnerTick)
|
||||||
: base(spinnerTick)
|
: base(spinnerTick)
|
||||||
{
|
{
|
||||||
|
Anchor = Anchor.Centre;
|
||||||
|
Origin = Anchor.Centre;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
|
protected override double MaximumJudgementOffset => DrawableSpinner.HitObject.Duration;
|
||||||
|
@ -69,8 +69,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
double startTime = StartTime + (float)(i + 1) / totalSpins * Duration;
|
double startTime = StartTime + (float)(i + 1) / totalSpins * Duration;
|
||||||
|
|
||||||
AddNested(i < SpinsRequired
|
AddNested(i < SpinsRequired
|
||||||
? new SpinnerTick { StartTime = startTime, Position = Position }
|
? new SpinnerTick { StartTime = startTime }
|
||||||
: new SpinnerBonusTick { StartTime = startTime, Position = Position });
|
: new SpinnerBonusTick { StartTime = startTime });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,7 +78,9 @@ namespace osu.Game.Rulesets.Osu.Skinning.Legacy
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!(source.FindProvider(s => s.GetTexture("spinner-top") != null) is DefaultLegacySkin))
|
var topProvider = source.FindProvider(s => s.GetTexture("spinner-top") != null);
|
||||||
|
|
||||||
|
if (topProvider is LegacySkinTransformer transformer && !(transformer.Skin is DefaultLegacySkin))
|
||||||
{
|
{
|
||||||
AddInternal(ApproachCircle = new Sprite
|
AddInternal(ApproachCircle = new Sprite
|
||||||
{
|
{
|
||||||
|
@ -116,6 +116,7 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
if (!(osuObject is Slider slider))
|
if (!(osuObject is Slider slider))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// No need to update the head and tail circles, since slider handles that when the new slider path is set
|
||||||
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
|
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
|
||||||
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
|
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(OsuPlayfield.BASE_SIZE.X - h.Position.X, h.Position.Y));
|
||||||
|
|
||||||
@ -137,6 +138,7 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
if (!(osuObject is Slider slider))
|
if (!(osuObject is Slider slider))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// No need to update the head and tail circles, since slider handles that when the new slider path is set
|
||||||
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||||
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
|
||||||
|
|
||||||
@ -146,5 +148,41 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
|
|
||||||
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rotate a slider about its start position by the specified angle.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slider">The slider to be rotated.</param>
|
||||||
|
/// <param name="rotation">The angle, measured in radians, to rotate the slider by.</param>
|
||||||
|
public static void RotateSlider(Slider slider, float rotation)
|
||||||
|
{
|
||||||
|
void rotateNestedObject(OsuHitObject nested) => nested.Position = rotateVector(nested.Position - slider.Position, rotation) + slider.Position;
|
||||||
|
|
||||||
|
// No need to update the head and tail circles, since slider handles that when the new slider path is set
|
||||||
|
slider.NestedHitObjects.OfType<SliderTick>().ForEach(rotateNestedObject);
|
||||||
|
slider.NestedHitObjects.OfType<SliderRepeat>().ForEach(rotateNestedObject);
|
||||||
|
|
||||||
|
var controlPoints = slider.Path.ControlPoints.Select(p => new PathControlPoint(p.Position, p.Type)).ToArray();
|
||||||
|
foreach (var point in controlPoints)
|
||||||
|
point.Position = rotateVector(point.Position, rotation);
|
||||||
|
|
||||||
|
slider.Path = new SliderPath(controlPoints, slider.Path.ExpectedDistance.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Rotate a vector by the specified angle.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="vector">The vector to be rotated.</param>
|
||||||
|
/// <param name="rotation">The angle, measured in radians, to rotate the vector by.</param>
|
||||||
|
/// <returns>The rotated vector.</returns>
|
||||||
|
private static Vector2 rotateVector(Vector2 vector, float rotation)
|
||||||
|
{
|
||||||
|
float angle = MathF.Atan2(vector.Y, vector.X) + rotation;
|
||||||
|
float length = vector.Length;
|
||||||
|
return new Vector2(
|
||||||
|
length * MathF.Cos(angle),
|
||||||
|
length * MathF.Sin(angle)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -37,15 +38,23 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
foreach (OsuHitObject hitObject in hitObjects)
|
foreach (OsuHitObject hitObject in hitObjects)
|
||||||
{
|
{
|
||||||
Vector2 relativePosition = hitObject.Position - previousPosition;
|
Vector2 relativePosition = hitObject.Position - previousPosition;
|
||||||
float absoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X);
|
float absoluteAngle = MathF.Atan2(relativePosition.Y, relativePosition.X);
|
||||||
float relativeAngle = absoluteAngle - previousAngle;
|
float relativeAngle = absoluteAngle - previousAngle;
|
||||||
|
|
||||||
positionInfos.Add(new ObjectPositionInfo(hitObject)
|
ObjectPositionInfo positionInfo;
|
||||||
|
positionInfos.Add(positionInfo = new ObjectPositionInfo(hitObject)
|
||||||
{
|
{
|
||||||
RelativeAngle = relativeAngle,
|
RelativeAngle = relativeAngle,
|
||||||
DistanceFromPrevious = relativePosition.Length
|
DistanceFromPrevious = relativePosition.Length
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (hitObject is Slider slider)
|
||||||
|
{
|
||||||
|
float absoluteRotation = getSliderRotation(slider);
|
||||||
|
positionInfo.Rotation = absoluteRotation - absoluteAngle;
|
||||||
|
absoluteAngle = absoluteRotation;
|
||||||
|
}
|
||||||
|
|
||||||
previousPosition = hitObject.EndPosition;
|
previousPosition = hitObject.EndPosition;
|
||||||
previousAngle = absoluteAngle;
|
previousAngle = absoluteAngle;
|
||||||
}
|
}
|
||||||
@ -70,7 +79,7 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
|
|
||||||
if (hitObject is Spinner)
|
if (hitObject is Spinner)
|
||||||
{
|
{
|
||||||
previous = null;
|
previous = current;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -124,16 +133,23 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
|
|
||||||
if (previous != null)
|
if (previous != null)
|
||||||
{
|
{
|
||||||
Vector2 earliestPosition = beforePrevious?.HitObject.EndPosition ?? playfield_centre;
|
if (previous.HitObject is Slider s)
|
||||||
Vector2 relativePosition = previous.HitObject.Position - earliestPosition;
|
{
|
||||||
previousAbsoluteAngle = (float)Math.Atan2(relativePosition.Y, relativePosition.X);
|
previousAbsoluteAngle = getSliderRotation(s);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Vector2 earliestPosition = beforePrevious?.HitObject.EndPosition ?? playfield_centre;
|
||||||
|
Vector2 relativePosition = previous.HitObject.Position - earliestPosition;
|
||||||
|
previousAbsoluteAngle = MathF.Atan2(relativePosition.Y, relativePosition.X);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
float absoluteAngle = previousAbsoluteAngle + current.PositionInfo.RelativeAngle;
|
float absoluteAngle = previousAbsoluteAngle + current.PositionInfo.RelativeAngle;
|
||||||
|
|
||||||
var posRelativeToPrev = new Vector2(
|
var posRelativeToPrev = new Vector2(
|
||||||
current.PositionInfo.DistanceFromPrevious * (float)Math.Cos(absoluteAngle),
|
current.PositionInfo.DistanceFromPrevious * MathF.Cos(absoluteAngle),
|
||||||
current.PositionInfo.DistanceFromPrevious * (float)Math.Sin(absoluteAngle)
|
current.PositionInfo.DistanceFromPrevious * MathF.Sin(absoluteAngle)
|
||||||
);
|
);
|
||||||
|
|
||||||
Vector2 lastEndPosition = previous?.EndPositionModified ?? playfield_centre;
|
Vector2 lastEndPosition = previous?.EndPositionModified ?? playfield_centre;
|
||||||
@ -141,6 +157,19 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
posRelativeToPrev = RotateAwayFromEdge(lastEndPosition, posRelativeToPrev);
|
posRelativeToPrev = RotateAwayFromEdge(lastEndPosition, posRelativeToPrev);
|
||||||
|
|
||||||
current.PositionModified = lastEndPosition + posRelativeToPrev;
|
current.PositionModified = lastEndPosition + posRelativeToPrev;
|
||||||
|
|
||||||
|
if (!(current.HitObject is Slider slider))
|
||||||
|
return;
|
||||||
|
|
||||||
|
absoluteAngle = MathF.Atan2(posRelativeToPrev.Y, posRelativeToPrev.X);
|
||||||
|
|
||||||
|
Vector2 centreOfMassOriginal = calculateCentreOfMass(slider);
|
||||||
|
Vector2 centreOfMassModified = rotateVector(centreOfMassOriginal, current.PositionInfo.Rotation + absoluteAngle - getSliderRotation(slider));
|
||||||
|
centreOfMassModified = RotateAwayFromEdge(current.PositionModified, centreOfMassModified);
|
||||||
|
|
||||||
|
float relativeRotation = MathF.Atan2(centreOfMassModified.Y, centreOfMassModified.X) - MathF.Atan2(centreOfMassOriginal.Y, centreOfMassOriginal.X);
|
||||||
|
if (!Precision.AlmostEquals(relativeRotation, 0))
|
||||||
|
RotateSlider(slider, relativeRotation);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -172,13 +201,13 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
var previousPosition = workingObject.PositionModified;
|
var previousPosition = workingObject.PositionModified;
|
||||||
|
|
||||||
// Clamp slider position to the placement area
|
// Clamp slider position to the placement area
|
||||||
// If the slider is larger than the playfield, force it to stay at the original position
|
// If the slider is larger than the playfield, at least make sure that the head circle is inside the playfield
|
||||||
float newX = possibleMovementBounds.Width < 0
|
float newX = possibleMovementBounds.Width < 0
|
||||||
? workingObject.PositionOriginal.X
|
? Math.Clamp(possibleMovementBounds.Left, 0, OsuPlayfield.BASE_SIZE.X)
|
||||||
: Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right);
|
: Math.Clamp(previousPosition.X, possibleMovementBounds.Left, possibleMovementBounds.Right);
|
||||||
|
|
||||||
float newY = possibleMovementBounds.Height < 0
|
float newY = possibleMovementBounds.Height < 0
|
||||||
? workingObject.PositionOriginal.Y
|
? Math.Clamp(possibleMovementBounds.Top, 0, OsuPlayfield.BASE_SIZE.Y)
|
||||||
: Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom);
|
: Math.Clamp(previousPosition.Y, possibleMovementBounds.Top, possibleMovementBounds.Bottom);
|
||||||
|
|
||||||
slider.Position = workingObject.PositionModified = new Vector2(newX, newY);
|
slider.Position = workingObject.PositionModified = new Vector2(newX, newY);
|
||||||
@ -287,6 +316,45 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Estimate the centre of mass of a slider relative to its start position.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slider">The slider to process.</param>
|
||||||
|
/// <returns>The centre of mass of the slider.</returns>
|
||||||
|
private static Vector2 calculateCentreOfMass(Slider slider)
|
||||||
|
{
|
||||||
|
const double sample_step = 50;
|
||||||
|
|
||||||
|
// just sample the start and end positions if the slider is too short
|
||||||
|
if (slider.Distance <= sample_step)
|
||||||
|
{
|
||||||
|
return Vector2.Divide(slider.Path.PositionAt(1), 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
int count = 0;
|
||||||
|
Vector2 sum = Vector2.Zero;
|
||||||
|
double pathDistance = slider.Distance;
|
||||||
|
|
||||||
|
for (double i = 0; i < pathDistance; i += sample_step)
|
||||||
|
{
|
||||||
|
sum += slider.Path.PositionAt(i / pathDistance);
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sum / count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Get the absolute rotation of a slider, defined as the angle from its start position to the end of its path.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="slider">The slider to process.</param>
|
||||||
|
/// <returns>The angle in radians.</returns>
|
||||||
|
private static float getSliderRotation(Slider slider)
|
||||||
|
{
|
||||||
|
var endPositionVector = slider.Path.PositionAt(1);
|
||||||
|
return MathF.Atan2(endPositionVector.Y, endPositionVector.X);
|
||||||
|
}
|
||||||
|
|
||||||
public class ObjectPositionInfo
|
public class ObjectPositionInfo
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -309,6 +377,13 @@ namespace osu.Game.Rulesets.Osu.Utils
|
|||||||
/// </remarks>
|
/// </remarks>
|
||||||
public float DistanceFromPrevious { get; set; }
|
public float DistanceFromPrevious { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The rotation of the hit object, relative to its jump angle.
|
||||||
|
/// For sliders, this is defined as the angle from the slider's start position to the end of its path, relative to its jump angle.
|
||||||
|
/// For hit circles and spinners, this property is ignored.
|
||||||
|
/// </summary>
|
||||||
|
public float Rotation { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The hit object associated with this <see cref="ObjectPositionInfo"/>.
|
/// The hit object associated with this <see cref="ObjectPositionInfo"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -8,6 +8,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Pooling;
|
using osu.Framework.Graphics.Pooling;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -321,12 +322,14 @@ namespace osu.Game.Rulesets.Taiko.UI
|
|||||||
|
|
||||||
private class ProxyContainer : LifetimeManagementContainer
|
private class ProxyContainer : LifetimeManagementContainer
|
||||||
{
|
{
|
||||||
public new MarginPadding Padding
|
|
||||||
{
|
|
||||||
set => base.Padding = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Add(Drawable proxy) => AddInternal(proxy);
|
public void Add(Drawable proxy) => AddInternal(proxy);
|
||||||
|
|
||||||
|
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds)
|
||||||
|
{
|
||||||
|
// DrawableHitObject disables masking.
|
||||||
|
// Hitobject content is proxied and unproxied based on hit status and the IsMaskedAway value could get stuck because of this.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Tests.Chat
|
|||||||
[SetUp]
|
[SetUp]
|
||||||
public void Setup() => Schedule(() =>
|
public void Setup() => Schedule(() =>
|
||||||
{
|
{
|
||||||
var container = new ChannelManagerContainer();
|
var container = new ChannelManagerContainer(API);
|
||||||
Child = container;
|
Child = container;
|
||||||
channelManager = container.ChannelManager;
|
channelManager = container.ChannelManager;
|
||||||
});
|
});
|
||||||
@ -145,11 +145,11 @@ namespace osu.Game.Tests.Chat
|
|||||||
private class ChannelManagerContainer : CompositeDrawable
|
private class ChannelManagerContainer : CompositeDrawable
|
||||||
{
|
{
|
||||||
[Cached]
|
[Cached]
|
||||||
public ChannelManager ChannelManager { get; } = new ChannelManager();
|
public ChannelManager ChannelManager { get; }
|
||||||
|
|
||||||
public ChannelManagerContainer()
|
public ChannelManagerContainer(IAPIProvider apiProvider)
|
||||||
{
|
{
|
||||||
InternalChild = ChannelManager;
|
InternalChild = ChannelManager = new ChannelManager(apiProvider);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -710,7 +710,7 @@ namespace osu.Game.Tests.Database
|
|||||||
|
|
||||||
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
var imported = await LoadOszIntoStore(importer, realm.Realm);
|
||||||
|
|
||||||
realm.Realm.Write(() =>
|
await realm.Realm.WriteAsync(() =>
|
||||||
{
|
{
|
||||||
foreach (var b in imported.Beatmaps)
|
foreach (var b in imported.Beatmaps)
|
||||||
b.OnlineID = -1;
|
b.OnlineID = -1;
|
||||||
|
@ -59,30 +59,34 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great });
|
scoreProcessor.ApplyResult(new JudgementResult(beatmap.HitObjects[0], new TestJudgement(HitResult.Great)) { Type = HitResult.Great });
|
||||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
|
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
|
||||||
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||||
|
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1));
|
||||||
|
|
||||||
// No header shouldn't cause any change
|
// No header shouldn't cause any change
|
||||||
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame());
|
scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame());
|
||||||
|
|
||||||
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
|
Assert.That(scoreProcessor.TotalScore.Value, Is.EqualTo(1_000_000));
|
||||||
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||||
|
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(1));
|
||||||
|
|
||||||
// Reset with a miss instead.
|
// Reset with a miss instead.
|
||||||
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame
|
scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame
|
||||||
{
|
{
|
||||||
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int> { { HitResult.Miss, 1 } }, DateTimeOffset.Now)
|
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int> { { HitResult.Miss, 1 } }, DateTimeOffset.Now)
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
||||||
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
Assert.That(scoreProcessor.JudgedHits, Is.EqualTo(1));
|
||||||
|
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
|
||||||
|
|
||||||
// Reset with no judged hit.
|
// Reset with no judged hit.
|
||||||
scoreProcessor.ResetFromReplayFrame(new OsuRuleset(), new OsuReplayFrame
|
scoreProcessor.ResetFromReplayFrame(new OsuReplayFrame
|
||||||
{
|
{
|
||||||
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int>(), DateTimeOffset.Now)
|
Header = new FrameHeader(0, 0, 0, new Dictionary<HitResult, int>(), DateTimeOffset.Now)
|
||||||
});
|
});
|
||||||
|
|
||||||
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
Assert.That(scoreProcessor.TotalScore.Value, Is.Zero);
|
||||||
Assert.That(scoreProcessor.JudgedHits, Is.Zero);
|
Assert.That(scoreProcessor.JudgedHits, Is.Zero);
|
||||||
|
Assert.That(scoreProcessor.Combo.Value, Is.EqualTo(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TestJudgement : Judgement
|
private class TestJudgement : Judgement
|
||||||
|
@ -59,11 +59,13 @@ namespace osu.Game.Tests.Skins
|
|||||||
AddAssert("Check float parse lookup", () => requester.GetConfig<string, float>("FloatTest")?.Value == 1.1f);
|
AddAssert("Check float parse lookup", () => requester.GetConfig<string, float>("FloatTest")?.Value == 1.1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[TestCase("0", false)]
|
||||||
public void TestBoolLookup()
|
[TestCase("1", true)]
|
||||||
|
[TestCase("2", true)] // https://github.com/ppy/osu/issues/18579
|
||||||
|
public void TestBoolLookup(string originalValue, bool expectedParsedValue)
|
||||||
{
|
{
|
||||||
AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = "1");
|
AddStep("Add config values", () => userSource.Configuration.ConfigDictionary["BoolTest"] = originalValue);
|
||||||
AddAssert("Check bool parse lookup", () => requester.GetConfig<string, bool>("BoolTest")?.Value == true);
|
AddAssert("Check bool parse lookup", () => requester.GetConfig<string, bool>("BoolTest")?.Value == expectedParsedValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
48
osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs
Normal file
48
osu.Game.Tests/Visual/Editing/TestSceneTapButton.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Screens.Edit.Timing;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public class TestSceneTapButton : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private TapButton tapButton;
|
||||||
|
|
||||||
|
[Cached]
|
||||||
|
private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestBasic()
|
||||||
|
{
|
||||||
|
AddStep("create button", () =>
|
||||||
|
{
|
||||||
|
Child = tapButton = new TapButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Scale = new Vector2(4),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
bool pressed = false;
|
||||||
|
|
||||||
|
AddRepeatStep("Press button", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(tapButton);
|
||||||
|
if (!pressed)
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
else
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
|
||||||
|
pressed = !pressed;
|
||||||
|
}, 100);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Timing;
|
using osu.Game.Screens.Edit.Timing;
|
||||||
@ -77,28 +77,6 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
timingInfo.Text = $"offset: {selectedGroup.Value.Time:N2} bpm: {selectedGroup.Value.ControlPoints.OfType<TimingControlPoint>().First().BPM:N2}";
|
timingInfo.Text = $"offset: {selectedGroup.Value.Time:N2} bpm: {selectedGroup.Value.ControlPoints.OfType<TimingControlPoint>().First().BPM:N2}";
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestTapThenReset()
|
|
||||||
{
|
|
||||||
AddStep("click tap button", () =>
|
|
||||||
{
|
|
||||||
control.ChildrenOfType<RoundedButton>()
|
|
||||||
.Last()
|
|
||||||
.TriggerClick();
|
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("wait for track playing", () => Clock.IsRunning);
|
|
||||||
|
|
||||||
AddStep("click reset button", () =>
|
|
||||||
{
|
|
||||||
control.ChildrenOfType<RoundedButton>()
|
|
||||||
.First()
|
|
||||||
.TriggerClick();
|
|
||||||
});
|
|
||||||
|
|
||||||
AddUntilStep("wait for track stopped", () => !Clock.IsRunning);
|
|
||||||
}
|
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestBasic()
|
public void TestBasic()
|
||||||
{
|
{
|
||||||
@ -109,7 +87,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
|
|
||||||
AddStep("click tap button", () =>
|
AddStep("click tap button", () =>
|
||||||
{
|
{
|
||||||
control.ChildrenOfType<RoundedButton>()
|
control.ChildrenOfType<OsuButton>()
|
||||||
.Last()
|
.Last()
|
||||||
.TriggerClick();
|
.TriggerClick();
|
||||||
});
|
});
|
||||||
@ -123,6 +101,28 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTapThenReset()
|
||||||
|
{
|
||||||
|
AddStep("click tap button", () =>
|
||||||
|
{
|
||||||
|
control.ChildrenOfType<OsuButton>()
|
||||||
|
.Last()
|
||||||
|
.TriggerClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for track playing", () => Clock.IsRunning);
|
||||||
|
|
||||||
|
AddStep("click reset button", () =>
|
||||||
|
{
|
||||||
|
control.ChildrenOfType<OsuButton>()
|
||||||
|
.First()
|
||||||
|
.TriggerClick();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for track stopped", () => !Clock.IsRunning);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
Beatmap.Disabled = false;
|
Beatmap.Disabled = false;
|
||||||
|
48
osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs
Normal file
48
osu.Game.Tests/Visual/Editing/TestSceneTimelineZoom.cs
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
// 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 NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
|
{
|
||||||
|
public class TestSceneTimelineZoom : TimelineTestScene
|
||||||
|
{
|
||||||
|
public override Drawable CreateTestComponent() => Empty();
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVisibleRangeUpdatesOnZoomChange()
|
||||||
|
{
|
||||||
|
double initialVisibleRange = 0;
|
||||||
|
|
||||||
|
AddStep("reset zoom", () => TimelineArea.Timeline.Zoom = 100);
|
||||||
|
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
|
||||||
|
|
||||||
|
AddStep("scale zoom", () => TimelineArea.Timeline.Zoom = 200);
|
||||||
|
AddAssert("range halved", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange / 2, 1));
|
||||||
|
AddStep("descale zoom", () => TimelineArea.Timeline.Zoom = 50);
|
||||||
|
AddAssert("range doubled", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange * 2, 1));
|
||||||
|
|
||||||
|
AddStep("restore zoom", () => TimelineArea.Timeline.Zoom = 100);
|
||||||
|
AddAssert("range restored", () => Precision.AlmostEquals(TimelineArea.Timeline.VisibleRange, initialVisibleRange, 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestVisibleRangeConstantOnSizeChange()
|
||||||
|
{
|
||||||
|
double initialVisibleRange = 0;
|
||||||
|
|
||||||
|
AddStep("reset timeline size", () => TimelineArea.Timeline.Width = 1);
|
||||||
|
AddStep("get initial range", () => initialVisibleRange = TimelineArea.Timeline.VisibleRange);
|
||||||
|
|
||||||
|
AddStep("scale timeline size", () => TimelineArea.Timeline.Width = 2);
|
||||||
|
AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange);
|
||||||
|
AddStep("descale timeline size", () => TimelineArea.Timeline.Width = 0.5f);
|
||||||
|
AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange);
|
||||||
|
|
||||||
|
AddStep("restore timeline size", () => TimelineArea.Timeline.Width = 1);
|
||||||
|
AddAssert("same range", () => TimelineArea.Timeline.VisibleRange == initialVisibleRange);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,14 +1,18 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Timing;
|
using osu.Game.Screens.Edit.Timing;
|
||||||
|
using osu.Game.Screens.Edit.Timing.RowAttributes;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Editing
|
namespace osu.Game.Tests.Visual.Editing
|
||||||
{
|
{
|
||||||
@ -22,6 +26,8 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
|
||||||
|
|
||||||
|
private TimingScreen timingScreen;
|
||||||
|
|
||||||
protected override bool ScrollUsingMouseWheel => false;
|
protected override bool ScrollUsingMouseWheel => false;
|
||||||
|
|
||||||
public TestSceneTimingScreen()
|
public TestSceneTimingScreen()
|
||||||
@ -36,12 +42,54 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
|
Beatmap.Value = CreateWorkingBeatmap(editorBeatmap.PlayableBeatmap);
|
||||||
Beatmap.Disabled = true;
|
Beatmap.Disabled = true;
|
||||||
|
|
||||||
Child = new TimingScreen
|
Child = timingScreen = new TimingScreen
|
||||||
{
|
{
|
||||||
State = { Value = Visibility.Visible },
|
State = { Value = Visibility.Visible },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("Stop clock", () => Clock.Stop());
|
||||||
|
|
||||||
|
AddUntilStep("wait for rows to load", () => Child.ChildrenOfType<EffectRowAttribute>().Any());
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTrackingCurrentTimeWhileRunning()
|
||||||
|
{
|
||||||
|
AddStep("Select first effect point", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(Child.ChildrenOfType<EffectRowAttribute>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670);
|
||||||
|
AddUntilStep("Ensure seeked to correct time", () => Clock.CurrentTimeAccurate == 54670);
|
||||||
|
|
||||||
|
AddStep("Seek to just before next point", () => Clock.Seek(69000));
|
||||||
|
AddStep("Start clock", () => Clock.Start());
|
||||||
|
|
||||||
|
AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestTrackingCurrentTimeWhilePaused()
|
||||||
|
{
|
||||||
|
AddStep("Select first effect point", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(Child.ChildrenOfType<EffectRowAttribute>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 54670);
|
||||||
|
AddUntilStep("Ensure seeked to correct time", () => Clock.CurrentTimeAccurate == 54670);
|
||||||
|
|
||||||
|
AddStep("Seek to later", () => Clock.Seek(80000));
|
||||||
|
AddUntilStep("Selection changed", () => timingScreen.SelectedGroup.Value.Time == 69670);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
{
|
{
|
||||||
Beatmap.Disabled = false;
|
Beatmap.Disabled = false;
|
||||||
|
@ -44,7 +44,12 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = OsuColour.Gray(30)
|
Colour = OsuColour.Gray(30)
|
||||||
},
|
},
|
||||||
scrollContainer = new ZoomableScrollContainer { RelativeSizeAxes = Axes.Both }
|
scrollContainer = new ZoomableScrollContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new MenuCursor()
|
new MenuCursor()
|
||||||
@ -62,7 +67,15 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestWidthInitialization()
|
public void TestWidthInitialization()
|
||||||
{
|
{
|
||||||
AddAssert("Inner container width was initialized", () => innerBox.DrawWidth > 0);
|
AddAssert("Inner container width was initialized", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestWidthUpdatesOnDrawSizeChanges()
|
||||||
|
{
|
||||||
|
AddStep("Shrink scroll container", () => scrollContainer.Width = 0.5f);
|
||||||
|
AddAssert("Scroll container width shrunk", () => scrollContainer.DrawWidth == scrollContainer.Parent.DrawWidth / 2);
|
||||||
|
AddAssert("Inner container width matches scroll container", () => innerBox.DrawWidth == scrollContainer.DrawWidth);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
|
@ -18,7 +18,6 @@ using osu.Game.Online.API.Requests.Responses;
|
|||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Spectator;
|
using osu.Game.Online.Spectator;
|
||||||
using osu.Game.Replays.Legacy;
|
using osu.Game.Replays.Legacy;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
@ -27,7 +26,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
public abstract class MultiplayerGameplayLeaderboardTestScene : OsuTestScene
|
public abstract class MultiplayerGameplayLeaderboardTestScene : OsuTestScene
|
||||||
{
|
{
|
||||||
private const int total_users = 16;
|
protected const int TOTAL_USERS = 16;
|
||||||
|
|
||||||
protected readonly BindableList<MultiplayerRoomUser> MultiplayerUsers = new BindableList<MultiplayerRoomUser>();
|
protected readonly BindableList<MultiplayerRoomUser> MultiplayerUsers = new BindableList<MultiplayerRoomUser>();
|
||||||
|
|
||||||
@ -35,9 +34,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
protected virtual MultiplayerRoomUser CreateUser(int userId) => new MultiplayerRoomUser(userId);
|
protected virtual MultiplayerRoomUser CreateUser(int userId) => new MultiplayerRoomUser(userId);
|
||||||
|
|
||||||
protected abstract MultiplayerGameplayLeaderboard CreateLeaderboard(OsuScoreProcessor scoreProcessor);
|
protected abstract MultiplayerGameplayLeaderboard CreateLeaderboard();
|
||||||
|
|
||||||
private readonly BindableList<int> multiplayerUserIds = new BindableList<int>();
|
private readonly BindableList<int> multiplayerUserIds = new BindableList<int>();
|
||||||
|
private readonly BindableDictionary<int, SpectatorState> watchedUserStates = new BindableDictionary<int, SpectatorState>();
|
||||||
|
|
||||||
private OsuConfigManager config;
|
private OsuConfigManager config;
|
||||||
|
|
||||||
@ -81,6 +81,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
|
|
||||||
multiplayerClient.SetupGet(c => c.CurrentMatchPlayingUserIds)
|
multiplayerClient.SetupGet(c => c.CurrentMatchPlayingUserIds)
|
||||||
.Returns(() => multiplayerUserIds);
|
.Returns(() => multiplayerUserIds);
|
||||||
|
|
||||||
|
spectatorClient.SetupGet(c => c.WatchedUserStates)
|
||||||
|
.Returns(() => watchedUserStates);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
@ -100,8 +103,26 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
AddStep("populate users", () =>
|
AddStep("populate users", () =>
|
||||||
{
|
{
|
||||||
MultiplayerUsers.Clear();
|
MultiplayerUsers.Clear();
|
||||||
for (int i = 0; i < total_users; i++)
|
|
||||||
MultiplayerUsers.Add(CreateUser(i));
|
for (int i = 0; i < TOTAL_USERS; i++)
|
||||||
|
{
|
||||||
|
var user = CreateUser(i);
|
||||||
|
|
||||||
|
MultiplayerUsers.Add(user);
|
||||||
|
|
||||||
|
watchedUserStates[i] = new SpectatorState
|
||||||
|
{
|
||||||
|
BeatmapID = 0,
|
||||||
|
RulesetID = 0,
|
||||||
|
Mods = user.Mods,
|
||||||
|
MaximumScoringValues = new ScoringValues
|
||||||
|
{
|
||||||
|
BaseScore = 10000,
|
||||||
|
MaxCombo = 1000,
|
||||||
|
CountBasicHitObjects = 1000
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("create leaderboard", () =>
|
AddStep("create leaderboard", () =>
|
||||||
@ -109,13 +130,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
Leaderboard?.Expire();
|
Leaderboard?.Expire();
|
||||||
|
|
||||||
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||||
var playableBeatmap = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
|
|
||||||
OsuScoreProcessor scoreProcessor = new OsuScoreProcessor();
|
|
||||||
scoreProcessor.ApplyBeatmap(playableBeatmap);
|
|
||||||
|
|
||||||
Child = scoreProcessor;
|
LoadComponentAsync(Leaderboard = CreateLeaderboard(), Add);
|
||||||
|
|
||||||
LoadComponentAsync(Leaderboard = CreateLeaderboard(scoreProcessor), Add);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
AddUntilStep("wait for load", () => Leaderboard.IsLoaded);
|
AddUntilStep("wait for load", () => Leaderboard.IsLoaded);
|
||||||
|
@ -4,11 +4,11 @@
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
using osu.Game.Screens.OnlinePlay.Multiplayer.Spectate;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
|
||||||
@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{
|
{
|
||||||
AddStep("reset", () =>
|
AddStep("reset", () =>
|
||||||
{
|
{
|
||||||
Clear();
|
leaderboard?.RemoveAndDisposeImmediately();
|
||||||
|
|
||||||
clocks = new Dictionary<int, ManualClock>
|
clocks = new Dictionary<int, ManualClock>
|
||||||
{
|
{
|
||||||
@ -32,21 +32,18 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
{ PLAYER_2_ID, new ManualClock() }
|
{ PLAYER_2_ID, new ManualClock() }
|
||||||
};
|
};
|
||||||
|
|
||||||
foreach ((int userId, var _) in clocks)
|
foreach ((int userId, _) in clocks)
|
||||||
{
|
{
|
||||||
SpectatorClient.SendStartPlay(userId, 0);
|
SpectatorClient.SendStartPlay(userId, 0);
|
||||||
OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = userId });
|
OnlinePlayDependencies.MultiplayerClient.AddUser(new APIUser { Id = userId }, true);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
AddStep("create leaderboard", () =>
|
AddStep("create leaderboard", () =>
|
||||||
{
|
{
|
||||||
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
Beatmap.Value = CreateWorkingBeatmap(Ruleset.Value);
|
||||||
var playable = Beatmap.Value.GetPlayableBeatmap(Ruleset.Value);
|
|
||||||
var scoreProcessor = new OsuScoreProcessor();
|
|
||||||
scoreProcessor.ApplyBeatmap(playable);
|
|
||||||
|
|
||||||
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(Ruleset.Value, scoreProcessor, clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray())
|
LoadComponentAsync(leaderboard = new MultiSpectatorLeaderboard(clocks.Keys.Select(id => new MultiplayerRoomUser(id)).ToArray())
|
||||||
{
|
{
|
||||||
Expanded = { Value = true }
|
Expanded = { Value = true }
|
||||||
}, Add);
|
}, Add);
|
||||||
|
@ -1,22 +1,58 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.Multiplayer;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Multiplayer
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
{
|
{
|
||||||
public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerGameplayLeaderboardTestScene
|
public class TestSceneMultiplayerGameplayLeaderboard : MultiplayerGameplayLeaderboardTestScene
|
||||||
{
|
{
|
||||||
protected override MultiplayerGameplayLeaderboard CreateLeaderboard(OsuScoreProcessor scoreProcessor)
|
protected override MultiplayerRoomUser CreateUser(int userId)
|
||||||
{
|
{
|
||||||
return new MultiplayerGameplayLeaderboard(Ruleset.Value, scoreProcessor, MultiplayerUsers.ToArray())
|
var user = base.CreateUser(userId);
|
||||||
|
|
||||||
|
if (userId == TOTAL_USERS - 1)
|
||||||
|
user.Mods = new[] { new APIMod(new OsuModNoFail()) };
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override MultiplayerGameplayLeaderboard CreateLeaderboard()
|
||||||
|
{
|
||||||
|
return new TestLeaderboard(MultiplayerUsers.ToArray())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPerUserMods()
|
||||||
|
{
|
||||||
|
AddStep("first user has no mods", () => Assert.That(((TestLeaderboard)Leaderboard).UserMods[0], Is.Empty));
|
||||||
|
AddStep("last user has NF mod", () =>
|
||||||
|
{
|
||||||
|
Assert.That(((TestLeaderboard)Leaderboard).UserMods[TOTAL_USERS - 1], Has.One.Items);
|
||||||
|
Assert.That(((TestLeaderboard)Leaderboard).UserMods[TOTAL_USERS - 1].Single(), Is.TypeOf<OsuModNoFail>());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestLeaderboard : MultiplayerGameplayLeaderboard
|
||||||
|
{
|
||||||
|
public Dictionary<int, IReadOnlyList<Mod>> UserMods => UserScores.ToDictionary(kvp => kvp.Key, kvp => kvp.Value.ScoreProcessor.Mods);
|
||||||
|
|
||||||
|
public TestLeaderboard(MultiplayerRoomUser[] users)
|
||||||
|
: base(users)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ using System.Linq;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Online.Multiplayer;
|
using osu.Game.Online.Multiplayer;
|
||||||
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
using osu.Game.Online.Multiplayer.MatchTypes.TeamVersus;
|
||||||
using osu.Game.Rulesets.Osu.Scoring;
|
|
||||||
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
using osu.Game.Screens.OnlinePlay.Multiplayer;
|
||||||
using osu.Game.Screens.Play.HUD;
|
using osu.Game.Screens.Play.HUD;
|
||||||
|
|
||||||
@ -25,8 +24,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
|||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override MultiplayerGameplayLeaderboard CreateLeaderboard(OsuScoreProcessor scoreProcessor) =>
|
protected override MultiplayerGameplayLeaderboard CreateLeaderboard() =>
|
||||||
new MultiplayerGameplayLeaderboard(Ruleset.Value, scoreProcessor, MultiplayerUsers.ToArray())
|
new MultiplayerGameplayLeaderboard(MultiplayerUsers.ToArray())
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
|
@ -609,8 +609,6 @@ namespace osu.Game.Tests.Visual.Navigation
|
|||||||
public ModSelectOverlay ModSelectOverlay => ModSelect;
|
public ModSelectOverlay ModSelectOverlay => ModSelect;
|
||||||
|
|
||||||
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;
|
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;
|
||||||
|
|
||||||
protected override bool DisplayStableImportPrompt => false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
{
|
{
|
||||||
linkColour = colours.Blue;
|
linkColour = colours.Blue;
|
||||||
|
|
||||||
var chatManager = new ChannelManager();
|
var chatManager = new ChannelManager(API);
|
||||||
BindableList<Channel> availableChannels = (BindableList<Channel>)chatManager.AvailableChannels;
|
BindableList<Channel> availableChannels = (BindableList<Channel>)chatManager.AvailableChannels;
|
||||||
availableChannels.Add(new Channel { Name = "#english" });
|
availableChannels.Add(new Channel { Name = "#english" });
|
||||||
availableChannels.Add(new Channel { Name = "#japanese" });
|
availableChannels.Add(new Channel { Name = "#japanese" });
|
||||||
|
@ -59,7 +59,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
CachedDependencies = new (Type, object)[]
|
CachedDependencies = new (Type, object)[]
|
||||||
{
|
{
|
||||||
(typeof(ChannelManager), channelManager = new ChannelManager()),
|
(typeof(ChannelManager), channelManager = new ChannelManager(API)),
|
||||||
},
|
},
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -469,6 +469,8 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
chatOverlay.Show();
|
chatOverlay.Show();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
|
||||||
|
|
||||||
waitForChannel1Visible();
|
waitForChannel1Visible();
|
||||||
AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
|
AddStep("Press document next keys", () => InputManager.Keys(PlatformAction.DocumentNext));
|
||||||
waitForChannel2Visible();
|
waitForChannel2Visible();
|
||||||
@ -570,15 +572,15 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
public SlowLoadingDrawableChannel GetSlowLoadingChannel(Channel channel) => DrawableChannels.OfType<SlowLoadingDrawableChannel>().Single(c => c.Channel == channel);
|
public SlowLoadingDrawableChannel GetSlowLoadingChannel(Channel channel) => DrawableChannels.OfType<SlowLoadingDrawableChannel>().Single(c => c.Channel == channel);
|
||||||
|
|
||||||
protected override ChatOverlayDrawableChannel CreateDrawableChannel(Channel newChannel)
|
protected override DrawableChannel CreateDrawableChannel(Channel newChannel)
|
||||||
{
|
{
|
||||||
return SlowLoading
|
return SlowLoading
|
||||||
? new SlowLoadingDrawableChannel(newChannel)
|
? new SlowLoadingDrawableChannel(newChannel)
|
||||||
: new ChatOverlayDrawableChannel(newChannel);
|
: new DrawableChannel(newChannel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class SlowLoadingDrawableChannel : ChatOverlayDrawableChannel
|
private class SlowLoadingDrawableChannel : DrawableChannel
|
||||||
{
|
{
|
||||||
public readonly ManualResetEventSlim LoadEvent = new ManualResetEventSlim();
|
public readonly ManualResetEventSlim LoadEvent = new ManualResetEventSlim();
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
Schedule(() =>
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
Child = testContainer = new TestContainer(new[] { publicChannel, privateMessageChannel })
|
Child = testContainer = new TestContainer(API, new[] { publicChannel, privateMessageChannel })
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
};
|
};
|
||||||
@ -178,6 +179,36 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
|
AddAssert("1 notification fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures that <see cref="MessageNotifier"/> handles channels which have not been or could not be resolved (i.e. <see cref="Channel.Id"/> = 0).
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestSendInUnresolvedChannel()
|
||||||
|
{
|
||||||
|
int i = 1;
|
||||||
|
Channel unresolved = null;
|
||||||
|
|
||||||
|
AddRepeatStep("join unresolved channels", () => testContainer.ChannelManager.JoinChannel(unresolved = new Channel(new APIUser
|
||||||
|
{
|
||||||
|
Id = 100 + i,
|
||||||
|
Username = $"Foreign #{i++}",
|
||||||
|
})), 5);
|
||||||
|
|
||||||
|
AddStep("send message in unresolved channel", () =>
|
||||||
|
{
|
||||||
|
Debug.Assert(unresolved.Id == 0);
|
||||||
|
|
||||||
|
unresolved.AddLocalEcho(new LocalEchoMessage
|
||||||
|
{
|
||||||
|
Sender = API.LocalUser.Value,
|
||||||
|
ChannelId = unresolved.Id,
|
||||||
|
Content = "Some message",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("no notifications fired", () => testContainer.NotificationOverlay.UnreadCount.Value == 0);
|
||||||
|
}
|
||||||
|
|
||||||
private void receiveMessage(APIUser sender, Channel channel, string content) => channel.AddNewMessages(createMessage(sender, channel, content));
|
private void receiveMessage(APIUser sender, Channel channel, string content) => channel.AddNewMessages(createMessage(sender, channel, content));
|
||||||
|
|
||||||
private Message createMessage(APIUser sender, Channel channel, string content) => new Message(messageIdCounter++)
|
private Message createMessage(APIUser sender, Channel channel, string content) => new Message(messageIdCounter++)
|
||||||
@ -198,7 +229,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
private class TestContainer : Container
|
private class TestContainer : Container
|
||||||
{
|
{
|
||||||
[Cached]
|
[Cached]
|
||||||
public ChannelManager ChannelManager { get; } = new ChannelManager();
|
public ChannelManager ChannelManager { get; }
|
||||||
|
|
||||||
[Cached(typeof(INotificationOverlay))]
|
[Cached(typeof(INotificationOverlay))]
|
||||||
public NotificationOverlay NotificationOverlay { get; } = new NotificationOverlay
|
public NotificationOverlay NotificationOverlay { get; } = new NotificationOverlay
|
||||||
@ -214,9 +245,10 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
private readonly Channel[] channels;
|
private readonly Channel[] channels;
|
||||||
|
|
||||||
public TestContainer(Channel[] channels)
|
public TestContainer(IAPIProvider api, Channel[] channels)
|
||||||
{
|
{
|
||||||
this.channels = channels;
|
this.channels = channels;
|
||||||
|
ChannelManager = new ChannelManager(api);
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -11,6 +11,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays.Chat;
|
using osu.Game.Overlays.Chat;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -44,17 +45,22 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
Id = 5,
|
Id = 5,
|
||||||
};
|
};
|
||||||
|
|
||||||
[Cached]
|
private ChannelManager channelManager;
|
||||||
private ChannelManager channelManager = new ChannelManager();
|
|
||||||
|
|
||||||
private TestStandAloneChatDisplay chatDisplay;
|
private TestStandAloneChatDisplay chatDisplay;
|
||||||
private int messageIdSequence;
|
private int messageIdSequence;
|
||||||
|
|
||||||
private Channel testChannel;
|
private Channel testChannel;
|
||||||
|
|
||||||
public TestSceneStandAloneChatDisplay()
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
|
||||||
{
|
{
|
||||||
Add(channelManager);
|
Add(channelManager = new ChannelManager(parent.Get<IAPIProvider>()));
|
||||||
|
|
||||||
|
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
|
|
||||||
|
dependencies.Cache(channelManager);
|
||||||
|
|
||||||
|
return dependencies;
|
||||||
}
|
}
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
@ -128,11 +134,11 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
AddAssert("Ensure no adjacent day separators", () =>
|
AddAssert("Ensure no adjacent day separators", () =>
|
||||||
{
|
{
|
||||||
var indices = chatDisplay.FillFlow.OfType<DrawableChannel.DaySeparator>().Select(ds => chatDisplay.FillFlow.IndexOf(ds));
|
var indices = chatDisplay.FillFlow.OfType<DaySeparator>().Select(ds => chatDisplay.FillFlow.IndexOf(ds));
|
||||||
|
|
||||||
foreach (int i in indices)
|
foreach (int i in indices)
|
||||||
{
|
{
|
||||||
if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DrawableChannel.DaySeparator)
|
if (i < chatDisplay.FillFlow.Count && chatDisplay.FillFlow[i + 1] is DaySeparator)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -173,6 +173,8 @@ namespace osu.Game.Tests.Visual.Playlists
|
|||||||
{
|
{
|
||||||
AddUntilStep("wait for scores loaded", () =>
|
AddUntilStep("wait for scores loaded", () =>
|
||||||
requestComplete
|
requestComplete
|
||||||
|
// request handler may need to fire more than once to get scores.
|
||||||
|
&& totalCount > 0
|
||||||
&& resultsScreen.ScorePanelList.GetScorePanels().Count() == totalCount
|
&& resultsScreen.ScorePanelList.GetScorePanels().Count() == totalCount
|
||||||
&& resultsScreen.ScorePanelList.AllPanelsVisible);
|
&& resultsScreen.ScorePanelList.AllPanelsVisible);
|
||||||
AddWaitStep("wait for display", 5);
|
AddWaitStep("wait for display", 5);
|
||||||
|
@ -96,6 +96,7 @@ namespace osu.Game.Tests.Visual.Ranking
|
|||||||
beatmap.Metadata.Author = author;
|
beatmap.Metadata.Author = author;
|
||||||
beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title";
|
beatmap.Metadata.Title = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap title";
|
||||||
beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist";
|
beatmap.Metadata.Artist = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong beatmap artist";
|
||||||
|
beatmap.DifficultyName = "Verrrrrrrrrrrrrrrrrrry looooooooooooooooooooooooong difficulty name";
|
||||||
|
|
||||||
return beatmap;
|
return beatmap;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
@ -83,7 +84,7 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
AddStep("clear label", () => textBox.LabelText = default);
|
AddStep("clear label", () => textBox.LabelText = default);
|
||||||
AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(restoreDefaultValueButton.Parent.DrawHeight, control.DrawHeight, 1));
|
AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(restoreDefaultValueButton.Parent.DrawHeight, control.DrawHeight, 1));
|
||||||
|
|
||||||
AddStep("set warning text", () => textBox.WarningText = "This is some very important warning text! Hopefully it doesn't break the alignment of the default value indicator...");
|
AddStep("set warning text", () => textBox.SetNoticeText("This is some very important warning text! Hopefully it doesn't break the alignment of the default value indicator...", true));
|
||||||
AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(restoreDefaultValueButton.Parent.DrawHeight, control.DrawHeight, 1));
|
AddAssert("default value button centre aligned to control size", () => Precision.AlmostEquals(restoreDefaultValueButton.Parent.DrawHeight, control.DrawHeight, 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,16 +130,18 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
SettingsNumberBox numberBox = null;
|
SettingsNumberBox numberBox = null;
|
||||||
|
|
||||||
AddStep("create settings item", () => Child = numberBox = new SettingsNumberBox());
|
AddStep("create settings item", () => Child = numberBox = new SettingsNumberBox());
|
||||||
AddAssert("warning text not created", () => !numberBox.ChildrenOfType<SettingsNoticeText>().Any());
|
AddAssert("warning text not created", () => !numberBox.ChildrenOfType<LinkFlowContainer>().Any());
|
||||||
|
|
||||||
AddStep("set warning text", () => numberBox.WarningText = "this is a warning!");
|
AddStep("set warning text", () => numberBox.SetNoticeText("this is a warning!", true));
|
||||||
AddAssert("warning text created", () => numberBox.ChildrenOfType<SettingsNoticeText>().Single().Alpha == 1);
|
AddAssert("warning text created", () => numberBox.ChildrenOfType<LinkFlowContainer>().Single().Alpha == 1);
|
||||||
|
|
||||||
AddStep("unset warning text", () => numberBox.WarningText = default);
|
AddStep("unset warning text", () => numberBox.ClearNoticeText());
|
||||||
AddAssert("warning text hidden", () => numberBox.ChildrenOfType<SettingsNoticeText>().Single().Alpha == 0);
|
AddAssert("warning text hidden", () => !numberBox.ChildrenOfType<LinkFlowContainer>().Any());
|
||||||
|
|
||||||
AddStep("set warning text again", () => numberBox.WarningText = "another warning!");
|
AddStep("set warning text again", () => numberBox.SetNoticeText("another warning!", true));
|
||||||
AddAssert("warning text shown again", () => numberBox.ChildrenOfType<SettingsNoticeText>().Single().Alpha == 1);
|
AddAssert("warning text shown again", () => numberBox.ChildrenOfType<LinkFlowContainer>().Single().Alpha == 1);
|
||||||
|
|
||||||
|
AddStep("set non warning text", () => numberBox.SetNoticeText("you did good!"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,11 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Online.API;
|
|
||||||
using osu.Game.Online.API.Requests;
|
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Catch;
|
using osu.Game.Rulesets.Catch;
|
||||||
using osu.Game.Rulesets.Mania;
|
using osu.Game.Rulesets.Mania;
|
||||||
@ -23,38 +22,28 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
{
|
{
|
||||||
public class TestSceneBeatmapRecommendations : OsuGameTestScene
|
public class TestSceneBeatmapRecommendations : OsuGameTestScene
|
||||||
{
|
{
|
||||||
|
[Resolved]
|
||||||
|
private IRulesetStore rulesetStore { get; set; }
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public override void SetUpSteps()
|
public override void SetUpSteps()
|
||||||
{
|
{
|
||||||
AddStep("register request handling", () =>
|
|
||||||
{
|
|
||||||
((DummyAPIAccess)API).HandleRequest = req =>
|
|
||||||
{
|
|
||||||
switch (req)
|
|
||||||
{
|
|
||||||
case GetUserRequest userRequest:
|
|
||||||
userRequest.TriggerSuccess(getUser(userRequest.Ruleset.OnlineID));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
base.SetUpSteps();
|
base.SetUpSteps();
|
||||||
|
|
||||||
APIUser getUser(int? rulesetID)
|
AddStep("populate ruleset statistics", () =>
|
||||||
{
|
{
|
||||||
return new APIUser
|
Dictionary<string, UserStatistics> rulesetStatistics = new Dictionary<string, UserStatistics>();
|
||||||
|
|
||||||
|
rulesetStore.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset()).ForEach(rulesetInfo =>
|
||||||
{
|
{
|
||||||
Username = @"Dummy",
|
rulesetStatistics[rulesetInfo.ShortName] = new UserStatistics
|
||||||
Id = 1001,
|
|
||||||
Statistics = new UserStatistics
|
|
||||||
{
|
{
|
||||||
PP = getNecessaryPP(rulesetID)
|
PP = getNecessaryPP(rulesetInfo.OnlineID)
|
||||||
}
|
};
|
||||||
};
|
});
|
||||||
}
|
|
||||||
|
API.LocalUser.Value.RulesetsStatistics = rulesetStatistics;
|
||||||
|
});
|
||||||
|
|
||||||
decimal getNecessaryPP(int? rulesetID)
|
decimal getNecessaryPP(int? rulesetID)
|
||||||
{
|
{
|
||||||
|
@ -18,6 +18,7 @@ using osu.Game.Database;
|
|||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Chat;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Mods;
|
using osu.Game.Overlays.Mods;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
@ -80,6 +81,37 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
AddStep("delete all beatmaps", () => manager?.Delete());
|
AddStep("delete all beatmaps", () => manager?.Delete());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlaceholderBeatmapPresence()
|
||||||
|
{
|
||||||
|
createSongSelect();
|
||||||
|
|
||||||
|
AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible);
|
||||||
|
|
||||||
|
addRulesetImportStep(0);
|
||||||
|
AddUntilStep("wait for placeholder hidden", () => getPlaceholder()?.State.Value == Visibility.Hidden);
|
||||||
|
|
||||||
|
AddStep("delete all beatmaps", () => manager?.Delete());
|
||||||
|
AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlaceholderConvertSetting()
|
||||||
|
{
|
||||||
|
changeRuleset(2);
|
||||||
|
addRulesetImportStep(0);
|
||||||
|
AddStep("change convert setting", () => config.SetValue(OsuSetting.ShowConvertedBeatmaps, false));
|
||||||
|
|
||||||
|
createSongSelect();
|
||||||
|
|
||||||
|
AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible);
|
||||||
|
|
||||||
|
AddStep("click link in placeholder", () => getPlaceholder().ChildrenOfType<DrawableLinkCompiler>().First().TriggerClick());
|
||||||
|
|
||||||
|
AddUntilStep("convert setting changed", () => config.Get<bool>(OsuSetting.ShowConvertedBeatmaps));
|
||||||
|
AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Hidden);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestSingleFilterOnEnter()
|
public void TestSingleFilterOnEnter()
|
||||||
{
|
{
|
||||||
@ -941,6 +973,8 @@ namespace osu.Game.Tests.Visual.SongSelect
|
|||||||
|
|
||||||
private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.IndexOf(info);
|
private int getBeatmapIndex(BeatmapSetInfo set, BeatmapInfo info) => set.Beatmaps.IndexOf(info);
|
||||||
|
|
||||||
|
private NoResultsPlaceholder getPlaceholder() => songSelect.ChildrenOfType<NoResultsPlaceholder>().FirstOrDefault();
|
||||||
|
|
||||||
private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmapInfo);
|
private int getCurrentBeatmapIndex() => getBeatmapIndex(songSelect.Carousel.SelectedBeatmapSet, songSelect.Carousel.SelectedBeatmapInfo);
|
||||||
|
|
||||||
private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon)
|
private int getDifficultyIconIndex(DrawableCarouselBeatmapSet set, FilterableDifficultyIcon icon)
|
||||||
|
@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using osu.Game.Overlays.Chat;
|
using osu.Game.Overlays.Chat;
|
||||||
using osu.Game.Tournament.IPC;
|
using osu.Game.Tournament.IPC;
|
||||||
@ -29,7 +30,7 @@ namespace osu.Game.Tournament.Components
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(MatchIPCInfo ipc)
|
private void load(MatchIPCInfo ipc, IAPIProvider api)
|
||||||
{
|
{
|
||||||
if (ipc != null)
|
if (ipc != null)
|
||||||
{
|
{
|
||||||
@ -45,7 +46,7 @@ namespace osu.Game.Tournament.Components
|
|||||||
|
|
||||||
if (manager == null)
|
if (manager == null)
|
||||||
{
|
{
|
||||||
AddInternal(manager = new ChannelManager { HighPollRate = { Value = true } });
|
AddInternal(manager = new ChannelManager(api) { HighPollRate = { Value = true } });
|
||||||
Channel.BindTo(manager.CurrentChannel);
|
Channel.BindTo(manager.CurrentChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,6 +319,15 @@ namespace osu.Game.Beatmaps
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void DeleteAllVideos()
|
||||||
|
{
|
||||||
|
realm.Write(r =>
|
||||||
|
{
|
||||||
|
var items = r.All<BeatmapSetInfo>().Where(s => !s.DeletePending && !s.Protected);
|
||||||
|
beatmapModelManager.DeleteVideos(items.ToList());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
public void UndeleteAll()
|
public void UndeleteAll()
|
||||||
{
|
{
|
||||||
realm.Run(r => beatmapModelManager.Undelete(r.All<BeatmapSetInfo>().Where(s => s.DeletePending).ToList()));
|
realm.Run(r => beatmapModelManager.Undelete(r.All<BeatmapSetInfo>().Where(s => s.DeletePending).ToList()));
|
||||||
|
@ -16,6 +16,7 @@ using osu.Game.Database;
|
|||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Stores;
|
using osu.Game.Stores;
|
||||||
|
using osu.Game.Overlays.Notifications;
|
||||||
|
|
||||||
#nullable enable
|
#nullable enable
|
||||||
|
|
||||||
@ -33,6 +34,8 @@ namespace osu.Game.Beatmaps
|
|||||||
|
|
||||||
protected override string[] HashableFileTypes => new[] { ".osu" };
|
protected override string[] HashableFileTypes => new[] { ".osu" };
|
||||||
|
|
||||||
|
public static readonly string[] VIDEO_EXTENSIONS = { ".mp4", ".mov", ".avi", ".flv" };
|
||||||
|
|
||||||
public BeatmapModelManager(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null)
|
public BeatmapModelManager(RealmAccess realm, Storage storage, BeatmapOnlineLookupQueue? onlineLookupQueue = null)
|
||||||
: base(realm, storage, onlineLookupQueue)
|
: base(realm, storage, onlineLookupQueue)
|
||||||
{
|
{
|
||||||
@ -114,5 +117,50 @@ namespace osu.Game.Beatmaps
|
|||||||
item.CopyChangesToRealm(existing);
|
item.CopyChangesToRealm(existing);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Delete videos from a list of beatmaps.
|
||||||
|
/// This will post notifications tracking progress.
|
||||||
|
/// </summary>
|
||||||
|
public void DeleteVideos(List<BeatmapSetInfo> items, bool silent = false)
|
||||||
|
{
|
||||||
|
if (items.Count == 0) return;
|
||||||
|
|
||||||
|
var notification = new ProgressNotification
|
||||||
|
{
|
||||||
|
Progress = 0,
|
||||||
|
Text = $"Preparing to delete all {HumanisedModelName} videos...",
|
||||||
|
CompletionText = "No videos found to delete!",
|
||||||
|
State = ProgressNotificationState.Active,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!silent)
|
||||||
|
PostNotification?.Invoke(notification);
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
int deleted = 0;
|
||||||
|
|
||||||
|
foreach (var b in items)
|
||||||
|
{
|
||||||
|
if (notification.State == ProgressNotificationState.Cancelled)
|
||||||
|
// user requested abort
|
||||||
|
return;
|
||||||
|
|
||||||
|
var video = b.Files.FirstOrDefault(f => VIDEO_EXTENSIONS.Any(ex => f.Filename.EndsWith(ex, StringComparison.Ordinal)));
|
||||||
|
|
||||||
|
if (video != null)
|
||||||
|
{
|
||||||
|
DeleteFile(b, video);
|
||||||
|
deleted++;
|
||||||
|
notification.CompletionText = $"Deleted {deleted} {HumanisedModelName} video(s)!";
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.Text = $"Deleting videos from {HumanisedModelName}s ({deleted} deleted)";
|
||||||
|
|
||||||
|
notification.Progress = (float)++i / items.Count;
|
||||||
|
}
|
||||||
|
|
||||||
|
notification.State = ProgressNotificationState.Completed;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,8 @@ using System.Linq;
|
|||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Extensions;
|
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
using osu.Game.Online.API.Requests;
|
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps
|
namespace osu.Game.Beatmaps
|
||||||
@ -25,26 +22,15 @@ namespace osu.Game.Beatmaps
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; }
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private IRulesetStore rulesets { get; set; }
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private Bindable<RulesetInfo> ruleset { get; set; }
|
private Bindable<RulesetInfo> ruleset { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
private readonly Dictionary<string, double> recommendedDifficultyMapping = new Dictionary<string, double>();
|
||||||
/// The user for which the last requests were run.
|
|
||||||
/// </summary>
|
|
||||||
private int? requestedUserId;
|
|
||||||
|
|
||||||
private readonly Dictionary<IRulesetInfo, double> recommendedDifficultyMapping = new Dictionary<IRulesetInfo, double>();
|
|
||||||
|
|
||||||
private readonly IBindable<APIState> apiState = new Bindable<APIState>();
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
apiState.BindTo(api.State);
|
api.LocalUser.BindValueChanged(_ => populateValues(), true);
|
||||||
apiState.BindValueChanged(onlineStateChanged, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -58,12 +44,12 @@ namespace osu.Game.Beatmaps
|
|||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
public BeatmapInfo GetRecommendedBeatmap(IEnumerable<BeatmapInfo> beatmaps)
|
public BeatmapInfo GetRecommendedBeatmap(IEnumerable<BeatmapInfo> beatmaps)
|
||||||
{
|
{
|
||||||
foreach (var r in orderedRulesets)
|
foreach (string r in orderedRulesets)
|
||||||
{
|
{
|
||||||
if (!recommendedDifficultyMapping.TryGetValue(r, out double recommendation))
|
if (!recommendedDifficultyMapping.TryGetValue(r, out double recommendation))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.Equals(r)).OrderBy(b =>
|
BeatmapInfo beatmapInfo = beatmaps.Where(b => b.Ruleset.ShortName.Equals(r)).OrderBy(b =>
|
||||||
{
|
{
|
||||||
double difference = b.StarRating - recommendation;
|
double difference = b.StarRating - recommendation;
|
||||||
return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder
|
return difference >= 0 ? difference * 2 : difference * -1; // prefer easier over harder
|
||||||
@ -76,55 +62,35 @@ namespace osu.Game.Beatmaps
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fetchRecommendedValues()
|
private void populateValues()
|
||||||
{
|
{
|
||||||
if (recommendedDifficultyMapping.Count > 0 && api.LocalUser.Value.Id == requestedUserId)
|
if (api.LocalUser.Value.RulesetsStatistics == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
requestedUserId = api.LocalUser.Value.Id;
|
foreach (var kvp in api.LocalUser.Value.RulesetsStatistics)
|
||||||
|
|
||||||
// only query API for built-in rulesets
|
|
||||||
rulesets.AvailableRulesets.Where(ruleset => ruleset.IsLegacyRuleset()).ForEach(rulesetInfo =>
|
|
||||||
{
|
{
|
||||||
var req = new GetUserRequest(api.LocalUser.Value.Id, rulesetInfo);
|
// algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505
|
||||||
|
recommendedDifficultyMapping[kvp.Key] = Math.Pow((double)(kvp.Value.PP ?? 0), 0.4) * 0.195;
|
||||||
req.Success += result =>
|
}
|
||||||
{
|
|
||||||
// algorithm taken from https://github.com/ppy/osu-web/blob/e6e2825516449e3d0f3f5e1852c6bdd3428c3437/app/Models/User.php#L1505
|
|
||||||
recommendedDifficultyMapping[rulesetInfo] = Math.Pow((double)(result.Statistics.PP ?? 0), 0.4) * 0.195;
|
|
||||||
};
|
|
||||||
|
|
||||||
api.Queue(req);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <returns>
|
/// <returns>
|
||||||
/// Rulesets ordered descending by their respective recommended difficulties.
|
/// Rulesets ordered descending by their respective recommended difficulties.
|
||||||
/// The currently selected ruleset will always be first.
|
/// The currently selected ruleset will always be first.
|
||||||
/// </returns>
|
/// </returns>
|
||||||
private IEnumerable<IRulesetInfo> orderedRulesets
|
private IEnumerable<string> orderedRulesets
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
if (LoadState < LoadState.Ready || ruleset.Value == null)
|
if (LoadState < LoadState.Ready || ruleset.Value == null)
|
||||||
return Enumerable.Empty<RulesetInfo>();
|
return Enumerable.Empty<string>();
|
||||||
|
|
||||||
return recommendedDifficultyMapping
|
return recommendedDifficultyMapping
|
||||||
.OrderByDescending(pair => pair.Value)
|
.OrderByDescending(pair => pair.Value)
|
||||||
.Select(pair => pair.Key)
|
.Select(pair => pair.Key)
|
||||||
.Where(r => !r.Equals(ruleset.Value))
|
.Where(r => !r.Equals(ruleset.Value.ShortName))
|
||||||
.Prepend(ruleset.Value);
|
.Prepend(ruleset.Value.ShortName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onlineStateChanged(ValueChangedEvent<APIState> state) => Schedule(() =>
|
|
||||||
{
|
|
||||||
switch (state.NewValue)
|
|
||||||
{
|
|
||||||
case APIState.Online:
|
|
||||||
fetchRecommendedValues();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,8 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
downloadTrackers.Add(beatmapDownloadTracker);
|
downloadTrackers.Add(beatmapDownloadTracker);
|
||||||
AddInternal(beatmapDownloadTracker);
|
AddInternal(beatmapDownloadTracker);
|
||||||
|
|
||||||
|
// Note that this is downloading the beatmaps even if they are already downloaded.
|
||||||
|
// We could rely more on `BeatmapDownloadTracker`'s exposed state to avoid this.
|
||||||
beatmapDownloader.Download(beatmapSet);
|
beatmapDownloader.Download(beatmapSet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,7 +35,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
protected readonly BeatmapDownloadTracker DownloadTracker;
|
protected readonly BeatmapDownloadTracker DownloadTracker;
|
||||||
|
|
||||||
protected BeatmapCard(APIBeatmapSet beatmapSet, bool allowExpansion = true)
|
protected BeatmapCard(APIBeatmapSet beatmapSet, bool allowExpansion = true)
|
||||||
: base(HoverSampleSet.Submit)
|
: base(HoverSampleSet.Button)
|
||||||
{
|
{
|
||||||
Expanded = new BindableBool { Disabled = !allowExpansion };
|
Expanded = new BindableBool { Disabled = !allowExpansion };
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -245,10 +244,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (BeatmapSet.HasVideo)
|
if (BeatmapSet.HasVideo)
|
||||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
|
leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) });
|
||||||
|
|
||||||
if (BeatmapSet.HasStoryboard)
|
if (BeatmapSet.HasStoryboard)
|
||||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) });
|
leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) });
|
||||||
|
|
||||||
if (BeatmapSet.FeaturedInSpotlight)
|
if (BeatmapSet.FeaturedInSpotlight)
|
||||||
{
|
{
|
||||||
|
@ -7,7 +7,6 @@ using System.Collections.Generic;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
using osu.Game.Beatmaps.Drawables.Cards.Statistics;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -226,10 +225,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (BeatmapSet.HasVideo)
|
if (BeatmapSet.HasVideo)
|
||||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) });
|
leftIconArea.Add(new VideoIconPill { IconSize = new Vector2(20) });
|
||||||
|
|
||||||
if (BeatmapSet.HasStoryboard)
|
if (BeatmapSet.HasStoryboard)
|
||||||
leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) });
|
leftIconArea.Add(new StoryboardIconPill { IconSize = new Vector2(20) });
|
||||||
|
|
||||||
if (BeatmapSet.FeaturedInSpotlight)
|
if (BeatmapSet.FeaturedInSpotlight)
|
||||||
{
|
{
|
||||||
|
@ -3,14 +3,16 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Beatmaps.Drawables.Cards
|
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||||
{
|
{
|
||||||
public class IconPill : CircularContainer
|
public abstract class IconPill : CircularContainer, IHasTooltip
|
||||||
{
|
{
|
||||||
public Vector2 IconSize
|
public Vector2 IconSize
|
||||||
{
|
{
|
||||||
@ -20,7 +22,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
|
|
||||||
private readonly Container iconContainer;
|
private readonly Container iconContainer;
|
||||||
|
|
||||||
public IconPill(IconUsage icon)
|
protected IconPill(IconUsage icon)
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both;
|
AutoSizeAxes = Axes.Both;
|
||||||
Masking = true;
|
Masking = true;
|
||||||
@ -47,5 +49,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public abstract LocalisableString TooltipText { get; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
19
osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs
Normal file
19
osu.Game/Beatmaps/Drawables/Cards/StoryboardIconPill.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// 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.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||||
|
{
|
||||||
|
public class StoryboardIconPill : IconPill
|
||||||
|
{
|
||||||
|
public StoryboardIconPill()
|
||||||
|
: base(FontAwesome.Solid.Image)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoStoryboard;
|
||||||
|
}
|
||||||
|
}
|
19
osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs
Normal file
19
osu.Game/Beatmaps/Drawables/Cards/VideoIconPill.cs
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
// 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.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
|
namespace osu.Game.Beatmaps.Drawables.Cards
|
||||||
|
{
|
||||||
|
public class VideoIconPill : IconPill
|
||||||
|
{
|
||||||
|
public VideoIconPill()
|
||||||
|
: base(FontAwesome.Solid.Film)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public override LocalisableString TooltipText => BeatmapsetsStrings.ShowInfoVideo;
|
||||||
|
}
|
||||||
|
}
|
@ -91,7 +91,7 @@ namespace osu.Game.Database
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void performLookup()
|
private async Task performLookup()
|
||||||
{
|
{
|
||||||
// contains at most 50 unique IDs from tasks, which is used to perform the lookup.
|
// contains at most 50 unique IDs from tasks, which is used to perform the lookup.
|
||||||
var nextTaskBatch = new Dictionary<TLookup, List<TaskCompletionSource<TValue>>>();
|
var nextTaskBatch = new Dictionary<TLookup, List<TaskCompletionSource<TValue>>>();
|
||||||
@ -127,7 +127,7 @@ namespace osu.Game.Database
|
|||||||
|
|
||||||
// rather than queueing, we maintain our own single-threaded request stream.
|
// rather than queueing, we maintain our own single-threaded request stream.
|
||||||
// todo: we probably want retry logic here.
|
// todo: we probably want retry logic here.
|
||||||
api.Perform(request);
|
await api.PerformAsync(request).ConfigureAwait(false);
|
||||||
|
|
||||||
finishPendingTask();
|
finishPendingTask();
|
||||||
|
|
||||||
|
@ -392,7 +392,7 @@ namespace osu.Game.Database
|
|||||||
{
|
{
|
||||||
total_writes_async.Value++;
|
total_writes_async.Value++;
|
||||||
using (var realm = getRealmInstance())
|
using (var realm = getRealmInstance())
|
||||||
await realm.WriteAsync(action);
|
await realm.WriteAsync(() => action(realm));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public class ScalingContainer : Container
|
public class ScalingContainer : Container
|
||||||
{
|
{
|
||||||
private const float duration = 500;
|
internal const float TRANSITION_DURATION = 500;
|
||||||
|
|
||||||
private Bindable<float> sizeX;
|
private Bindable<float> sizeX;
|
||||||
private Bindable<float> sizeY;
|
private Bindable<float> sizeY;
|
||||||
@ -99,7 +99,7 @@ namespace osu.Game.Graphics.Containers
|
|||||||
if (applyUIScale)
|
if (applyUIScale)
|
||||||
{
|
{
|
||||||
uiScale = osuConfig.GetBindable<float>(OsuSetting.UIScale);
|
uiScale = osuConfig.GetBindable<float>(OsuSetting.UIScale);
|
||||||
uiScale.BindValueChanged(args => this.TransformTo(nameof(CurrentScale), args.NewValue, duration, Easing.OutQuart), true);
|
uiScale.BindValueChanged(args => this.TransformTo(nameof(CurrentScale), args.NewValue, TRANSITION_DURATION, Easing.OutQuart), true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -163,10 +163,10 @@ namespace osu.Game.Graphics.Containers
|
|||||||
backgroundStack.Push(new ScalingBackgroundScreen());
|
backgroundStack.Push(new ScalingBackgroundScreen());
|
||||||
}
|
}
|
||||||
|
|
||||||
backgroundStack.FadeIn(duration);
|
backgroundStack.FadeIn(TRANSITION_DURATION);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
backgroundStack?.FadeOut(duration);
|
backgroundStack?.FadeOut(TRANSITION_DURATION);
|
||||||
}
|
}
|
||||||
|
|
||||||
RectangleF targetRect = new RectangleF(Vector2.Zero, Vector2.One);
|
RectangleF targetRect = new RectangleF(Vector2.Zero, Vector2.One);
|
||||||
@ -195,13 +195,13 @@ namespace osu.Game.Graphics.Containers
|
|||||||
if (requiresMasking)
|
if (requiresMasking)
|
||||||
sizableContainer.Masking = true;
|
sizableContainer.Masking = true;
|
||||||
|
|
||||||
sizableContainer.MoveTo(targetRect.Location, duration, Easing.OutQuart);
|
sizableContainer.MoveTo(targetRect.Location, TRANSITION_DURATION, Easing.OutQuart);
|
||||||
sizableContainer.ResizeTo(targetRect.Size, duration, Easing.OutQuart);
|
sizableContainer.ResizeTo(targetRect.Size, TRANSITION_DURATION, Easing.OutQuart);
|
||||||
|
|
||||||
// Of note, this will not work great in the case of nested ScalingContainers where multiple are applying corner radius.
|
// Of note, this will not work great in the case of nested ScalingContainers where multiple are applying corner radius.
|
||||||
// Masking and corner radius should likely only be applied at one point in the full game stack to fix this.
|
// Masking and corner radius should likely only be applied at one point in the full game stack to fix this.
|
||||||
// An example of how this can occur is when the skin editor is visible and the game screen scaling is set to "Everything".
|
// An example of how this can occur is when the skin editor is visible and the game screen scaling is set to "Everything".
|
||||||
sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, duration, requiresMasking ? Easing.OutQuart : Easing.None)
|
sizableContainer.TransformTo(nameof(CornerRadius), requiresMasking ? corner_radius : 0, TRANSITION_DURATION, requiresMasking ? Easing.OutQuart : Easing.None)
|
||||||
.OnComplete(_ => { sizableContainer.Masking = requiresMasking; });
|
.OnComplete(_ => { sizableContainer.Masking = requiresMasking; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
Size = TwoLayerButton.SIZE_EXTENDED;
|
Size = TwoLayerButton.SIZE_EXTENDED;
|
||||||
|
|
||||||
Child = button = new TwoLayerButton(HoverSampleSet.Submit)
|
Child = button = new TwoLayerButton
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopLeft,
|
Anchor = Anchor.TopLeft,
|
||||||
Origin = Anchor.TopLeft,
|
Origin = Anchor.TopLeft,
|
||||||
|
@ -56,8 +56,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
private readonly SpriteText spriteText;
|
private readonly SpriteText spriteText;
|
||||||
private Vector2 hoverSpacing => new Vector2(3f, 0f);
|
private Vector2 hoverSpacing => new Vector2(3f, 0f);
|
||||||
|
|
||||||
public DialogButton()
|
public DialogButton(HoverSampleSet sampleSet = HoverSampleSet.Button)
|
||||||
: base(HoverSampleSet.Submit)
|
: base(sampleSet)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
Icon = FontAwesome.Solid.ExternalLinkAlt,
|
Icon = FontAwesome.Solid.ExternalLinkAlt,
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
},
|
},
|
||||||
new HoverClickSounds(HoverSampleSet.Submit)
|
new HoverClickSounds()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Extensions;
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Framework.Utils;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
@ -37,7 +38,10 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition))
|
if (buttons.Contains(e.Button) && Contains(e.ScreenSpaceMousePosition))
|
||||||
sampleClick?.Play();
|
{
|
||||||
|
sampleClick.Frequency.Value = 0.99 + RNG.NextDouble(0.02);
|
||||||
|
sampleClick.Play();
|
||||||
|
}
|
||||||
|
|
||||||
return base.OnClick(e);
|
return base.OnClick(e);
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,6 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
[Description("default")]
|
[Description("default")]
|
||||||
Default,
|
Default,
|
||||||
|
|
||||||
[Description("submit")]
|
|
||||||
Submit,
|
|
||||||
|
|
||||||
[Description("button")]
|
[Description("button")]
|
||||||
Button,
|
Button,
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
public class ShearedToggleButton : ShearedButton
|
public class ShearedToggleButton : ShearedButton
|
||||||
{
|
{
|
||||||
|
private Sample? sampleClick;
|
||||||
private Sample? sampleOff;
|
private Sample? sampleOff;
|
||||||
private Sample? sampleOn;
|
private Sample? sampleOn;
|
||||||
|
|
||||||
@ -39,8 +40,9 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
sampleOn = audio.Samples.Get(@"UI/check-on");
|
sampleClick = audio.Samples.Get(@"UI/default-select");
|
||||||
sampleOff = audio.Samples.Get(@"UI/check-off");
|
sampleOn = audio.Samples.Get(@"UI/dropdown-open");
|
||||||
|
sampleOff = audio.Samples.Get(@"UI/dropdown-close");
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds(sampleSet);
|
||||||
@ -67,6 +69,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
private void playSample()
|
private void playSample()
|
||||||
{
|
{
|
||||||
|
sampleClick?.Play();
|
||||||
|
|
||||||
if (Active.Value)
|
if (Active.Value)
|
||||||
sampleOn?.Play();
|
sampleOn?.Play();
|
||||||
else
|
else
|
||||||
|
@ -2,11 +2,13 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -65,6 +67,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
|
if (BeatmapModelManager.VIDEO_EXTENSIONS.Contains(File.Extension))
|
||||||
|
return FontAwesome.Regular.FileVideo;
|
||||||
|
|
||||||
switch (File.Extension)
|
switch (File.Extension)
|
||||||
{
|
{
|
||||||
case @".ogg":
|
case @".ogg":
|
||||||
@ -77,12 +82,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
|||||||
case @".png":
|
case @".png":
|
||||||
return FontAwesome.Regular.FileImage;
|
return FontAwesome.Regular.FileImage;
|
||||||
|
|
||||||
case @".mp4":
|
|
||||||
case @".avi":
|
|
||||||
case @".mov":
|
|
||||||
case @".flv":
|
|
||||||
return FontAwesome.Regular.FileVideo;
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return FontAwesome.Regular.File;
|
return FontAwesome.Regular.File;
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Buffers;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.Toolkit.HighPerformance;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using SharpCompress.Archives.Zip;
|
using SharpCompress.Archives.Zip;
|
||||||
|
using SixLabors.ImageSharp.Memory;
|
||||||
|
|
||||||
namespace osu.Game.IO.Archives
|
namespace osu.Game.IO.Archives
|
||||||
{
|
{
|
||||||
@ -27,15 +31,12 @@ namespace osu.Game.IO.Archives
|
|||||||
if (entry == null)
|
if (entry == null)
|
||||||
throw new FileNotFoundException();
|
throw new FileNotFoundException();
|
||||||
|
|
||||||
// allow seeking
|
var owner = MemoryAllocator.Default.Allocate<byte>((int)entry.Size);
|
||||||
MemoryStream copy = new MemoryStream();
|
|
||||||
|
|
||||||
using (Stream s = entry.OpenEntryStream())
|
using (Stream s = entry.OpenEntryStream())
|
||||||
s.CopyTo(copy);
|
s.ReadToFill(owner.Memory.Span);
|
||||||
|
|
||||||
copy.Position = 0;
|
return new MemoryOwnerMemoryStream(owner);
|
||||||
|
|
||||||
return copy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Dispose()
|
public override void Dispose()
|
||||||
@ -45,5 +46,48 @@ namespace osu.Game.IO.Archives
|
|||||||
}
|
}
|
||||||
|
|
||||||
public override IEnumerable<string> Filenames => archive.Entries.Select(e => e.Key).ExcludeSystemFileNames();
|
public override IEnumerable<string> Filenames => archive.Entries.Select(e => e.Key).ExcludeSystemFileNames();
|
||||||
|
|
||||||
|
private class MemoryOwnerMemoryStream : Stream
|
||||||
|
{
|
||||||
|
private readonly IMemoryOwner<byte> owner;
|
||||||
|
private readonly Stream stream;
|
||||||
|
|
||||||
|
public MemoryOwnerMemoryStream(IMemoryOwner<byte> owner)
|
||||||
|
{
|
||||||
|
this.owner = owner;
|
||||||
|
|
||||||
|
stream = owner.Memory.AsStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool disposing)
|
||||||
|
{
|
||||||
|
owner?.Dispose();
|
||||||
|
base.Dispose(disposing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Flush() => stream.Flush();
|
||||||
|
|
||||||
|
public override int Read(byte[] buffer, int offset, int count) => stream.Read(buffer, offset, count);
|
||||||
|
|
||||||
|
public override long Seek(long offset, SeekOrigin origin) => stream.Seek(offset, origin);
|
||||||
|
|
||||||
|
public override void SetLength(long value) => stream.SetLength(value);
|
||||||
|
|
||||||
|
public override void Write(byte[] buffer, int offset, int count) => stream.Write(buffer, offset, count);
|
||||||
|
|
||||||
|
public override bool CanRead => stream.CanRead;
|
||||||
|
|
||||||
|
public override bool CanSeek => stream.CanSeek;
|
||||||
|
|
||||||
|
public override bool CanWrite => stream.CanWrite;
|
||||||
|
|
||||||
|
public override long Length => stream.Length;
|
||||||
|
|
||||||
|
public override long Position
|
||||||
|
{
|
||||||
|
get => stream.Position;
|
||||||
|
set => stream.Position = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,7 @@ namespace osu.Game.Input.Bindings
|
|||||||
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
|
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
|
||||||
new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode),
|
new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode),
|
||||||
new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay),
|
new KeyBinding(new[] { InputKey.F5 }, GlobalAction.EditorTestGameplay),
|
||||||
|
new KeyBinding(new[] { InputKey.T }, GlobalAction.EditorTapForBPM),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally),
|
new KeyBinding(new[] { InputKey.Control, InputKey.H }, GlobalAction.EditorFlipHorizontally),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically),
|
new KeyBinding(new[] { InputKey.Control, InputKey.J }, GlobalAction.EditorFlipVertically),
|
||||||
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing),
|
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.MouseWheelDown }, GlobalAction.EditorDecreaseDistanceSpacing),
|
||||||
@ -322,5 +323,8 @@ namespace osu.Game.Input.Bindings
|
|||||||
|
|
||||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DeselectAllMods))]
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.DeselectAllMods))]
|
||||||
DeselectAllMods,
|
DeselectAllMods,
|
||||||
|
|
||||||
|
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorTapForBPM))]
|
||||||
|
EditorTapForBPM,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,12 +30,12 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString OutputDevice => new TranslatableString(getKey(@"output_device"), @"Output device");
|
public static LocalisableString OutputDevice => new TranslatableString(getKey(@"output_device"), @"Output device");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Master"
|
/// "Hitsound stereo separation"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Hitsound stereo separation");
|
public static LocalisableString PositionalLevel => new TranslatableString(getKey(@"positional_hitsound_audio_level"), @"Hitsound stereo separation");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Level"
|
/// "Master"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master");
|
public static LocalisableString MasterVolume => new TranslatableString(getKey(@"master_volume"), @"Master");
|
||||||
|
|
||||||
@ -69,6 +69,6 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString OffsetWizard => new TranslatableString(getKey(@"offset_wizard"), @"Offset wizard");
|
public static LocalisableString OffsetWizard => new TranslatableString(getKey(@"offset_wizard"), @"Offset wizard");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,6 +174,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString EditorTimingMode => new TranslatableString(getKey(@"editor_timing_mode"), @"Timing mode");
|
public static LocalisableString EditorTimingMode => new TranslatableString(getKey(@"editor_timing_mode"), @"Timing mode");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Tap for BPM"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString EditorTapForBPM => new TranslatableString(getKey(@"editor_tap_for_bpm"), @"Tap for BPM");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Cycle grid display mode"
|
/// "Cycle grid display mode"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -205,7 +210,7 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString ToggleInGameInterface => new TranslatableString(getKey(@"toggle_in_game_interface"), @"Toggle in-game interface");
|
public static LocalisableString ToggleInGameInterface => new TranslatableString(getKey(@"toggle_in_game_interface"), @"Toggle in-game interface");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Toggle Mod Select"
|
/// "Toggle mod select"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ToggleModSelection => new TranslatableString(getKey(@"toggle_mod_selection"), @"Toggle mod select");
|
public static LocalisableString ToggleModSelection => new TranslatableString(getKey(@"toggle_mod_selection"), @"Toggle mod select");
|
||||||
|
|
||||||
@ -294,6 +299,6 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ToggleChatFocus => new TranslatableString(getKey(@"toggle_chat_focus"), @"Toggle chat focus");
|
public static LocalisableString ToggleChatFocus => new TranslatableString(getKey(@"toggle_chat_focus"), @"Toggle chat focus");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,10 +15,10 @@ namespace osu.Game.Localisation
|
|||||||
public static LocalisableString JoystickGamepad => new TranslatableString(getKey(@"joystick_gamepad"), @"Joystick / Gamepad");
|
public static LocalisableString JoystickGamepad => new TranslatableString(getKey(@"joystick_gamepad"), @"Joystick / Gamepad");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Deadzone Threshold"
|
/// "Deadzone"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString DeadzoneThreshold => new TranslatableString(getKey(@"deadzone_threshold"), @"Deadzone");
|
public static LocalisableString DeadzoneThreshold => new TranslatableString(getKey(@"deadzone_threshold"), @"Deadzone");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
34
osu.Game/Localisation/LayoutSettingsStrings.cs
Normal file
34
osu.Game/Localisation/LayoutSettingsStrings.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
|
||||||
|
namespace osu.Game.Localisation
|
||||||
|
{
|
||||||
|
public static class LayoutSettingsStrings
|
||||||
|
{
|
||||||
|
private const string prefix = @"osu.Game.Resources.Localisation.LayoutSettings";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Checking for fullscreen capabilities..."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString CheckingForFullscreenCapabilities => new TranslatableString(getKey(@"checking_for_fullscreen_capabilities"), @"Checking for fullscreen capabilities...");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "osu! is running exclusive fullscreen, guaranteeing low latency!"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString OsuIsRunningExclusiveFullscreen => new TranslatableString(getKey(@"osu_is_running_exclusive_fullscreen"), @"osu! is running exclusive fullscreen, guaranteeing low latency!");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Unable to run exclusive fullscreen. You'll still experience some input latency."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString UnableToRunExclusiveFullscreen => new TranslatableString(getKey(@"unable_to_run_exclusive_fullscreen"), @"Unable to run exclusive fullscreen. You'll still experience some input latency.");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended."
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString FullscreenMacOSNote => new TranslatableString(getKey(@"fullscreen_macos_note"), @"Using fullscreen on macOS makes interacting with the menu bar and spaces no longer work, and may lead to freezes if a system dialog is presented. Using borderless is recommended.");
|
||||||
|
|
||||||
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
|
}
|
||||||
|
}
|
@ -29,6 +29,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString DeleteAllBeatmaps => new TranslatableString(getKey(@"delete_all_beatmaps"), @"Delete ALL beatmaps");
|
public static LocalisableString DeleteAllBeatmaps => new TranslatableString(getKey(@"delete_all_beatmaps"), @"Delete ALL beatmaps");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Delete ALL beatmap videos"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString DeleteAllBeatmapVideos => new TranslatableString(getKey(@"delete_all_beatmap_videos"), @"Delete ALL beatmap videos");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Import scores from stable"
|
/// "Import scores from stable"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -54,6 +54,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ExportSkinButton => new TranslatableString(getKey(@"export_skin_button"), @"Export selected skin");
|
public static LocalisableString ExportSkinButton => new TranslatableString(getKey(@"export_skin_button"), @"Export selected skin");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Delete selected skin"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString DeleteSkinButton => new TranslatableString(getKey(@"delete_skin_button"), @"Delete selected skin");
|
||||||
|
|
||||||
private static string getKey(string key) => $"{prefix}:{key}";
|
private static string getKey(string key) => $"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -121,8 +121,16 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
if (isFailing) return;
|
if (isFailing) return;
|
||||||
|
|
||||||
Logger.Log($@"Performing request {this}", LoggingTarget.Network);
|
try
|
||||||
WebRequest.Perform();
|
{
|
||||||
|
Logger.Log($@"Performing request {this}", LoggingTarget.Network);
|
||||||
|
WebRequest.Perform();
|
||||||
|
}
|
||||||
|
catch (OperationCanceledException)
|
||||||
|
{
|
||||||
|
// ignore this. internally Perform is running async and the fail state may have changed since
|
||||||
|
// the last check of `isFailing` above.
|
||||||
|
}
|
||||||
|
|
||||||
if (isFailing) return;
|
if (isFailing) return;
|
||||||
|
|
||||||
|
@ -63,12 +63,13 @@ namespace osu.Game.Online.API
|
|||||||
|
|
||||||
public virtual void Queue(APIRequest request)
|
public virtual void Queue(APIRequest request)
|
||||||
{
|
{
|
||||||
if (HandleRequest?.Invoke(request) != true)
|
Schedule(() =>
|
||||||
{
|
{
|
||||||
// this will fail due to not receiving an APIAccess, and trigger a failure on the request.
|
if (HandleRequest?.Invoke(request) != true)
|
||||||
// this is intended - any request in testing that needs non-failures should use HandleRequest.
|
{
|
||||||
request.Perform(this);
|
request.Fail(new InvalidOperationException($@"{nameof(DummyAPIAccess)} cannot process this request."));
|
||||||
}
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Perform(APIRequest request) => HandleRequest?.Invoke(request);
|
public void Perform(APIRequest request) => HandleRequest?.Invoke(request);
|
||||||
|
@ -61,8 +61,7 @@ namespace osu.Game.Online.Chat
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public IBindableList<Channel> AvailableChannels => availableChannels;
|
public IBindableList<Channel> AvailableChannels => availableChannels;
|
||||||
|
|
||||||
[Resolved]
|
private readonly IAPIProvider api;
|
||||||
private IAPIProvider api { get; set; }
|
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private UserLookupCache users { get; set; }
|
private UserLookupCache users { get; set; }
|
||||||
@ -71,8 +70,9 @@ namespace osu.Game.Online.Chat
|
|||||||
|
|
||||||
private readonly IBindable<bool> isIdle = new BindableBool();
|
private readonly IBindable<bool> isIdle = new BindableBool();
|
||||||
|
|
||||||
public ChannelManager()
|
public ChannelManager(IAPIProvider api)
|
||||||
{
|
{
|
||||||
|
this.api = api;
|
||||||
CurrentChannel.ValueChanged += currentChannelChanged;
|
CurrentChannel.ValueChanged += currentChannelChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,6 @@ namespace osu.Game.Online.Chat
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DrawableLinkCompiler(IEnumerable<Drawable> parts)
|
public DrawableLinkCompiler(IEnumerable<Drawable> parts)
|
||||||
: base(HoverSampleSet.Submit)
|
|
||||||
{
|
{
|
||||||
Parts = parts.ToList();
|
Parts = parts.ToList();
|
||||||
}
|
}
|
||||||
|
@ -77,7 +77,7 @@ namespace osu.Game.Online.Chat
|
|||||||
if (!messages.Any())
|
if (!messages.Any())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == messages.First().ChannelId);
|
var channel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id > 0 && c.Id == messages.First().ChannelId);
|
||||||
|
|
||||||
if (channel == null)
|
if (channel == null)
|
||||||
return;
|
return;
|
||||||
|
@ -155,39 +155,42 @@ namespace osu.Game.Online.Chat
|
|||||||
{
|
{
|
||||||
public Func<Message, ChatLine> CreateChatLineAction;
|
public Func<Message, ChatLine> CreateChatLineAction;
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private OsuColour colours { get; set; }
|
|
||||||
|
|
||||||
public StandAloneDrawableChannel(Channel channel)
|
public StandAloneDrawableChannel(Channel channel)
|
||||||
: base(channel)
|
: base(channel)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
ChatLineFlow.Padding = new MarginPadding { Horizontal = 0 };
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m);
|
protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m);
|
||||||
|
|
||||||
protected override Drawable CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time)
|
protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new StandAloneDaySeparator(time);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class StandAloneDaySeparator : DaySeparator
|
||||||
|
{
|
||||||
|
protected override float TextSize => 14;
|
||||||
|
protected override float LineHeight => 1;
|
||||||
|
protected override float Spacing => 5;
|
||||||
|
protected override float DateAlign => 125;
|
||||||
|
|
||||||
|
public StandAloneDaySeparator(DateTimeOffset time)
|
||||||
|
: base(time)
|
||||||
{
|
{
|
||||||
TextSize = 14,
|
}
|
||||||
Colour = colours.Yellow,
|
|
||||||
LineHeight = 1,
|
[BackgroundDependencyLoader]
|
||||||
Padding = new MarginPadding { Horizontal = 10 },
|
private void load(OsuColour colours)
|
||||||
Margin = new MarginPadding { Vertical = 5 },
|
{
|
||||||
};
|
Height = 25;
|
||||||
|
Colour = colours.Yellow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected class StandAloneMessage : ChatLine
|
protected class StandAloneMessage : ChatLine
|
||||||
{
|
{
|
||||||
protected override float TextSize => 15;
|
protected override float TextSize => 15;
|
||||||
|
protected override float Spacing => 5;
|
||||||
protected override float HorizontalPadding => 10;
|
protected override float TimestampWidth => 45;
|
||||||
protected override float MessagePadding => 120;
|
protected override float UsernameWidth => 75;
|
||||||
protected override float TimestampPadding => 50;
|
|
||||||
|
|
||||||
public StandAloneMessage(Message message)
|
public StandAloneMessage(Message message)
|
||||||
: base(message)
|
: base(message)
|
||||||
|
@ -197,6 +197,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
|
|
||||||
APIRoom.Playlist.Clear();
|
APIRoom.Playlist.Clear();
|
||||||
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
|
APIRoom.Playlist.AddRange(joinedRoom.Playlist.Select(createPlaylistItem));
|
||||||
|
APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == joinedRoom.Settings.PlaylistItemId);
|
||||||
|
|
||||||
Debug.Assert(LocalUser != null);
|
Debug.Assert(LocalUser != null);
|
||||||
addUserToAPIRoom(LocalUser);
|
addUserToAPIRoom(LocalUser);
|
||||||
@ -737,6 +738,7 @@ namespace osu.Game.Online.Multiplayer
|
|||||||
APIRoom.Type.Value = Room.Settings.MatchType;
|
APIRoom.Type.Value = Room.Settings.MatchType;
|
||||||
APIRoom.QueueMode.Value = Room.Settings.QueueMode;
|
APIRoom.QueueMode.Value = Room.Settings.QueueMode;
|
||||||
APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration;
|
APIRoom.AutoStartDuration.Value = Room.Settings.AutoStartDuration;
|
||||||
|
APIRoom.CurrentPlaylistItem.Value = APIRoom.Playlist.Single(item => item.ID == settings.PlaylistItemId);
|
||||||
|
|
||||||
RoomUpdated?.Invoke();
|
RoomUpdated?.Invoke();
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,13 @@ namespace osu.Game.Online.Rooms
|
|||||||
/// Used for serialising to the API.
|
/// Used for serialising to the API.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonProperty("beatmap_id")]
|
[JsonProperty("beatmap_id")]
|
||||||
private int onlineBeatmapId => Beatmap.OnlineID;
|
private int onlineBeatmapId
|
||||||
|
{
|
||||||
|
get => Beatmap.OnlineID;
|
||||||
|
// This setter is only required for client-side serialise-then-deserialise operations.
|
||||||
|
// Serialisation is supposed to emit only a `beatmap_id`, but a (non-null) `beatmap` is required on deserialise.
|
||||||
|
set => Beatmap = new APIBeatmap { OnlineID = value };
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A beatmap representing this playlist item.
|
/// A beatmap representing this playlist item.
|
||||||
|
@ -162,6 +162,13 @@ namespace osu.Game.Online.Rooms
|
|||||||
Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue));
|
Password.BindValueChanged(p => HasPassword.Value = !string.IsNullOrEmpty(p.NewValue));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Copies values from another <see cref="Room"/> into this one.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// **Beware**: This will store references between <see cref="Room"/>s.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="other">The <see cref="Room"/> to copy values from.</param>
|
||||||
public void CopyFrom(Room other)
|
public void CopyFrom(Room other)
|
||||||
{
|
{
|
||||||
RoomID.Value = other.RoomID.Value;
|
RoomID.Value = other.RoomID.Value;
|
||||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Online.Spectator
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// The states of all users currently being watched.
|
/// The states of all users currently being watched.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public IBindableDictionary<int, SpectatorState> WatchedUserStates => watchedUserStates;
|
public virtual IBindableDictionary<int, SpectatorState> WatchedUserStates => watchedUserStates;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A global list of all players currently playing.
|
/// A global list of all players currently playing.
|
||||||
@ -172,6 +172,7 @@ namespace osu.Game.Online.Spectator
|
|||||||
currentState.RulesetID = score.ScoreInfo.RulesetID;
|
currentState.RulesetID = score.ScoreInfo.RulesetID;
|
||||||
currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray();
|
currentState.Mods = score.ScoreInfo.Mods.Select(m => new APIMod(m)).ToArray();
|
||||||
currentState.State = SpectatedUserState.Playing;
|
currentState.State = SpectatedUserState.Playing;
|
||||||
|
currentState.MaximumScoringValues = state.ScoreProcessor.MaximumScoringValues;
|
||||||
|
|
||||||
currentBeatmap = state.Beatmap;
|
currentBeatmap = state.Beatmap;
|
||||||
currentScore = score;
|
currentScore = score;
|
||||||
|
192
osu.Game/Online/Spectator/SpectatorScoreProcessor.cs
Normal file
192
osu.Game/Online/Spectator/SpectatorScoreProcessor.cs
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
// 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 enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Timing;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.Spectator
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A wrapper over a <see cref="ScoreProcessor"/> for spectated users.
|
||||||
|
/// This should be used when a local "playable" beatmap is unavailable or expensive to generate for the spectated user.
|
||||||
|
/// </summary>
|
||||||
|
public class SpectatorScoreProcessor : Component
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The current total score.
|
||||||
|
/// </summary>
|
||||||
|
public readonly BindableDouble TotalScore = new BindableDouble { MinValue = 0 };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current accuracy.
|
||||||
|
/// </summary>
|
||||||
|
public readonly BindableDouble Accuracy = new BindableDouble(1) { MinValue = 0, MaxValue = 1 };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The current combo.
|
||||||
|
/// </summary>
|
||||||
|
public readonly BindableInt Combo = new BindableInt();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The <see cref="ScoringMode"/> used to calculate scores.
|
||||||
|
/// </summary>
|
||||||
|
public readonly Bindable<ScoringMode> Mode = new Bindable<ScoringMode>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The applied <see cref="Mod"/>s.
|
||||||
|
/// </summary>
|
||||||
|
public IReadOnlyList<Mod> Mods => scoreProcessor?.Mods.Value ?? Array.Empty<Mod>();
|
||||||
|
|
||||||
|
private IClock? referenceClock;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The clock used to determine the current score.
|
||||||
|
/// </summary>
|
||||||
|
public IClock ReferenceClock
|
||||||
|
{
|
||||||
|
get => referenceClock ?? Clock;
|
||||||
|
set => referenceClock = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private SpectatorClient spectatorClient { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private RulesetStore rulesetStore { get; set; } = null!;
|
||||||
|
|
||||||
|
private readonly IBindableDictionary<int, SpectatorState> spectatorStates = new BindableDictionary<int, SpectatorState>();
|
||||||
|
private readonly List<TimedFrame> replayFrames = new List<TimedFrame>();
|
||||||
|
private readonly int userId;
|
||||||
|
|
||||||
|
private SpectatorState? spectatorState;
|
||||||
|
private ScoreProcessor? scoreProcessor;
|
||||||
|
private ScoreInfo? scoreInfo;
|
||||||
|
|
||||||
|
public SpectatorScoreProcessor(int userId)
|
||||||
|
{
|
||||||
|
this.userId = userId;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
Mode.BindValueChanged(_ => UpdateScore());
|
||||||
|
|
||||||
|
spectatorStates.BindTo(spectatorClient.WatchedUserStates);
|
||||||
|
spectatorStates.BindCollectionChanged(onSpectatorStatesChanged, true);
|
||||||
|
|
||||||
|
spectatorClient.OnNewFrames += onNewFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSpectatorStatesChanged(object? sender, NotifyDictionaryChangedEventArgs<int, SpectatorState> e)
|
||||||
|
{
|
||||||
|
if (!spectatorStates.TryGetValue(userId, out var userState) || userState.BeatmapID == null || userState.RulesetID == null)
|
||||||
|
{
|
||||||
|
scoreProcessor?.RemoveAndDisposeImmediately();
|
||||||
|
scoreProcessor = null;
|
||||||
|
scoreInfo = null;
|
||||||
|
spectatorState = null;
|
||||||
|
replayFrames.Clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scoreProcessor != null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Debug.Assert(scoreInfo == null);
|
||||||
|
|
||||||
|
RulesetInfo? rulesetInfo = rulesetStore.GetRuleset(userState.RulesetID.Value);
|
||||||
|
if (rulesetInfo == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Ruleset ruleset = rulesetInfo.CreateInstance();
|
||||||
|
|
||||||
|
spectatorState = userState;
|
||||||
|
scoreInfo = new ScoreInfo { Ruleset = rulesetInfo };
|
||||||
|
scoreProcessor = ruleset.CreateScoreProcessor();
|
||||||
|
scoreProcessor.Mods.Value = userState.Mods.Select(m => m.ToMod(ruleset)).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onNewFrames(int incomingUserId, FrameDataBundle bundle)
|
||||||
|
{
|
||||||
|
if (incomingUserId != userId)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
if (scoreProcessor == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
replayFrames.Add(new TimedFrame(bundle.Frames.First().Time, bundle.Header));
|
||||||
|
UpdateScore();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateScore()
|
||||||
|
{
|
||||||
|
if (scoreInfo == null || replayFrames.Count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
Debug.Assert(spectatorState != null);
|
||||||
|
Debug.Assert(scoreProcessor != null);
|
||||||
|
|
||||||
|
int frameIndex = replayFrames.BinarySearch(new TimedFrame(ReferenceClock.CurrentTime));
|
||||||
|
if (frameIndex < 0)
|
||||||
|
frameIndex = ~frameIndex;
|
||||||
|
frameIndex = Math.Clamp(frameIndex - 1, 0, replayFrames.Count - 1);
|
||||||
|
|
||||||
|
TimedFrame frame = replayFrames[frameIndex];
|
||||||
|
Debug.Assert(frame.Header != null);
|
||||||
|
|
||||||
|
scoreInfo.MaxCombo = frame.Header.MaxCombo;
|
||||||
|
scoreInfo.Statistics = frame.Header.Statistics;
|
||||||
|
|
||||||
|
Accuracy.Value = frame.Header.Accuracy;
|
||||||
|
Combo.Value = frame.Header.Combo;
|
||||||
|
|
||||||
|
scoreProcessor.ExtractScoringValues(frame.Header, out var currentScoringValues, out _);
|
||||||
|
TotalScore.Value = scoreProcessor.ComputeScore(Mode.Value, currentScoringValues, spectatorState.MaximumScoringValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
if (spectatorClient.IsNotNull())
|
||||||
|
spectatorClient.OnNewFrames -= onNewFrames;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TimedFrame : IComparable<TimedFrame>
|
||||||
|
{
|
||||||
|
public readonly double Time;
|
||||||
|
public readonly FrameHeader? Header;
|
||||||
|
|
||||||
|
public TimedFrame(double time)
|
||||||
|
{
|
||||||
|
Time = time;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TimedFrame(double time, FrameHeader header)
|
||||||
|
{
|
||||||
|
Time = time;
|
||||||
|
Header = header;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int CompareTo(TimedFrame other) => Time.CompareTo(other.Time);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,6 +7,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using MessagePack;
|
using MessagePack;
|
||||||
using osu.Game.Online.API;
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Scoring;
|
||||||
|
|
||||||
namespace osu.Game.Online.Spectator
|
namespace osu.Game.Online.Spectator
|
||||||
{
|
{
|
||||||
@ -27,6 +28,9 @@ namespace osu.Game.Online.Spectator
|
|||||||
[Key(3)]
|
[Key(3)]
|
||||||
public SpectatedUserState State { get; set; }
|
public SpectatedUserState State { get; set; }
|
||||||
|
|
||||||
|
[Key(4)]
|
||||||
|
public ScoringValues MaximumScoringValues { get; set; }
|
||||||
|
|
||||||
public bool Equals(SpectatorState other)
|
public bool Equals(SpectatorState other)
|
||||||
{
|
{
|
||||||
if (ReferenceEquals(null, other)) return false;
|
if (ReferenceEquals(null, other)) return false;
|
||||||
|
@ -9,6 +9,7 @@ using System.Threading;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Humanizer;
|
using Humanizer;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
@ -658,11 +659,14 @@ namespace osu.Game
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override IDictionary<FrameworkSetting, object> GetFrameworkConfigDefaults()
|
protected override IDictionary<FrameworkSetting, object> GetFrameworkConfigDefaults()
|
||||||
=> new Dictionary<FrameworkSetting, object>
|
{
|
||||||
|
return new Dictionary<FrameworkSetting, object>
|
||||||
{
|
{
|
||||||
// General expectation that osu! starts in fullscreen by default (also gives the most predictable performance)
|
// General expectation that osu! starts in fullscreen by default (also gives the most predictable performance).
|
||||||
{ FrameworkSetting.WindowMode, WindowMode.Fullscreen }
|
// However, macOS is bound to have issues when using exclusive fullscreen as it takes full control away from OS, therefore borderless is default there.
|
||||||
|
{ FrameworkSetting.WindowMode, RuntimeInfo.OS == RuntimeInfo.Platform.macOS ? WindowMode.Borderless : WindowMode.Fullscreen }
|
||||||
};
|
};
|
||||||
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
@ -847,7 +851,7 @@ namespace osu.Game
|
|||||||
loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(news = new NewsOverlay(), overlayContent.Add, true);
|
||||||
var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true);
|
var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true);
|
loadComponentSingleFile(channelManager = new ChannelManager(API), AddInternal, true);
|
||||||
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
|
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
|
||||||
loadComponentSingleFile(new MessageNotifier(), AddInternal, true);
|
loadComponentSingleFile(new MessageNotifier(), AddInternal, true);
|
||||||
loadComponentSingleFile(Settings = new SettingsOverlay(), leftFloatingOverlayContent.Add, true);
|
loadComponentSingleFile(Settings = new SettingsOverlay(), leftFloatingOverlayContent.Add, true);
|
||||||
|
@ -80,6 +80,9 @@ namespace osu.Game
|
|||||||
|
|
||||||
public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild;
|
public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild;
|
||||||
|
|
||||||
|
internal EndpointConfiguration CreateEndpoints() =>
|
||||||
|
UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();
|
||||||
|
|
||||||
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
|
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -268,7 +271,7 @@ namespace osu.Game
|
|||||||
dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler));
|
dependencies.Cache(SkinManager = new SkinManager(Storage, realm, Host, Resources, Audio, Scheduler));
|
||||||
dependencies.CacheAs<ISkinSource>(SkinManager);
|
dependencies.CacheAs<ISkinSource>(SkinManager);
|
||||||
|
|
||||||
EndpointConfiguration endpoints = UseDevelopmentServer ? (EndpointConfiguration)new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration();
|
EndpointConfiguration endpoints = CreateEndpoints();
|
||||||
|
|
||||||
MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl;
|
MessageFormatter.WebsiteRootUrl = endpoints.WebsiteRootUrl;
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
@ -69,7 +70,7 @@ namespace osu.Game.Overlays.AccountCreation
|
|||||||
},
|
},
|
||||||
usernameTextBox = new OsuTextBox
|
usernameTextBox = new OsuTextBox
|
||||||
{
|
{
|
||||||
PlaceholderText = UsersStrings.LoginUsername,
|
PlaceholderText = UsersStrings.LoginUsername.ToLower(),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
TabbableContentContainer = this
|
TabbableContentContainer = this
|
||||||
},
|
},
|
||||||
@ -91,7 +92,7 @@ namespace osu.Game.Overlays.AccountCreation
|
|||||||
},
|
},
|
||||||
passwordTextBox = new OsuPasswordTextBox
|
passwordTextBox = new OsuPasswordTextBox
|
||||||
{
|
{
|
||||||
PlaceholderText = "password",
|
PlaceholderText = UsersStrings.LoginPassword.ToLower(),
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
TabbableContentContainer = this,
|
TabbableContentContainer = this,
|
||||||
},
|
},
|
||||||
|
@ -40,7 +40,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
|||||||
Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular),
|
Font = OsuFont.GetFont(size: 13, weight: FontWeight.Regular),
|
||||||
Text = LabelFor(Value)
|
Text = LabelFor(Value)
|
||||||
},
|
},
|
||||||
new HoverClickSounds()
|
new HoverClickSounds(HoverSampleSet.TabSelect)
|
||||||
});
|
});
|
||||||
|
|
||||||
Enabled.Value = true;
|
Enabled.Value = true;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
#nullable enable
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -26,42 +28,6 @@ namespace osu.Game.Overlays.Chat
|
|||||||
{
|
{
|
||||||
public class ChatLine : CompositeDrawable
|
public class ChatLine : CompositeDrawable
|
||||||
{
|
{
|
||||||
public const float LEFT_PADDING = default_message_padding + default_horizontal_padding * 2;
|
|
||||||
|
|
||||||
private const float default_message_padding = 200;
|
|
||||||
|
|
||||||
protected virtual float MessagePadding => default_message_padding;
|
|
||||||
|
|
||||||
private const float default_timestamp_padding = 65;
|
|
||||||
|
|
||||||
protected virtual float TimestampPadding => default_timestamp_padding;
|
|
||||||
|
|
||||||
private const float default_horizontal_padding = 15;
|
|
||||||
|
|
||||||
protected virtual float HorizontalPadding => default_horizontal_padding;
|
|
||||||
|
|
||||||
protected virtual float TextSize => 20;
|
|
||||||
|
|
||||||
private Color4 usernameColour;
|
|
||||||
|
|
||||||
private OsuSpriteText timestamp;
|
|
||||||
|
|
||||||
public ChatLine(Message message)
|
|
||||||
{
|
|
||||||
Message = message;
|
|
||||||
Padding = new MarginPadding { Left = HorizontalPadding, Right = HorizontalPadding };
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
}
|
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
|
||||||
private ChannelManager chatManager { get; set; }
|
|
||||||
|
|
||||||
private Message message;
|
|
||||||
private OsuSpriteText username;
|
|
||||||
|
|
||||||
public LinkFlowContainer ContentFlow { get; private set; }
|
|
||||||
|
|
||||||
public Message Message
|
public Message Message
|
||||||
{
|
{
|
||||||
get => message;
|
get => message;
|
||||||
@ -78,119 +44,101 @@ namespace osu.Game.Overlays.Chat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LinkFlowContainer ContentFlow { get; private set; } = null!;
|
||||||
|
|
||||||
|
protected virtual float TextSize => 20;
|
||||||
|
|
||||||
|
protected virtual float Spacing => 15;
|
||||||
|
|
||||||
|
protected virtual float TimestampWidth => 60;
|
||||||
|
|
||||||
|
protected virtual float UsernameWidth => 130;
|
||||||
|
|
||||||
|
private Color4 usernameColour;
|
||||||
|
|
||||||
|
private OsuSpriteText timestamp = null!;
|
||||||
|
|
||||||
|
private Message message = null!;
|
||||||
|
|
||||||
|
private OsuSpriteText username = null!;
|
||||||
|
|
||||||
|
private Container? highlight;
|
||||||
|
|
||||||
private bool senderHasColour => !string.IsNullOrEmpty(message.Sender.Colour);
|
private bool senderHasColour => !string.IsNullOrEmpty(message.Sender.Colour);
|
||||||
|
|
||||||
|
private bool messageHasColour => Message.IsAction && senderHasColour;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuColour colours { get; set; }
|
private ChannelManager? chatManager { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
|
public ChatLine(Message message)
|
||||||
|
{
|
||||||
|
Message = message;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(OverlayColourProvider? colourProvider)
|
||||||
{
|
{
|
||||||
usernameColour = senderHasColour
|
usernameColour = senderHasColour
|
||||||
? Color4Extensions.FromHex(message.Sender.Colour)
|
? Color4Extensions.FromHex(message.Sender.Colour)
|
||||||
: username_colours[message.Sender.Id % username_colours.Length];
|
: username_colours[message.Sender.Id % username_colours.Length];
|
||||||
|
|
||||||
Drawable effectedUsername = username = new OsuSpriteText
|
InternalChild = new GridContainer
|
||||||
{
|
{
|
||||||
Shadow = false,
|
RelativeSizeAxes = Axes.X,
|
||||||
Colour = senderHasColour ? colours.ChatBlue : usernameColour,
|
AutoSizeAxes = Axes.Y,
|
||||||
Truncate = true,
|
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) },
|
||||||
EllipsisString = "… :",
|
ColumnDimensions = new[]
|
||||||
Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true),
|
|
||||||
Anchor = Anchor.TopRight,
|
|
||||||
Origin = Anchor.TopRight,
|
|
||||||
MaxWidth = MessagePadding - TimestampPadding
|
|
||||||
};
|
|
||||||
|
|
||||||
if (senderHasColour)
|
|
||||||
{
|
|
||||||
// Background effect
|
|
||||||
effectedUsername = new Container
|
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
new Dimension(GridSizeMode.Absolute, TimestampWidth + Spacing + UsernameWidth + Spacing),
|
||||||
Masking = true,
|
new Dimension(),
|
||||||
CornerRadius = 4,
|
|
||||||
EdgeEffect = new EdgeEffectParameters
|
|
||||||
{
|
|
||||||
Roundness = 1,
|
|
||||||
Radius = 1,
|
|
||||||
Colour = Color4.Black.Opacity(0.3f),
|
|
||||||
Offset = new Vector2(0, 1),
|
|
||||||
Type = EdgeEffectType.Shadow,
|
|
||||||
},
|
|
||||||
Child = new Container
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Y = 0,
|
|
||||||
Masking = true,
|
|
||||||
CornerRadius = 4,
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new Box
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Colour = usernameColour,
|
|
||||||
},
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Padding = new MarginPadding { Left = 4, Right = 4, Bottom = 1, Top = -2 },
|
|
||||||
Child = username
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
Size = new Vector2(MessagePadding, TextSize),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
timestamp = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Shadow = false,
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Font = OsuFont.GetFont(size: TextSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true)
|
|
||||||
},
|
|
||||||
new MessageSender(message.Sender)
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Origin = Anchor.TopRight,
|
|
||||||
Anchor = Anchor.TopRight,
|
|
||||||
Child = effectedUsername,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
new Container
|
Content = new[]
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
new Drawable[]
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Padding = new MarginPadding { Left = MessagePadding + HorizontalPadding },
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
timestamp = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Shadow = false,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Font = OsuFont.GetFont(size: TextSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||||
|
MaxWidth = TimestampWidth,
|
||||||
|
Colour = colourProvider?.Background1 ?? Colour4.White,
|
||||||
|
},
|
||||||
|
new MessageSender(message.Sender)
|
||||||
|
{
|
||||||
|
Width = UsernameWidth,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Child = createUsername(),
|
||||||
|
Margin = new MarginPadding { Horizontal = Spacing },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
ContentFlow = new LinkFlowContainer(t =>
|
ContentFlow = new LinkFlowContainer(t =>
|
||||||
{
|
{
|
||||||
t.Shadow = false;
|
t.Shadow = false;
|
||||||
|
t.Font = t.Font.With(size: TextSize, italics: Message.IsAction);
|
||||||
if (Message.IsAction)
|
t.Colour = messageHasColour ? Color4Extensions.FromHex(message.Sender.Colour) : colourProvider?.Content1 ?? Colour4.White;
|
||||||
{
|
|
||||||
t.Font = OsuFont.GetFont(italics: true);
|
|
||||||
|
|
||||||
if (senderHasColour)
|
|
||||||
t.Colour = Color4Extensions.FromHex(message.Sender.Colour);
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Font = t.Font.With(size: TextSize);
|
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -203,8 +151,6 @@ namespace osu.Game.Overlays.Chat
|
|||||||
FinishTransforms(true);
|
FinishTransforms(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Container highlight;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs a highlight animation on this <see cref="ChatLine"/>.
|
/// Performs a highlight animation on this <see cref="ChatLine"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -233,7 +179,7 @@ namespace osu.Game.Overlays.Chat
|
|||||||
timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint);
|
timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint);
|
||||||
|
|
||||||
timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}";
|
timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}";
|
||||||
username.Text = $@"{message.Sender.Username}" + (senderHasColour || message.IsAction ? "" : ":");
|
username.Text = $@"{message.Sender.Username}";
|
||||||
|
|
||||||
// remove non-existent channels from the link list
|
// 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);
|
message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument.ToString()) != true);
|
||||||
@ -242,22 +188,78 @@ namespace osu.Game.Overlays.Chat
|
|||||||
ContentFlow.AddLinks(message.DisplayContent, message.Links);
|
ContentFlow.AddLinks(message.DisplayContent, message.Links);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Drawable createUsername()
|
||||||
|
{
|
||||||
|
username = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Shadow = false,
|
||||||
|
Colour = senderHasColour ? colours.ChatBlue : usernameColour,
|
||||||
|
Truncate = true,
|
||||||
|
EllipsisString = "…",
|
||||||
|
Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true),
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
MaxWidth = UsernameWidth,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!senderHasColour)
|
||||||
|
return username;
|
||||||
|
|
||||||
|
// Background effect
|
||||||
|
return new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopRight,
|
||||||
|
Origin = Anchor.TopRight,
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 4,
|
||||||
|
EdgeEffect = new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Roundness = 1,
|
||||||
|
Radius = 1,
|
||||||
|
Colour = Color4.Black.Opacity(0.3f),
|
||||||
|
Offset = new Vector2(0, 1),
|
||||||
|
Type = EdgeEffectType.Shadow,
|
||||||
|
},
|
||||||
|
Child = new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Masking = true,
|
||||||
|
CornerRadius = 4,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = usernameColour,
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Padding = new MarginPadding { Left = 4, Right = 4, Bottom = 1, Top = -2 },
|
||||||
|
Child = username
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private class MessageSender : OsuClickableContainer, IHasContextMenu
|
private class MessageSender : OsuClickableContainer, IHasContextMenu
|
||||||
{
|
{
|
||||||
private readonly APIUser sender;
|
private readonly APIUser sender;
|
||||||
|
|
||||||
private Action startChatAction;
|
private Action startChatAction = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private IAPIProvider api { get; set; }
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
public MessageSender(APIUser sender)
|
public MessageSender(APIUser sender)
|
||||||
{
|
{
|
||||||
this.sender = sender;
|
this.sender = sender;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader]
|
||||||
private void load(UserProfileOverlay profile, ChannelManager chatManager)
|
private void load(UserProfileOverlay? profile, ChannelManager? chatManager)
|
||||||
{
|
{
|
||||||
Action = () => profile?.ShowUser(sender);
|
Action = () => profile?.ShowUser(sender);
|
||||||
startChatAction = () => chatManager?.OpenPrivateChannel(sender);
|
startChatAction = () => chatManager?.OpenPrivateChannel(sender);
|
||||||
|
@ -1,109 +0,0 @@
|
|||||||
// 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 enable
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Game.Online.Chat;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Chat
|
|
||||||
{
|
|
||||||
public class ChatOverlayDrawableChannel : DrawableChannel
|
|
||||||
{
|
|
||||||
public ChatOverlayDrawableChannel(Channel channel)
|
|
||||||
: base(channel)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
ChatLineFlow.Padding = new MarginPadding(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override Drawable CreateDaySeparator(DateTimeOffset time) => new ChatOverlayDaySeparator(time);
|
|
||||||
|
|
||||||
private class ChatOverlayDaySeparator : Container
|
|
||||||
{
|
|
||||||
private readonly DateTimeOffset time;
|
|
||||||
|
|
||||||
public ChatOverlayDaySeparator(DateTimeOffset time)
|
|
||||||
{
|
|
||||||
this.time = time;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OverlayColourProvider colourProvider)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
Padding = new MarginPadding { Horizontal = 15, Vertical = 20 };
|
|
||||||
Child = new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(GridSizeMode.Absolute, 200),
|
|
||||||
new Dimension(GridSizeMode.Absolute, 15),
|
|
||||||
new Dimension(),
|
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.Absolute, 15),
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
},
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new[]
|
|
||||||
{
|
|
||||||
new Circle
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreRight,
|
|
||||||
Origin = Anchor.CentreRight,
|
|
||||||
Colour = colourProvider.Background5,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 2,
|
|
||||||
},
|
|
||||||
Drawable.Empty(),
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreRight,
|
|
||||||
Origin = Anchor.CentreRight,
|
|
||||||
Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(),
|
|
||||||
Font = OsuFont.Torus.With(size: 15, weight: FontWeight.SemiBold),
|
|
||||||
Colour = colourProvider.Content1,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
Drawable.Empty(),
|
|
||||||
new Circle
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Colour = colourProvider.Background5,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 2,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
105
osu.Game/Overlays/Chat/DaySeparator.cs
Normal file
105
osu.Game/Overlays/Chat/DaySeparator.cs
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
// 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 enable
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Chat
|
||||||
|
{
|
||||||
|
public class DaySeparator : Container
|
||||||
|
{
|
||||||
|
protected virtual float TextSize => 15;
|
||||||
|
|
||||||
|
protected virtual float LineHeight => 2;
|
||||||
|
|
||||||
|
protected virtual float DateAlign => 205;
|
||||||
|
|
||||||
|
protected virtual float Spacing => 15;
|
||||||
|
|
||||||
|
private readonly DateTimeOffset time;
|
||||||
|
|
||||||
|
[Resolved(CanBeNull = true)]
|
||||||
|
private OverlayColourProvider? colourProvider { get; set; }
|
||||||
|
|
||||||
|
public DaySeparator(DateTimeOffset time)
|
||||||
|
{
|
||||||
|
this.time = time;
|
||||||
|
Height = 40;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
Child = new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
RowDimensions = new[] { new Dimension() },
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.Absolute, DateAlign),
|
||||||
|
new Dimension(GridSizeMode.Absolute, Spacing),
|
||||||
|
new Dimension(),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
RowDimensions = new[] { new Dimension() },
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(),
|
||||||
|
new Dimension(GridSizeMode.Absolute, Spacing),
|
||||||
|
new Dimension(GridSizeMode.AutoSize),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new[]
|
||||||
|
{
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = LineHeight,
|
||||||
|
Colour = colourProvider?.Background5 ?? Colour4.White,
|
||||||
|
},
|
||||||
|
Drawable.Empty(),
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Text = time.ToLocalTime().ToString("dd MMMM yyyy").ToUpper(),
|
||||||
|
Font = OsuFont.Torus.With(size: TextSize, weight: FontWeight.SemiBold),
|
||||||
|
Colour = colourProvider?.Content1 ?? Colour4.White,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Drawable.Empty(),
|
||||||
|
new Circle
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = LineHeight,
|
||||||
|
Colour = colourProvider?.Background5 ?? Colour4.White,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -7,14 +7,9 @@ using System.Diagnostics;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.Cursor;
|
using osu.Game.Graphics.Cursor;
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
|
|
||||||
@ -40,9 +35,6 @@ namespace osu.Game.Overlays.Chat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private OsuColour colours { get; set; }
|
|
||||||
|
|
||||||
public DrawableChannel(Channel channel)
|
public DrawableChannel(Channel channel)
|
||||||
{
|
{
|
||||||
Channel = channel;
|
Channel = channel;
|
||||||
@ -67,7 +59,7 @@ namespace osu.Game.Overlays.Chat
|
|||||||
Padding = new MarginPadding { Bottom = 5 },
|
Padding = new MarginPadding { Bottom = 5 },
|
||||||
Child = ChatLineFlow = new FillFlowContainer
|
Child = ChatLineFlow = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Padding = new MarginPadding { Left = 20, Right = 20 },
|
Padding = new MarginPadding { Horizontal = 10 },
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
Direction = FillDirection.Vertical,
|
Direction = FillDirection.Vertical,
|
||||||
@ -121,11 +113,7 @@ namespace osu.Game.Overlays.Chat
|
|||||||
|
|
||||||
protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m);
|
protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m);
|
||||||
|
|
||||||
protected virtual Drawable CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time)
|
protected virtual DaySeparator CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time);
|
||||||
{
|
|
||||||
Colour = colours.ChatBlue.Lighten(0.7f),
|
|
||||||
Margin = new MarginPadding { Vertical = 10 },
|
|
||||||
};
|
|
||||||
|
|
||||||
private void newMessagesArrived(IEnumerable<Message> newMessages) => Schedule(() =>
|
private void newMessagesArrived(IEnumerable<Message> newMessages) => Schedule(() =>
|
||||||
{
|
{
|
||||||
@ -203,69 +191,5 @@ namespace osu.Game.Overlays.Chat
|
|||||||
});
|
});
|
||||||
|
|
||||||
private IEnumerable<ChatLine> chatLines => ChatLineFlow.Children.OfType<ChatLine>();
|
private IEnumerable<ChatLine> chatLines => ChatLineFlow.Children.OfType<ChatLine>();
|
||||||
|
|
||||||
public class DaySeparator : Container
|
|
||||||
{
|
|
||||||
public float TextSize
|
|
||||||
{
|
|
||||||
get => text.Font.Size;
|
|
||||||
set => text.Font = text.Font.With(size: value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private float lineHeight = 2;
|
|
||||||
|
|
||||||
public float LineHeight
|
|
||||||
{
|
|
||||||
get => lineHeight;
|
|
||||||
set => lineHeight = leftBox.Height = rightBox.Height = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
private readonly SpriteText text;
|
|
||||||
private readonly Box leftBox;
|
|
||||||
private readonly Box rightBox;
|
|
||||||
|
|
||||||
public DaySeparator(DateTimeOffset time)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
Child = new GridContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
ColumnDimensions = new[]
|
|
||||||
{
|
|
||||||
new Dimension(),
|
|
||||||
new Dimension(GridSizeMode.AutoSize),
|
|
||||||
new Dimension(),
|
|
||||||
},
|
|
||||||
RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), },
|
|
||||||
Content = new[]
|
|
||||||
{
|
|
||||||
new Drawable[]
|
|
||||||
{
|
|
||||||
leftBox = new Box
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = lineHeight,
|
|
||||||
},
|
|
||||||
text = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Margin = new MarginPadding { Horizontal = 10 },
|
|
||||||
Text = time.ToLocalTime().ToString("dd MMM yyyy"),
|
|
||||||
},
|
|
||||||
rightBox = new Box
|
|
||||||
{
|
|
||||||
Anchor = Anchor.Centre,
|
|
||||||
Origin = Anchor.Centre,
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = lineHeight,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,8 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Audio;
|
||||||
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -16,6 +18,7 @@ using osu.Framework.Localisation;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -32,6 +35,8 @@ namespace osu.Game.Overlays.Chat.Listing
|
|||||||
public IEnumerable<LocalisableString> FilterTerms => new LocalisableString[] { Channel.Name, Channel.Topic ?? string.Empty };
|
public IEnumerable<LocalisableString> FilterTerms => new LocalisableString[] { Channel.Name, Channel.Topic ?? string.Empty };
|
||||||
public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); }
|
public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); }
|
||||||
|
|
||||||
|
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
||||||
|
|
||||||
private Box hoverBox = null!;
|
private Box hoverBox = null!;
|
||||||
private SpriteIcon checkbox = null!;
|
private SpriteIcon checkbox = null!;
|
||||||
private OsuSpriteText channelText = null!;
|
private OsuSpriteText channelText = null!;
|
||||||
@ -46,14 +51,20 @@ namespace osu.Game.Overlays.Chat.Listing
|
|||||||
|
|
||||||
private const float vertical_margin = 1.5f;
|
private const float vertical_margin = 1.5f;
|
||||||
|
|
||||||
|
private Sample? sampleJoin;
|
||||||
|
private Sample? sampleLeave;
|
||||||
|
|
||||||
public ChannelListingItem(Channel channel)
|
public ChannelListingItem(Channel channel)
|
||||||
{
|
{
|
||||||
Channel = channel;
|
Channel = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
|
sampleJoin = audio.Samples.Get(@"UI/check-on");
|
||||||
|
sampleLeave = audio.Samples.Get(@"UI/check-off");
|
||||||
|
|
||||||
Masking = true;
|
Masking = true;
|
||||||
CornerRadius = 5;
|
CornerRadius = 5;
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
@ -156,7 +167,19 @@ namespace osu.Game.Overlays.Chat.Listing
|
|||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(Channel);
|
Action = () =>
|
||||||
|
{
|
||||||
|
if (channelJoined.Value)
|
||||||
|
{
|
||||||
|
OnRequestLeave?.Invoke(Channel);
|
||||||
|
sampleLeave?.Play();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
OnRequestJoin?.Invoke(Channel);
|
||||||
|
sampleJoin?.Play();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
@ -38,9 +38,9 @@ namespace osu.Game.Overlays
|
|||||||
private LoadingLayer loading = null!;
|
private LoadingLayer loading = null!;
|
||||||
private ChannelListing channelListing = null!;
|
private ChannelListing channelListing = null!;
|
||||||
private ChatTextBar textBar = null!;
|
private ChatTextBar textBar = null!;
|
||||||
private Container<ChatOverlayDrawableChannel> currentChannelContainer = null!;
|
private Container<DrawableChannel> currentChannelContainer = null!;
|
||||||
|
|
||||||
private readonly Dictionary<Channel, ChatOverlayDrawableChannel> loadedChannels = new Dictionary<Channel, ChatOverlayDrawableChannel>();
|
private readonly Dictionary<Channel, DrawableChannel> loadedChannels = new Dictionary<Channel, DrawableChannel>();
|
||||||
|
|
||||||
protected IEnumerable<DrawableChannel> DrawableChannels => loadedChannels.Values;
|
protected IEnumerable<DrawableChannel> DrawableChannels => loadedChannels.Values;
|
||||||
|
|
||||||
@ -126,7 +126,7 @@ namespace osu.Game.Overlays
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colourProvider.Background4,
|
Colour = colourProvider.Background4,
|
||||||
},
|
},
|
||||||
currentChannelContainer = new Container<ChatOverlayDrawableChannel>
|
currentChannelContainer = new Container<DrawableChannel>
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
},
|
||||||
@ -313,7 +313,7 @@ namespace osu.Game.Overlays
|
|||||||
loading.Show();
|
loading.Show();
|
||||||
|
|
||||||
// Ensure the drawable channel is stored before async load to prevent double loading
|
// Ensure the drawable channel is stored before async load to prevent double loading
|
||||||
ChatOverlayDrawableChannel drawableChannel = CreateDrawableChannel(newChannel);
|
DrawableChannel drawableChannel = CreateDrawableChannel(newChannel);
|
||||||
loadedChannels.Add(newChannel, drawableChannel);
|
loadedChannels.Add(newChannel, drawableChannel);
|
||||||
|
|
||||||
LoadComponentAsync(drawableChannel, loadedDrawable =>
|
LoadComponentAsync(drawableChannel, loadedDrawable =>
|
||||||
@ -338,7 +338,7 @@ namespace osu.Game.Overlays
|
|||||||
channelManager.MarkChannelAsRead(newChannel);
|
channelManager.MarkChannelAsRead(newChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual ChatOverlayDrawableChannel CreateDrawableChannel(Channel newChannel) => new ChatOverlayDrawableChannel(newChannel);
|
protected virtual DrawableChannel CreateDrawableChannel(Channel newChannel) => new DrawableChannel(newChannel);
|
||||||
|
|
||||||
private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args)
|
private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args)
|
||||||
{
|
{
|
||||||
@ -361,7 +361,7 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
if (loadedChannels.ContainsKey(channel))
|
if (loadedChannels.ContainsKey(channel))
|
||||||
{
|
{
|
||||||
ChatOverlayDrawableChannel loaded = loadedChannels[channel];
|
DrawableChannel loaded = loadedChannels[channel];
|
||||||
loadedChannels.Remove(channel);
|
loadedChannels.Remove(channel);
|
||||||
// DrawableChannel removed from cache must be manually disposed
|
// DrawableChannel removed from cache must be manually disposed
|
||||||
loaded.Dispose();
|
loaded.Dispose();
|
||||||
|
@ -154,12 +154,15 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
|
|
||||||
var downloadTracker = tutorialDownloader.DownloadTrackers.First();
|
var downloadTracker = tutorialDownloader.DownloadTrackers.First();
|
||||||
|
|
||||||
|
downloadTracker.State.BindValueChanged(state =>
|
||||||
|
{
|
||||||
|
if (state.NewValue == DownloadState.LocallyAvailable)
|
||||||
|
downloadTutorialButton.Complete();
|
||||||
|
}, true);
|
||||||
|
|
||||||
downloadTracker.Progress.BindValueChanged(progress =>
|
downloadTracker.Progress.BindValueChanged(progress =>
|
||||||
{
|
{
|
||||||
downloadTutorialButton.SetProgress(progress.NewValue, false);
|
downloadTutorialButton.SetProgress(progress.NewValue, false);
|
||||||
|
|
||||||
if (progress.NewValue == 1)
|
|
||||||
downloadTutorialButton.Complete();
|
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -126,6 +126,7 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
private class SampleScreenContainer : CompositeDrawable
|
private class SampleScreenContainer : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly OsuScreen screen;
|
private readonly OsuScreen screen;
|
||||||
|
|
||||||
// Minimal isolation from main game.
|
// Minimal isolation from main game.
|
||||||
|
|
||||||
[Cached]
|
[Cached]
|
||||||
@ -151,6 +152,9 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) =>
|
||||||
|
new DependencyContainer(new DependencyIsolationContainer(base.CreateChildDependencies(parent)));
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio, TextureStore textures, RulesetStore rulesets)
|
private void load(AudioManager audio, TextureStore textures, RulesetStore rulesets)
|
||||||
{
|
{
|
||||||
@ -197,5 +201,41 @@ namespace osu.Game.Overlays.FirstRunSetup
|
|||||||
stack.PushSynchronously(screen);
|
stack.PushSynchronously(screen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class DependencyIsolationContainer : IReadOnlyDependencyContainer
|
||||||
|
{
|
||||||
|
private readonly IReadOnlyDependencyContainer parentDependencies;
|
||||||
|
|
||||||
|
private readonly Type[] isolatedTypes =
|
||||||
|
{
|
||||||
|
typeof(OsuGame)
|
||||||
|
};
|
||||||
|
|
||||||
|
public DependencyIsolationContainer(IReadOnlyDependencyContainer parentDependencies)
|
||||||
|
{
|
||||||
|
this.parentDependencies = parentDependencies;
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(Type type)
|
||||||
|
{
|
||||||
|
if (isolatedTypes.Contains(type))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return parentDependencies.Get(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
public object Get(Type type, CacheInfo info)
|
||||||
|
{
|
||||||
|
if (isolatedTypes.Contains(type))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
return parentDependencies.Get(type, info);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Inject<T>(T instance) where T : class
|
||||||
|
{
|
||||||
|
parentDependencies.Inject(instance);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,6 @@ using osu.Framework.Platform;
|
|||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.News
|
namespace osu.Game.Overlays.News
|
||||||
@ -29,7 +28,6 @@ namespace osu.Game.Overlays.News
|
|||||||
private TextFlowContainer main;
|
private TextFlowContainer main;
|
||||||
|
|
||||||
public NewsCard(APINewsPost post)
|
public NewsCard(APINewsPost post)
|
||||||
: base(HoverSampleSet.Submit)
|
|
||||||
{
|
{
|
||||||
this.post = post;
|
this.post = post;
|
||||||
|
|
||||||
|
@ -18,7 +18,6 @@ using System.Diagnostics;
|
|||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.News.Sidebar
|
namespace osu.Game.Overlays.News.Sidebar
|
||||||
{
|
{
|
||||||
@ -129,7 +128,6 @@ namespace osu.Game.Overlays.News.Sidebar
|
|||||||
private readonly APINewsPost post;
|
private readonly APINewsPost post;
|
||||||
|
|
||||||
public PostButton(APINewsPost post)
|
public PostButton(APINewsPost post)
|
||||||
: base(HoverSampleSet.Submit)
|
|
||||||
{
|
{
|
||||||
this.post = post;
|
this.post = post;
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
|||||||
private Sample sampleOpen;
|
private Sample sampleOpen;
|
||||||
private Sample sampleClose;
|
private Sample sampleClose;
|
||||||
|
|
||||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverClickSounds();
|
||||||
|
|
||||||
public ExpandDetailsButton()
|
public ExpandDetailsButton()
|
||||||
{
|
{
|
||||||
|
@ -6,7 +6,6 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Profile.Sections
|
namespace osu.Game.Overlays.Profile.Sections
|
||||||
{
|
{
|
||||||
@ -18,7 +17,6 @@ namespace osu.Game.Overlays.Profile.Sections
|
|||||||
private readonly IBeatmapInfo beatmapInfo;
|
private readonly IBeatmapInfo beatmapInfo;
|
||||||
|
|
||||||
protected BeatmapMetadataContainer(IBeatmapInfo beatmapInfo)
|
protected BeatmapMetadataContainer(IBeatmapInfo beatmapInfo)
|
||||||
: base(HoverSampleSet.Submit)
|
|
||||||
{
|
{
|
||||||
this.beatmapInfo = beatmapInfo;
|
this.beatmapInfo = beatmapInfo;
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Drawing;
|
using System.Drawing;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using osu.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Configuration;
|
using osu.Framework.Configuration;
|
||||||
@ -13,6 +14,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
|
using osu.Framework.Platform.Windows;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -34,10 +36,14 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
private Bindable<Size> sizeFullscreen;
|
private Bindable<Size> sizeFullscreen;
|
||||||
|
|
||||||
private readonly BindableList<Size> resolutions = new BindableList<Size>(new[] { new Size(9999, 9999) });
|
private readonly BindableList<Size> resolutions = new BindableList<Size>(new[] { new Size(9999, 9999) });
|
||||||
|
private readonly IBindable<FullscreenCapability> fullscreenCapability = new Bindable<FullscreenCapability>(FullscreenCapability.Capable);
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuGameBase game { get; set; }
|
private OsuGameBase game { get; set; }
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
private SettingsDropdown<Size> resolutionDropdown;
|
private SettingsDropdown<Size> resolutionDropdown;
|
||||||
private SettingsDropdown<Display> displayDropdown;
|
private SettingsDropdown<Display> displayDropdown;
|
||||||
private SettingsDropdown<WindowMode> windowModeDropdown;
|
private SettingsDropdown<WindowMode> windowModeDropdown;
|
||||||
@ -65,6 +71,9 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
windowModes.BindTo(host.Window.SupportedWindowModes);
|
windowModes.BindTo(host.Window.SupportedWindowModes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (host.Window is WindowsWindow windowsWindow)
|
||||||
|
fullscreenCapability.BindTo(windowsWindow.FullscreenCapability);
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
windowModeDropdown = new SettingsDropdown<WindowMode>
|
windowModeDropdown = new SettingsDropdown<WindowMode>
|
||||||
@ -139,6 +148,8 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
fullscreenCapability.BindValueChanged(_ => Schedule(updateScreenModeWarning), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -150,8 +161,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
windowModeDropdown.Current.BindValueChanged(mode =>
|
windowModeDropdown.Current.BindValueChanged(mode =>
|
||||||
{
|
{
|
||||||
updateDisplayModeDropdowns();
|
updateDisplayModeDropdowns();
|
||||||
|
updateScreenModeWarning();
|
||||||
windowModeDropdown.WarningText = mode.NewValue != WindowMode.Fullscreen ? GraphicsSettingsStrings.NotFullscreenNote : default;
|
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
windowModes.BindCollectionChanged((sender, args) =>
|
windowModes.BindCollectionChanged((sender, args) =>
|
||||||
@ -213,6 +223,48 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateScreenModeWarning()
|
||||||
|
{
|
||||||
|
if (RuntimeInfo.OS == RuntimeInfo.Platform.macOS)
|
||||||
|
{
|
||||||
|
if (windowModeDropdown.Current.Value == WindowMode.Fullscreen)
|
||||||
|
windowModeDropdown.SetNoticeText(LayoutSettingsStrings.FullscreenMacOSNote, true);
|
||||||
|
else
|
||||||
|
windowModeDropdown.ClearNoticeText();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (windowModeDropdown.Current.Value != WindowMode.Fullscreen)
|
||||||
|
{
|
||||||
|
windowModeDropdown.SetNoticeText(GraphicsSettingsStrings.NotFullscreenNote, true);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (host.Window is WindowsWindow)
|
||||||
|
{
|
||||||
|
switch (fullscreenCapability.Value)
|
||||||
|
{
|
||||||
|
case FullscreenCapability.Unknown:
|
||||||
|
windowModeDropdown.SetNoticeText(LayoutSettingsStrings.CheckingForFullscreenCapabilities, true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FullscreenCapability.Capable:
|
||||||
|
windowModeDropdown.SetNoticeText(LayoutSettingsStrings.OsuIsRunningExclusiveFullscreen);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case FullscreenCapability.Incapable:
|
||||||
|
windowModeDropdown.SetNoticeText(LayoutSettingsStrings.UnableToRunExclusiveFullscreen, true);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// We can only detect exclusive fullscreen status on windows currently.
|
||||||
|
windowModeDropdown.ClearNoticeText();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void bindPreviewEvent(Bindable<float> bindable)
|
private void bindPreviewEvent(Bindable<float> bindable)
|
||||||
{
|
{
|
||||||
bindable.ValueChanged += _ =>
|
bindable.ValueChanged += _ =>
|
||||||
|
@ -48,7 +48,16 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
|
|||||||
|
|
||||||
frameLimiterDropdown.Current.BindValueChanged(limit =>
|
frameLimiterDropdown.Current.BindValueChanged(limit =>
|
||||||
{
|
{
|
||||||
frameLimiterDropdown.WarningText = limit.NewValue == FrameSync.Unlimited ? GraphicsSettingsStrings.UnlimitedFramesNote : default;
|
switch (limit.NewValue)
|
||||||
|
{
|
||||||
|
case FrameSync.Unlimited:
|
||||||
|
frameLimiterDropdown.SetNoticeText(GraphicsSettingsStrings.UnlimitedFramesNote, true);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
frameLimiterDropdown.ClearNoticeText();
|
||||||
|
break;
|
||||||
|
}
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,9 +117,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
|
if (RuntimeInfo.OS != RuntimeInfo.Platform.Windows)
|
||||||
{
|
{
|
||||||
if (highPrecision.NewValue)
|
if (highPrecision.NewValue)
|
||||||
highPrecisionMouse.WarningText = MouseSettingsStrings.HighPrecisionPlatformWarning;
|
highPrecisionMouse.SetNoticeText(MouseSettingsStrings.HighPrecisionPlatformWarning, true);
|
||||||
else
|
else
|
||||||
highPrecisionMouse.WarningText = null;
|
highPrecisionMouse.ClearNoticeText();
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Localisation;
|
|||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
using osu.Framework.Threading;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
@ -95,11 +96,13 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
Text = TabletSettingsStrings.NoTabletDetected,
|
Text = TabletSettingsStrings.NoTabletDetected,
|
||||||
},
|
},
|
||||||
new SettingsNoticeText(colours)
|
new LinkFlowContainer(cp => cp.Colour = colours.Yellow)
|
||||||
{
|
{
|
||||||
TextAnchor = Anchor.TopCentre,
|
TextAnchor = Anchor.TopCentre,
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
}.With(t =>
|
}.With(t =>
|
||||||
{
|
{
|
||||||
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux)
|
if (RuntimeInfo.OS == RuntimeInfo.Platform.Windows || RuntimeInfo.OS == RuntimeInfo.Platform.Linux)
|
||||||
|
@ -28,6 +28,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
private SettingsButton deleteSkinsButton;
|
private SettingsButton deleteSkinsButton;
|
||||||
private SettingsButton restoreButton;
|
private SettingsButton restoreButton;
|
||||||
private SettingsButton undeleteButton;
|
private SettingsButton undeleteButton;
|
||||||
|
private SettingsButton deleteBeatmapVideosButton;
|
||||||
|
|
||||||
[BackgroundDependencyLoader(permitNulls: true)]
|
[BackgroundDependencyLoader(permitNulls: true)]
|
||||||
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay)
|
private void load(BeatmapManager beatmaps, ScoreManager scores, SkinManager skins, [CanBeNull] CollectionManager collectionManager, [CanBeNull] LegacyImportManager legacyImportManager, IDialogOverlay dialogOverlay)
|
||||||
@ -58,6 +59,19 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Add(deleteBeatmapVideosButton = new DangerousSettingsButton
|
||||||
|
{
|
||||||
|
Text = MaintenanceSettingsStrings.DeleteAllBeatmapVideos,
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
dialogOverlay?.Push(new MassVideoDeleteConfirmationDialog(() =>
|
||||||
|
{
|
||||||
|
deleteBeatmapVideosButton.Enabled.Value = false;
|
||||||
|
Task.Run(beatmaps.DeleteAllVideos).ContinueWith(t => Schedule(() => deleteBeatmapVideosButton.Enabled.Value = true));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
if (legacyImportManager?.SupportsImportFromStable == true)
|
if (legacyImportManager?.SupportsImportFromStable == true)
|
||||||
{
|
{
|
||||||
Add(importScoresButton = new SettingsButton
|
Add(importScoresButton = new SettingsButton
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
// 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;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Settings.Sections.Maintenance
|
||||||
|
{
|
||||||
|
public class MassVideoDeleteConfirmationDialog : MassDeleteConfirmationDialog
|
||||||
|
{
|
||||||
|
public MassVideoDeleteConfirmationDialog(Action deleteAction)
|
||||||
|
: base(deleteAction)
|
||||||
|
{
|
||||||
|
BodyText = "All beatmap videos? This cannot be undone!";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user