mirror of
https://github.com/ppy/osu.git
synced 2025-02-05 04:52:53 +08:00
Merge branch 'master' into beatmap-difficulty-manager
This commit is contained in:
commit
599a15edb8
@ -5,6 +5,6 @@
|
|||||||
"version": "3.1.100"
|
"version": "3.1.100"
|
||||||
},
|
},
|
||||||
"msbuild-sdks": {
|
"msbuild-sdks": {
|
||||||
"Microsoft.Build.Traversal": "2.0.50"
|
"Microsoft.Build.Traversal": "2.0.52"
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Catch.Beatmaps;
|
using osu.Game.Rulesets.Catch.Beatmaps;
|
||||||
using osu.Game.Rulesets.Catch.Judgements;
|
using osu.Game.Rulesets.Catch.Judgements;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
@ -25,6 +26,11 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
{
|
{
|
||||||
private RulesetInfo catchRuleset;
|
private RulesetInfo catchRuleset;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
|
private Catcher catcher => this.ChildrenOfType<CatcherArea>().First().MovableCatcher;
|
||||||
|
|
||||||
public TestSceneCatcherArea()
|
public TestSceneCatcherArea()
|
||||||
{
|
{
|
||||||
AddSliderStep<float>("CircleSize", 0, 8, 5, createCatcher);
|
AddSliderStep<float>("CircleSize", 0, 8, 5, createCatcher);
|
||||||
@ -34,24 +40,43 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
AddRepeatStep("catch fruit", () => catchFruit(new TestFruit(false)
|
AddRepeatStep("catch fruit", () => catchFruit(new TestFruit(false)
|
||||||
{
|
{
|
||||||
X = this.ChildrenOfType<CatcherArea>().First().MovableCatcher.X
|
X = catcher.X
|
||||||
}), 20);
|
}), 20);
|
||||||
AddRepeatStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
|
AddRepeatStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
|
||||||
{
|
{
|
||||||
X = this.ChildrenOfType<CatcherArea>().First().MovableCatcher.X,
|
X = catcher.X,
|
||||||
LastInCombo = true,
|
LastInCombo = true,
|
||||||
}), 20);
|
}), 20);
|
||||||
AddRepeatStep("catch kiai fruit", () => catchFruit(new TestFruit(true)
|
AddRepeatStep("catch kiai fruit", () => catchFruit(new TestFruit(true)
|
||||||
{
|
{
|
||||||
X = this.ChildrenOfType<CatcherArea>().First().MovableCatcher.X,
|
X = catcher.X
|
||||||
}), 20);
|
}), 20);
|
||||||
AddRepeatStep("miss fruit", () => catchFruit(new Fruit
|
AddRepeatStep("miss fruit", () => catchFruit(new Fruit
|
||||||
{
|
{
|
||||||
X = this.ChildrenOfType<CatcherArea>().First().MovableCatcher.X + 100,
|
X = catcher.X + 100,
|
||||||
LastInCombo = true,
|
LastInCombo = true,
|
||||||
}, true), 20);
|
}, true), 20);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(true)]
|
||||||
|
[TestCase(false)]
|
||||||
|
public void TestHitLighting(bool enable)
|
||||||
|
{
|
||||||
|
AddStep("create catcher", () => createCatcher(5));
|
||||||
|
|
||||||
|
AddStep("toggle hit lighting", () => config.Set(OsuSetting.HitLighting, enable));
|
||||||
|
AddStep("catch fruit", () => catchFruit(new TestFruit(false)
|
||||||
|
{
|
||||||
|
X = catcher.X
|
||||||
|
}));
|
||||||
|
AddStep("catch fruit last in combo", () => catchFruit(new TestFruit(false)
|
||||||
|
{
|
||||||
|
X = catcher.X,
|
||||||
|
LastInCombo = true
|
||||||
|
}));
|
||||||
|
AddAssert("check hit explosion", () => catcher.ChildrenOfType<HitExplosion>().Any() == enable);
|
||||||
|
}
|
||||||
|
|
||||||
private void catchFruit(Fruit fruit, bool miss = false)
|
private void catchFruit(Fruit fruit, bool miss = false)
|
||||||
{
|
{
|
||||||
this.ChildrenOfType<CatcherArea>().ForEach(area =>
|
this.ChildrenOfType<CatcherArea>().ForEach(area =>
|
||||||
|
@ -35,22 +35,25 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> createDrawableRepresentation)
|
||||||
{
|
{
|
||||||
Container explodingFruitContainer;
|
var explodingFruitContainer = new Container
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
explodingFruitContainer = new Container
|
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
},
|
};
|
||||||
|
|
||||||
CatcherArea = new CatcherArea(difficulty)
|
CatcherArea = new CatcherArea(difficulty)
|
||||||
{
|
{
|
||||||
CreateDrawableRepresentation = createDrawableRepresentation,
|
CreateDrawableRepresentation = createDrawableRepresentation,
|
||||||
ExplodingFruitTarget = explodingFruitContainer,
|
ExplodingFruitTarget = explodingFruitContainer,
|
||||||
Anchor = Anchor.BottomLeft,
|
Anchor = Anchor.BottomLeft,
|
||||||
Origin = Anchor.TopLeft,
|
Origin = Anchor.TopLeft,
|
||||||
},
|
};
|
||||||
HitObjectContainer
|
|
||||||
|
InternalChildren = new[]
|
||||||
|
{
|
||||||
|
explodingFruitContainer,
|
||||||
|
CatcherArea.MovableCatcher.CreateProxiedContent(),
|
||||||
|
HitObjectContainer,
|
||||||
|
CatcherArea
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,12 +5,14 @@ using System;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Catch.Objects;
|
using osu.Game.Rulesets.Catch.Objects;
|
||||||
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
using osu.Game.Rulesets.Catch.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Catch.Skinning;
|
using osu.Game.Rulesets.Catch.Skinning;
|
||||||
@ -46,6 +48,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public Container ExplodingFruitTarget;
|
public Container ExplodingFruitTarget;
|
||||||
|
|
||||||
|
private Container<DrawableHitObject> caughtFruitContainer { get; } = new Container<DrawableHitObject>
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.BottomCentre,
|
||||||
|
};
|
||||||
|
|
||||||
[NotNull]
|
[NotNull]
|
||||||
private readonly Container trailsTarget;
|
private readonly Container trailsTarget;
|
||||||
|
|
||||||
@ -83,8 +91,6 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private readonly float catchWidth;
|
private readonly float catchWidth;
|
||||||
|
|
||||||
private Container<DrawableHitObject> caughtFruit;
|
|
||||||
|
|
||||||
private CatcherSprite catcherIdle;
|
private CatcherSprite catcherIdle;
|
||||||
private CatcherSprite catcherKiai;
|
private CatcherSprite catcherKiai;
|
||||||
private CatcherSprite catcherFail;
|
private CatcherSprite catcherFail;
|
||||||
@ -99,6 +105,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private double hyperDashModifier = 1;
|
private double hyperDashModifier = 1;
|
||||||
private int hyperDashDirection;
|
private int hyperDashDirection;
|
||||||
private float hyperDashTargetPosition;
|
private float hyperDashTargetPosition;
|
||||||
|
private Bindable<bool> hitLighting;
|
||||||
|
|
||||||
public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
|
public Catcher([NotNull] Container trailsTarget, BeatmapDifficulty difficulty = null)
|
||||||
{
|
{
|
||||||
@ -114,15 +121,13 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(OsuConfigManager config)
|
||||||
{
|
{
|
||||||
|
hitLighting = config.GetBindable<bool>(OsuSetting.HitLighting);
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
caughtFruit = new Container<DrawableHitObject>
|
caughtFruitContainer,
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.BottomCentre,
|
|
||||||
},
|
|
||||||
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
|
catcherIdle = new CatcherSprite(CatcherAnimationState.Idle)
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
@ -145,6 +150,11 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
updateCatcher();
|
updateCatcher();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates proxied content to be displayed beneath hitobjects.
|
||||||
|
/// </summary>
|
||||||
|
public Drawable CreateProxiedContent() => caughtFruitContainer.CreateProxy();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
/// Calculates the scale of the catcher based off the provided beatmap difficulty.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -176,7 +186,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
const float allowance = 10;
|
const float allowance = 10;
|
||||||
|
|
||||||
while (caughtFruit.Any(f =>
|
while (caughtFruitContainer.Any(f =>
|
||||||
f.LifetimeEnd == double.MaxValue &&
|
f.LifetimeEnd == double.MaxValue &&
|
||||||
Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
|
Vector2Extensions.Distance(f.Position, fruit.Position) < (ourRadius + (theirRadius = f.DrawSize.X / 2 * f.Scale.X)) / (allowance / 2)))
|
||||||
{
|
{
|
||||||
@ -187,14 +197,17 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
fruit.X = Math.Clamp(fruit.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
|
fruit.X = Math.Clamp(fruit.X, -CatcherArea.CATCHER_SIZE / 2, CatcherArea.CATCHER_SIZE / 2);
|
||||||
|
|
||||||
caughtFruit.Add(fruit);
|
caughtFruitContainer.Add(fruit);
|
||||||
|
|
||||||
|
if (hitLighting.Value)
|
||||||
|
{
|
||||||
AddInternal(new HitExplosion(fruit)
|
AddInternal(new HitExplosion(fruit)
|
||||||
{
|
{
|
||||||
X = fruit.X,
|
X = fruit.X,
|
||||||
Scale = new Vector2(fruit.HitObject.Scale)
|
Scale = new Vector2(fruit.HitObject.Scale)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Let the catcher attempt to catch a fruit.
|
/// Let the catcher attempt to catch a fruit.
|
||||||
@ -342,7 +355,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Drop()
|
public void Drop()
|
||||||
{
|
{
|
||||||
foreach (var f in caughtFruit.ToArray())
|
foreach (var f in caughtFruitContainer.ToArray())
|
||||||
Drop(f);
|
Drop(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -351,7 +364,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void Explode()
|
public void Explode()
|
||||||
{
|
{
|
||||||
foreach (var f in caughtFruit.ToArray())
|
foreach (var f in caughtFruitContainer.ToArray())
|
||||||
Explode(f);
|
Explode(f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,9 +463,9 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
if (ExplodingFruitTarget != null)
|
if (ExplodingFruitTarget != null)
|
||||||
{
|
{
|
||||||
fruit.Anchor = Anchor.TopLeft;
|
fruit.Anchor = Anchor.TopLeft;
|
||||||
fruit.Position = caughtFruit.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
|
fruit.Position = caughtFruitContainer.ToSpaceOfOtherDrawable(fruit.DrawPosition, ExplodingFruitTarget);
|
||||||
|
|
||||||
if (!caughtFruit.Remove(fruit))
|
if (!caughtFruitContainer.Remove(fruit))
|
||||||
// we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
|
// we may have already been removed by a previous operation (due to the weird OnLoadComplete scheduling).
|
||||||
// this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
|
// this avoids a crash on potentially attempting to Add a fruit to ExplodingFruitTarget twice.
|
||||||
return;
|
return;
|
||||||
|
@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
|
|
||||||
public Func<CatchHitObject, DrawableHitObject<CatchHitObject>> CreateDrawableRepresentation;
|
public Func<CatchHitObject, DrawableHitObject<CatchHitObject>> CreateDrawableRepresentation;
|
||||||
|
|
||||||
|
public readonly Catcher MovableCatcher;
|
||||||
|
|
||||||
public Container ExplodingFruitTarget
|
public Container ExplodingFruitTarget
|
||||||
{
|
{
|
||||||
set => MovableCatcher.ExplodingFruitTarget = value;
|
set => MovableCatcher.ExplodingFruitTarget = value;
|
||||||
@ -104,7 +106,5 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
if (state?.CatcherX != null)
|
if (state?.CatcherX != null)
|
||||||
MovableCatcher.X = state.CatcherX.Value;
|
MovableCatcher.X = state.CatcherX.Value;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal readonly Catcher MovableCatcher;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Screens;
|
using osu.Framework.Screens;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
@ -10,6 +11,8 @@ using osu.Game.Replays;
|
|||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
using osu.Game.Rulesets.Mania.Replays;
|
using osu.Game.Rulesets.Mania.Replays;
|
||||||
|
using osu.Game.Rulesets.Mania.Scoring;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Replays;
|
using osu.Game.Rulesets.Replays;
|
||||||
using osu.Game.Rulesets.Scoring;
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
@ -236,6 +239,53 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
assertTailJudgement(HitResult.Meh);
|
assertTailJudgement(HitResult.Meh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMissReleaseAndHitSecondRelease()
|
||||||
|
{
|
||||||
|
var windows = new ManiaHitWindows();
|
||||||
|
windows.SetDifficulty(10);
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = 1000,
|
||||||
|
Duration = 500,
|
||||||
|
Column = 0,
|
||||||
|
},
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = 1000 + 500 + windows.WindowFor(HitResult.Miss) + 10,
|
||||||
|
Duration = 500,
|
||||||
|
Column = 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
BaseDifficulty = new BeatmapDifficulty
|
||||||
|
{
|
||||||
|
SliderTickRate = 4,
|
||||||
|
OverallDifficulty = 10,
|
||||||
|
},
|
||||||
|
Ruleset = new ManiaRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(beatmap.HitObjects[1].StartTime, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(beatmap.HitObjects[1].GetEndTime()),
|
||||||
|
}, beatmap);
|
||||||
|
|
||||||
|
AddAssert("first hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[0].NestedHitObjects.Contains(j.HitObject))
|
||||||
|
.All(j => j.Type == HitResult.Miss));
|
||||||
|
|
||||||
|
AddAssert("second hold note missed", () => judgementResults.Where(j => beatmap.HitObjects[1].NestedHitObjects.Contains(j.HitObject))
|
||||||
|
.All(j => j.Type == HitResult.Perfect));
|
||||||
|
}
|
||||||
|
|
||||||
private void assertHeadJudgement(HitResult result)
|
private void assertHeadJudgement(HitResult result)
|
||||||
=> AddAssert($"head judged as {result}", () => judgementResults[0].Type == result);
|
=> AddAssert($"head judged as {result}", () => judgementResults[0].Type == result);
|
||||||
|
|
||||||
@ -250,11 +300,11 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
|
|
||||||
private ScoreAccessibleReplayPlayer currentPlayer;
|
private ScoreAccessibleReplayPlayer currentPlayer;
|
||||||
|
|
||||||
private void performTest(List<ReplayFrame> frames)
|
private void performTest(List<ReplayFrame> frames, Beatmap<ManiaHitObject> beatmap = null)
|
||||||
{
|
{
|
||||||
AddStep("load player", () =>
|
if (beatmap == null)
|
||||||
{
|
{
|
||||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap<ManiaHitObject>
|
beatmap = new Beatmap<ManiaHitObject>
|
||||||
{
|
{
|
||||||
HitObjects =
|
HitObjects =
|
||||||
{
|
{
|
||||||
@ -270,9 +320,14 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||||
Ruleset = new ManiaRuleset().RulesetInfo
|
Ruleset = new ManiaRuleset().RulesetInfo
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f });
|
||||||
|
}
|
||||||
|
|
||||||
|
AddStep("load player", () =>
|
||||||
|
{
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(beatmap);
|
||||||
|
|
||||||
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } });
|
||||||
|
|
||||||
|
@ -0,0 +1,56 @@
|
|||||||
|
// 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.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
|
{
|
||||||
|
public class TestScenePlayfieldCoveringContainer : OsuTestScene
|
||||||
|
{
|
||||||
|
private readonly ScrollingTestContainer scrollingContainer;
|
||||||
|
private readonly PlayfieldCoveringWrapper cover;
|
||||||
|
|
||||||
|
public TestScenePlayfieldCoveringContainer()
|
||||||
|
{
|
||||||
|
Child = scrollingContainer = new ScrollingTestContainer(ScrollingDirection.Down)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Size = new Vector2(300, 500),
|
||||||
|
Child = cover = new PlayfieldCoveringWrapper(new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = Color4.Orange
|
||||||
|
})
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScrollingDownwards()
|
||||||
|
{
|
||||||
|
AddStep("set down scroll", () => scrollingContainer.Direction = ScrollingDirection.Down);
|
||||||
|
AddStep("set coverage = 0.5", () => cover.Coverage = 0.5f);
|
||||||
|
AddStep("set coverage = 0.8f", () => cover.Coverage = 0.8f);
|
||||||
|
AddStep("set coverage = 0.2f", () => cover.Coverage = 0.2f);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestScrollingUpwards()
|
||||||
|
{
|
||||||
|
AddStep("set up scroll", () => scrollingContainer.Direction = ScrollingDirection.Up);
|
||||||
|
AddStep("set coverage = 0.5", () => cover.Coverage = 0.5f);
|
||||||
|
AddStep("set coverage = 0.8f", () => cover.Coverage = 0.8f);
|
||||||
|
AddStep("set coverage = 0.2f", () => cover.Coverage = 0.2f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
@ -12,5 +13,7 @@ namespace osu.Game.Rulesets.Mania.Mods
|
|||||||
public override string Acronym => "FI";
|
public override string Acronym => "FI";
|
||||||
public override IconUsage? Icon => OsuIcon.ModHidden;
|
public override IconUsage? Icon => OsuIcon.ModHidden;
|
||||||
public override string Description => @"Keys appear out of nowhere!";
|
public override string Description => @"Keys appear out of nowhere!";
|
||||||
|
|
||||||
|
protected override CoverExpandDirection ExpandDirection => CoverExpandDirection.AlongScroll;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,15 +2,44 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Mania.Objects;
|
using osu.Game.Rulesets.Mania.Objects;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Mods
|
namespace osu.Game.Rulesets.Mania.Mods
|
||||||
{
|
{
|
||||||
public class ManiaModHidden : ModHidden
|
public class ManiaModHidden : ModHidden, IApplicableToDrawableRuleset<ManiaHitObject>
|
||||||
{
|
{
|
||||||
public override string Description => @"Keys fade out before you hit them!";
|
public override string Description => @"Keys fade out before you hit them!";
|
||||||
public override double ScoreMultiplier => 1;
|
public override double ScoreMultiplier => 1;
|
||||||
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight<ManiaHitObject>) };
|
public override Type[] IncompatibleMods => new[] { typeof(ModFlashlight<ManiaHitObject>) };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The direction in which the cover should expand.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual CoverExpandDirection ExpandDirection => CoverExpandDirection.AgainstScroll;
|
||||||
|
|
||||||
|
public virtual void ApplyToDrawableRuleset(DrawableRuleset<ManiaHitObject> drawableRuleset)
|
||||||
|
{
|
||||||
|
ManiaPlayfield maniaPlayfield = (ManiaPlayfield)drawableRuleset.Playfield;
|
||||||
|
|
||||||
|
foreach (Column column in maniaPlayfield.Stages.SelectMany(stage => stage.Columns))
|
||||||
|
{
|
||||||
|
HitObjectContainer hoc = column.HitObjectArea.HitObjectContainer;
|
||||||
|
Container hocParent = (Container)hoc.Parent;
|
||||||
|
|
||||||
|
hocParent.Remove(hoc);
|
||||||
|
hocParent.Add(new PlayfieldCoveringWrapper(hoc).With(c =>
|
||||||
|
{
|
||||||
|
c.RelativeSizeAxes = Axes.Both;
|
||||||
|
c.Direction = ExpandDirection;
|
||||||
|
c.Coverage = 0.5f;
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -167,6 +167,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
if (action != Action.Value)
|
if (action != Action.Value)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// The tail has a lenience applied to it which is factored into the miss window (i.e. the miss judgement will be delayed).
|
||||||
|
// But the hold cannot ever be started within the late-lenience window, so we should skip trying to begin the hold during that time.
|
||||||
|
// Note: Unlike below, we use the tail's start time to determine the time offset.
|
||||||
|
if (Time.Current > Tail.HitObject.StartTime && !Tail.HitObject.HitWindows.CanBeHit(Time.Current - Tail.HitObject.StartTime))
|
||||||
|
return false;
|
||||||
|
|
||||||
beginHoldAt(Time.Current - Head.HitObject.StartTime);
|
beginHoldAt(Time.Current - Head.HitObject.StartTime);
|
||||||
Head.UpdateResult();
|
Head.UpdateResult();
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
using osu.Game.Rulesets.Mania.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -31,7 +32,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
string imageName = GetColumnSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
|
string imageName = GetColumnSkinConfig<string>(skin, LegacyManiaSkinConfigurationLookups.HoldNoteBodyImage)?.Value
|
||||||
?? $"mania-note{FallbackColumnIndex}L";
|
?? $"mania-note{FallbackColumnIndex}L";
|
||||||
|
|
||||||
sprite = skin.GetAnimation(imageName, true, true).With(d =>
|
sprite = skin.GetAnimation(imageName, WrapMode.ClampToEdge, WrapMode.ClampToEdge, true, true).With(d =>
|
||||||
{
|
{
|
||||||
if (d == null)
|
if (d == null)
|
||||||
return;
|
return;
|
||||||
|
@ -5,6 +5,7 @@ using osu.Framework.Allocation;
|
|||||||
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;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -92,7 +93,7 @@ namespace osu.Game.Rulesets.Mania.Skinning
|
|||||||
string noteImage = GetColumnSkinConfig<string>(skin, lookup)?.Value
|
string noteImage = GetColumnSkinConfig<string>(skin, lookup)?.Value
|
||||||
?? $"mania-note{FallbackColumnIndex}{suffix}";
|
?? $"mania-note{FallbackColumnIndex}{suffix}";
|
||||||
|
|
||||||
return skin.GetTexture(noteImage);
|
return skin.GetTexture(noteImage, WrapMode.ClampToEdge, WrapMode.ClampToEdge);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,11 +33,11 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
|
|
||||||
public readonly Bindable<ManiaAction> Action = new Bindable<ManiaAction>();
|
public readonly Bindable<ManiaAction> Action = new Bindable<ManiaAction>();
|
||||||
|
|
||||||
private readonly ColumnHitObjectArea hitObjectArea;
|
public readonly ColumnHitObjectArea HitObjectArea;
|
||||||
|
|
||||||
internal readonly Container TopLevelContainer;
|
internal readonly Container TopLevelContainer;
|
||||||
|
|
||||||
public Container UnderlayElements => hitObjectArea.UnderlayElements;
|
public Container UnderlayElements => HitObjectArea.UnderlayElements;
|
||||||
|
|
||||||
public Column(int index)
|
public Column(int index)
|
||||||
{
|
{
|
||||||
@ -55,7 +55,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
{
|
{
|
||||||
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
// For input purposes, the background is added at the highest depth, but is then proxied back below all other elements
|
||||||
background.CreateProxy(),
|
background.CreateProxy(),
|
||||||
hitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
HitObjectArea = new ColumnHitObjectArea(Index, HitObjectContainer) { RelativeSizeAxes = Axes.Both },
|
||||||
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, Index), _ => new DefaultKeyArea())
|
new SkinnableDrawable(new ManiaSkinComponent(ManiaSkinComponents.KeyArea, Index), _ => new DefaultKeyArea())
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
@ -64,7 +64,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
TopLevelContainer = new Container { RelativeSizeAxes = Axes.Both }
|
||||||
};
|
};
|
||||||
|
|
||||||
TopLevelContainer.Add(hitObjectArea.Explosions.CreateProxy());
|
TopLevelContainer.Add(HitObjectArea.Explosions.CreateProxy());
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Axes RelativeSizeAxes => Axes.Y;
|
public override Axes RelativeSizeAxes => Axes.Y;
|
||||||
@ -114,7 +114,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
|||||||
RelativeSizeAxes = Axes.Both
|
RelativeSizeAxes = Axes.Both
|
||||||
};
|
};
|
||||||
|
|
||||||
hitObjectArea.Explosions.Add(explosion);
|
HitObjectArea.Explosions.Add(explosion);
|
||||||
|
|
||||||
explosion.Delay(200).Expire(true);
|
explosion.Delay(200).Expire(true);
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
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.Framework.Graphics.Containers;
|
||||||
using osu.Game.Rulesets.Mania.Skinning;
|
using osu.Game.Rulesets.Mania.Skinning;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Rulesets.UI.Scrolling;
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
@ -14,12 +15,14 @@ namespace osu.Game.Rulesets.Mania.UI.Components
|
|||||||
public class HitObjectArea : SkinReloadableDrawable
|
public class HitObjectArea : SkinReloadableDrawable
|
||||||
{
|
{
|
||||||
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
protected readonly IBindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
|
||||||
|
public readonly HitObjectContainer HitObjectContainer;
|
||||||
|
|
||||||
public HitObjectArea(HitObjectContainer hitObjectContainer)
|
public HitObjectArea(HitObjectContainer hitObjectContainer)
|
||||||
{
|
{
|
||||||
InternalChildren = new[]
|
InternalChild = new Container
|
||||||
{
|
{
|
||||||
hitObjectContainer,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = HitObjectContainer = hitObjectContainer
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
133
osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs
Normal file
133
osu.Game.Rulesets.Mania/UI/PlayfieldCoveringWrapper.cs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Rulesets.UI.Scrolling;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.UI
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="Container"/> that has its contents partially hidden by an adjustable "cover". This is intended to be used in a playfield.
|
||||||
|
/// </summary>
|
||||||
|
public class PlayfieldCoveringWrapper : CompositeDrawable
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The complete cover, including gradient and fill.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Drawable cover;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The gradient portion of the cover.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Box gradient;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The fully-opaque portion of the cover.
|
||||||
|
/// </summary>
|
||||||
|
private readonly Box filled;
|
||||||
|
|
||||||
|
private readonly IBindable<ScrollingDirection> scrollDirection = new Bindable<ScrollingDirection>();
|
||||||
|
|
||||||
|
public PlayfieldCoveringWrapper(Drawable content)
|
||||||
|
{
|
||||||
|
InternalChild = new BufferedContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Children = new[]
|
||||||
|
{
|
||||||
|
content,
|
||||||
|
cover = new Container
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Blending = new BlendingParameters
|
||||||
|
{
|
||||||
|
// Don't change the destination colour.
|
||||||
|
RGBEquation = BlendingEquation.Add,
|
||||||
|
Source = BlendingType.Zero,
|
||||||
|
Destination = BlendingType.One,
|
||||||
|
// Subtract the cover's alpha from the destination (points with alpha 1 should make the destination completely transparent).
|
||||||
|
AlphaEquation = BlendingEquation.Add,
|
||||||
|
SourceAlpha = BlendingType.Zero,
|
||||||
|
DestinationAlpha = BlendingType.OneMinusSrcAlpha
|
||||||
|
},
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
gradient = new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
RelativePositionAxes = Axes.Both,
|
||||||
|
Height = 0.25f,
|
||||||
|
Colour = ColourInfo.GradientVertical(
|
||||||
|
Color4.White.Opacity(0f),
|
||||||
|
Color4.White.Opacity(1f)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
filled = new Box
|
||||||
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IScrollingInfo scrollingInfo)
|
||||||
|
{
|
||||||
|
scrollDirection.BindTo(scrollingInfo.Direction);
|
||||||
|
scrollDirection.BindValueChanged(onScrollDirectionChanged, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onScrollDirectionChanged(ValueChangedEvent<ScrollingDirection> direction)
|
||||||
|
=> cover.Rotation = direction.NewValue == ScrollingDirection.Up ? 0 : 180f;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The relative area that should be completely covered. This does not include the fade.
|
||||||
|
/// </summary>
|
||||||
|
public float Coverage
|
||||||
|
{
|
||||||
|
set
|
||||||
|
{
|
||||||
|
filled.Height = value;
|
||||||
|
gradient.Y = -value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The direction in which the cover expands.
|
||||||
|
/// </summary>
|
||||||
|
public CoverExpandDirection Direction
|
||||||
|
{
|
||||||
|
set => cover.Scale = value == CoverExpandDirection.AlongScroll ? Vector2.One : new Vector2(1, -1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum CoverExpandDirection
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The cover expands along the scrolling direction.
|
||||||
|
/// </summary>
|
||||||
|
AlongScroll,
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The cover expands against the scrolling direction.
|
||||||
|
/// </summary>
|
||||||
|
AgainstScroll
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ 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;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Testing.Input;
|
using osu.Framework.Testing.Input;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -79,7 +80,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
|
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
|
||||||
|
|
||||||
public Texture GetTexture(string componentName)
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||||
{
|
{
|
||||||
switch (componentName)
|
switch (componentName)
|
||||||
{
|
{
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Audio.Sample;
|
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.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
@ -131,7 +132,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public Texture GetTexture(string componentName) => null;
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
|
||||||
|
|
||||||
public SampleChannel GetSample(ISampleInfo sampleInfo) => null;
|
public SampleChannel GetSample(ISampleInfo sampleInfo) => null;
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
|
|
||||||
private void testSingle(float circleSize, bool auto = false)
|
private void testSingle(float circleSize, bool auto = false)
|
||||||
{
|
{
|
||||||
var spinner = new Spinner { StartTime = Time.Current + 1000, EndTime = Time.Current + 4000 };
|
var spinner = new Spinner { StartTime = Time.Current + 2000, EndTime = Time.Current + 5000 };
|
||||||
|
|
||||||
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
|
spinner.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
|
||||||
|
|
||||||
|
@ -93,7 +93,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
Background = new SpinnerBackground
|
Background = new SpinnerBackground
|
||||||
{
|
{
|
||||||
Alpha = 0.6f,
|
Disc =
|
||||||
|
{
|
||||||
|
Alpha = 0f,
|
||||||
|
},
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
},
|
},
|
||||||
@ -125,10 +128,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
normalColour = baseColour;
|
normalColour = baseColour;
|
||||||
|
completeColour = colours.YellowLight;
|
||||||
|
|
||||||
Background.AccentColour = normalColour;
|
Background.AccentColour = normalColour;
|
||||||
|
Ticks.AccentColour = normalColour;
|
||||||
completeColour = colours.YellowLight.Opacity(0.75f);
|
|
||||||
|
|
||||||
Disc.AccentColour = fillColour;
|
Disc.AccentColour = fillColour;
|
||||||
circle.Colour = colours.BlueDark;
|
circle.Colour = colours.BlueDark;
|
||||||
@ -147,16 +150,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
if (Progress >= 1 && !Disc.Complete)
|
if (Progress >= 1 && !Disc.Complete)
|
||||||
{
|
{
|
||||||
Disc.Complete = true;
|
Disc.Complete = true;
|
||||||
|
transformFillColour(completeColour, 200);
|
||||||
const float duration = 200;
|
|
||||||
|
|
||||||
Disc.FadeAccent(completeColour, duration);
|
|
||||||
|
|
||||||
Background.FadeAccent(completeColour, duration);
|
|
||||||
Background.FadeOut(duration);
|
|
||||||
|
|
||||||
circle.FadeColour(completeColour, duration);
|
|
||||||
glow.FadeColour(completeColour, duration);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (userTriggered || Time.Current < Spinner.EndTime)
|
if (userTriggered || Time.Current < Spinner.EndTime)
|
||||||
@ -204,32 +198,59 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
circleContainer.ScaleTo(Spinner.Scale * 0.3f);
|
circleContainer.ScaleTo(0);
|
||||||
circleContainer.ScaleTo(Spinner.Scale, HitObject.TimePreempt / 1.4f, Easing.OutQuint);
|
mainContainer.ScaleTo(0);
|
||||||
|
|
||||||
|
using (BeginDelayedSequence(HitObject.TimePreempt / 2, true))
|
||||||
|
{
|
||||||
|
float phaseOneScale = Spinner.Scale * 0.7f;
|
||||||
|
|
||||||
|
circleContainer.ScaleTo(phaseOneScale, HitObject.TimePreempt / 4, Easing.OutQuint);
|
||||||
|
|
||||||
mainContainer
|
mainContainer
|
||||||
.ScaleTo(0)
|
.ScaleTo(phaseOneScale * circle.DrawHeight / DrawHeight * 1.6f, HitObject.TimePreempt / 4, Easing.OutQuint)
|
||||||
.ScaleTo(Spinner.Scale * circle.DrawHeight / DrawHeight * 1.4f, HitObject.TimePreempt - 150, Easing.OutQuint)
|
.RotateTo((float)(25 * Spinner.Duration / 2000), HitObject.TimePreempt + Spinner.Duration);
|
||||||
.Then()
|
|
||||||
.ScaleTo(1, 500, Easing.OutQuint);
|
using (BeginDelayedSequence(HitObject.TimePreempt / 2, true))
|
||||||
|
{
|
||||||
|
circleContainer.ScaleTo(Spinner.Scale, 400, Easing.OutQuint);
|
||||||
|
mainContainer.ScaleTo(1, 400, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateStateTransforms(ArmedState state)
|
protected override void UpdateStateTransforms(ArmedState state)
|
||||||
{
|
{
|
||||||
base.UpdateStateTransforms(state);
|
base.UpdateStateTransforms(state);
|
||||||
|
|
||||||
var sequence = this.Delay(Spinner.Duration).FadeOut(160);
|
using (BeginDelayedSequence(Spinner.Duration, true))
|
||||||
|
{
|
||||||
|
this.FadeOut(160);
|
||||||
|
|
||||||
switch (state)
|
switch (state)
|
||||||
{
|
{
|
||||||
case ArmedState.Hit:
|
case ArmedState.Hit:
|
||||||
sequence.ScaleTo(Scale * 1.2f, 320, Easing.Out);
|
transformFillColour(completeColour, 0);
|
||||||
|
this.ScaleTo(Scale * 1.2f, 320, Easing.Out);
|
||||||
|
mainContainer.RotateTo(mainContainer.Rotation + 180, 320);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ArmedState.Miss:
|
case ArmedState.Miss:
|
||||||
sequence.ScaleTo(Scale * 0.8f, 320, Easing.In);
|
this.ScaleTo(Scale * 0.8f, 320, Easing.In);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void transformFillColour(Colour4 colour, double duration)
|
||||||
|
{
|
||||||
|
Disc.FadeAccent(colour, duration);
|
||||||
|
|
||||||
|
Background.FadeAccent(colour.Darken(1), duration);
|
||||||
|
Ticks.FadeAccent(colour, duration);
|
||||||
|
|
||||||
|
circle.FadeColour(colour, duration);
|
||||||
|
glow.FadeColour(colour, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,18 +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 osuTK.Graphics;
|
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Effects;
|
using osu.Framework.Graphics.Effects;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class SpinnerBackground : CircularContainer, IHasAccentColour
|
public class SpinnerBackground : CircularContainer, IHasAccentColour
|
||||||
{
|
{
|
||||||
protected Box Disc;
|
public readonly Box Disc;
|
||||||
|
|
||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
{
|
{
|
||||||
|
@ -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;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
@ -9,10 +10,11 @@ using osu.Framework.Graphics.Effects;
|
|||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
||||||
{
|
{
|
||||||
public class SpinnerTicks : Container
|
public class SpinnerTicks : Container, IHasAccentColour
|
||||||
{
|
{
|
||||||
public SpinnerTicks()
|
public SpinnerTicks()
|
||||||
{
|
{
|
||||||
@ -20,28 +22,22 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
Anchor = Anchor.Centre;
|
Anchor = Anchor.Centre;
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
const float count = 18;
|
const float count = 8;
|
||||||
|
|
||||||
for (float i = 0; i < count; i++)
|
for (float i = 0; i < count; i++)
|
||||||
{
|
{
|
||||||
Add(new Container
|
Add(new Container
|
||||||
{
|
{
|
||||||
Colour = Color4.Black,
|
|
||||||
Alpha = 0.4f,
|
Alpha = 0.4f,
|
||||||
EdgeEffect = new EdgeEffectParameters
|
Blending = BlendingParameters.Additive,
|
||||||
{
|
|
||||||
Type = EdgeEffectType.Glow,
|
|
||||||
Radius = 10,
|
|
||||||
Colour = Color4.Gray.Opacity(0.2f),
|
|
||||||
},
|
|
||||||
RelativePositionAxes = Axes.Both,
|
RelativePositionAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
CornerRadius = 5,
|
CornerRadius = 5,
|
||||||
Size = new Vector2(60, 10),
|
Size = new Vector2(60, 10),
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Position = new Vector2(
|
Position = new Vector2(
|
||||||
0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.86f,
|
0.5f + MathF.Sin(i / count * 2 * MathF.PI) / 2 * 0.83f,
|
||||||
0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.86f
|
0.5f + MathF.Cos(i / count * 2 * MathF.PI) / 2 * 0.83f
|
||||||
),
|
),
|
||||||
Rotation = -i / count * 360 + 90,
|
Rotation = -i / count * 360 + 90,
|
||||||
Children = new[]
|
Children = new[]
|
||||||
@ -54,5 +50,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Color4 AccentColour
|
||||||
|
{
|
||||||
|
get => Colour;
|
||||||
|
set
|
||||||
|
{
|
||||||
|
Colour = value;
|
||||||
|
|
||||||
|
foreach (var c in Children.OfType<Container>())
|
||||||
|
{
|
||||||
|
c.EdgeEffect =
|
||||||
|
new EdgeEffectParameters
|
||||||
|
{
|
||||||
|
Type = EdgeEffectType.Glow,
|
||||||
|
Radius = 20,
|
||||||
|
Colour = value.Opacity(0.8f),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
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.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
@ -34,13 +35,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning
|
|||||||
Anchor = Anchor.CentreRight,
|
Anchor = Anchor.CentreRight,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Texture = skin.GetTexture("taiko-roll-end"),
|
Texture = skin.GetTexture("taiko-roll-end", WrapMode.ClampToEdge, WrapMode.ClampToEdge),
|
||||||
FillMode = FillMode.Fit,
|
FillMode = FillMode.Fit,
|
||||||
},
|
},
|
||||||
body = new Sprite
|
body = new Sprite
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Texture = skin.GetTexture("taiko-roll-middle"),
|
Texture = skin.GetTexture("taiko-roll-middle", WrapMode.ClampToEdge, WrapMode.ClampToEdge),
|
||||||
},
|
},
|
||||||
headCircle = new LegacyCirclePiece
|
headCircle = new LegacyCirclePiece
|
||||||
{
|
{
|
||||||
|
@ -8,6 +8,7 @@ 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;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -118,7 +119,7 @@ namespace osu.Game.Tests.Gameplay
|
|||||||
|
|
||||||
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
|
public Drawable GetDrawableComponent(ISkinComponent component) => throw new NotImplementedException();
|
||||||
|
|
||||||
public Texture GetTexture(string componentName) => throw new NotImplementedException();
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
|
||||||
|
|
||||||
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ 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;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -216,7 +217,7 @@ namespace osu.Game.Tests.Skins
|
|||||||
|
|
||||||
public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
|
public Drawable GetDrawableComponent(ISkinComponent component) => skin.GetDrawableComponent(component);
|
||||||
|
|
||||||
public Texture GetTexture(string componentName) => skin.GetTexture(componentName);
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => skin.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||||
|
|
||||||
public SampleChannel GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
|
public SampleChannel GetSample(ISampleInfo sampleInfo) => skin.GetSample(sampleInfo);
|
||||||
|
|
||||||
|
@ -175,13 +175,13 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
||||||
AddStep("Seek(49.999)", () => Clock.Seek(49.999));
|
AddStep("Seek(49.999)", () => Clock.Seek(49.999));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 50", () => Clock.CurrentTime == 50);
|
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
||||||
AddStep("Seek(99)", () => Clock.Seek(99));
|
AddStep("Seek(99)", () => Clock.Seek(99));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
||||||
AddStep("Seek(99.999)", () => Clock.Seek(99.999));
|
AddStep("Seek(99.999)", () => Clock.Seek(99.999));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 100", () => Clock.CurrentTime == 100);
|
AddAssert("Time = 100", () => Clock.CurrentTime == 150);
|
||||||
AddStep("Seek(174)", () => Clock.Seek(174));
|
AddStep("Seek(174)", () => Clock.Seek(174));
|
||||||
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
AddStep("SeekForward, Snap", () => Clock.SeekForward(true));
|
||||||
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
AddAssert("Time = 175", () => Clock.CurrentTime == 175);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||||
using osu.Game.Screens.Edit;
|
using osu.Game.Screens.Edit;
|
||||||
using osu.Game.Screens.Edit.Timing;
|
using osu.Game.Screens.Edit.Timing;
|
||||||
@ -13,6 +14,7 @@ namespace osu.Game.Tests.Visual.Editing
|
|||||||
public class TestSceneTimingScreen : EditorClockTestScene
|
public class TestSceneTimingScreen : EditorClockTestScene
|
||||||
{
|
{
|
||||||
[Cached(typeof(EditorBeatmap))]
|
[Cached(typeof(EditorBeatmap))]
|
||||||
|
[Cached(typeof(IBeatSnapProvider))]
|
||||||
private readonly EditorBeatmap editorBeatmap;
|
private readonly EditorBeatmap editorBeatmap;
|
||||||
|
|
||||||
public TestSceneTimingScreen()
|
public TestSceneTimingScreen()
|
||||||
|
@ -10,6 +10,7 @@ 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;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -295,7 +296,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
public Texture GetTexture(string componentName) => throw new NotImplementedException();
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
|
||||||
|
|
||||||
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
||||||
|
|
||||||
@ -306,7 +307,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
public Drawable GetDrawableComponent(ISkinComponent componentName) => new SecondarySourceBox();
|
public Drawable GetDrawableComponent(ISkinComponent componentName) => new SecondarySourceBox();
|
||||||
|
|
||||||
public Texture GetTexture(string componentName) => throw new NotImplementedException();
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
|
||||||
|
|
||||||
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
||||||
|
|
||||||
@ -318,7 +319,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
{
|
{
|
||||||
public Drawable GetDrawableComponent(ISkinComponent componentName) => new BaseSourceBox();
|
public Drawable GetDrawableComponent(ISkinComponent componentName) => new BaseSourceBox();
|
||||||
|
|
||||||
public Texture GetTexture(string componentName) => throw new NotImplementedException();
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => throw new NotImplementedException();
|
||||||
|
|
||||||
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
||||||
|
|
||||||
|
@ -1,84 +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.
|
|
||||||
|
|
||||||
using NUnit.Framework;
|
|
||||||
using osu.Game.Overlays;
|
|
||||||
using osu.Game.Users;
|
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class TestSceneSocialOverlay : OsuTestScene
|
|
||||||
{
|
|
||||||
protected override bool UseOnlineAPI => true;
|
|
||||||
|
|
||||||
public TestSceneSocialOverlay()
|
|
||||||
{
|
|
||||||
SocialOverlay s = new SocialOverlay
|
|
||||||
{
|
|
||||||
Users = new[]
|
|
||||||
{
|
|
||||||
new User
|
|
||||||
{
|
|
||||||
Username = @"flyte",
|
|
||||||
Id = 3103765,
|
|
||||||
Country = new Country { FlagName = @"JP" },
|
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg",
|
|
||||||
},
|
|
||||||
new User
|
|
||||||
{
|
|
||||||
Username = @"Cookiezi",
|
|
||||||
Id = 124493,
|
|
||||||
Country = new Country { FlagName = @"KR" },
|
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c2.jpg",
|
|
||||||
},
|
|
||||||
new User
|
|
||||||
{
|
|
||||||
Username = @"Angelsim",
|
|
||||||
Id = 1777162,
|
|
||||||
Country = new Country { FlagName = @"KR" },
|
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c3.jpg",
|
|
||||||
},
|
|
||||||
new User
|
|
||||||
{
|
|
||||||
Username = @"Rafis",
|
|
||||||
Id = 2558286,
|
|
||||||
Country = new Country { FlagName = @"PL" },
|
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c4.jpg",
|
|
||||||
},
|
|
||||||
new User
|
|
||||||
{
|
|
||||||
Username = @"hvick225",
|
|
||||||
Id = 50265,
|
|
||||||
Country = new Country { FlagName = @"TW" },
|
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c5.jpg",
|
|
||||||
},
|
|
||||||
new User
|
|
||||||
{
|
|
||||||
Username = @"peppy",
|
|
||||||
Id = 2,
|
|
||||||
Country = new Country { FlagName = @"AU" },
|
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c6.jpg"
|
|
||||||
},
|
|
||||||
new User
|
|
||||||
{
|
|
||||||
Username = @"filsdelama",
|
|
||||||
Id = 2831793,
|
|
||||||
Country = new Country { FlagName = @"FR" },
|
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c7.jpg"
|
|
||||||
},
|
|
||||||
new User
|
|
||||||
{
|
|
||||||
Username = @"_index",
|
|
||||||
Id = 652457,
|
|
||||||
Country = new Country { FlagName = @"RU" },
|
|
||||||
CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c8.jpg"
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Add(s);
|
|
||||||
|
|
||||||
AddStep(@"toggle", s.ToggleVisibility);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -64,49 +64,49 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to find the difficulty control point at.</param>
|
/// <param name="time">The time to find the difficulty control point at.</param>
|
||||||
/// <returns>The difficulty control point.</returns>
|
/// <returns>The difficulty control point.</returns>
|
||||||
public DifficultyControlPoint DifficultyPointAt(double time) => binarySearchWithFallback(DifficultyPoints, time);
|
public DifficultyControlPoint DifficultyPointAt(double time) => binarySearchWithFallback(DifficultyPoints, time, DifficultyControlPoint.DEFAULT);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the effect control point that is active at <paramref name="time"/>.
|
/// Finds the effect control point that is active at <paramref name="time"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to find the effect control point at.</param>
|
/// <param name="time">The time to find the effect control point at.</param>
|
||||||
/// <returns>The effect control point.</returns>
|
/// <returns>The effect control point.</returns>
|
||||||
public EffectControlPoint EffectPointAt(double time) => binarySearchWithFallback(EffectPoints, time);
|
public EffectControlPoint EffectPointAt(double time) => binarySearchWithFallback(EffectPoints, time, EffectControlPoint.DEFAULT);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the sound control point that is active at <paramref name="time"/>.
|
/// Finds the sound control point that is active at <paramref name="time"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to find the sound control point at.</param>
|
/// <param name="time">The time to find the sound control point at.</param>
|
||||||
/// <returns>The sound control point.</returns>
|
/// <returns>The sound control point.</returns>
|
||||||
public SampleControlPoint SamplePointAt(double time) => binarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : null);
|
public SampleControlPoint SamplePointAt(double time) => binarySearchWithFallback(SamplePoints, time, SamplePoints.Count > 0 ? SamplePoints[0] : SampleControlPoint.DEFAULT);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the timing control point that is active at <paramref name="time"/>.
|
/// Finds the timing control point that is active at <paramref name="time"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="time">The time to find the timing control point at.</param>
|
/// <param name="time">The time to find the timing control point at.</param>
|
||||||
/// <returns>The timing control point.</returns>
|
/// <returns>The timing control point.</returns>
|
||||||
public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null);
|
public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : TimingControlPoint.DEFAULT);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the maximum BPM represented by any timing control point.
|
/// Finds the maximum BPM represented by any timing control point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public double BPMMaximum =>
|
public double BPMMaximum =>
|
||||||
60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
|
60000 / (TimingPoints.OrderBy(c => c.BeatLength).FirstOrDefault() ?? TimingControlPoint.DEFAULT).BeatLength;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the minimum BPM represented by any timing control point.
|
/// Finds the minimum BPM represented by any timing control point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public double BPMMinimum =>
|
public double BPMMinimum =>
|
||||||
60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
|
60000 / (TimingPoints.OrderByDescending(c => c.BeatLength).FirstOrDefault() ?? TimingControlPoint.DEFAULT).BeatLength;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Finds the mode BPM (most common BPM) represented by the control points.
|
/// Finds the mode BPM (most common BPM) represented by the control points.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public double BPMMode =>
|
public double BPMMode =>
|
||||||
60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength;
|
60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? TimingControlPoint.DEFAULT).BeatLength;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Remove all <see cref="ControlPointGroup"/>s and return to a pristine state.
|
/// Remove all <see cref="ControlPointGroup"/>s and return to a pristine state.
|
||||||
@ -170,12 +170,12 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="list">The list to search.</param>
|
/// <param name="list">The list to search.</param>
|
||||||
/// <param name="time">The time to find the control point at.</param>
|
/// <param name="time">The time to find the control point at.</param>
|
||||||
/// <param name="prePoint">The control point to use when <paramref name="time"/> is before any control points. If null, a new control point will be constructed.</param>
|
/// <param name="fallback">The control point to use when <paramref name="time"/> is before any control points.</param>
|
||||||
/// <returns>The active control point at <paramref name="time"/>, or a fallback <see cref="ControlPoint"/> if none found.</returns>
|
/// <returns>The active control point at <paramref name="time"/>, or a fallback <see cref="ControlPoint"/> if none found.</returns>
|
||||||
private T binarySearchWithFallback<T>(IReadOnlyList<T> list, double time, T prePoint = null)
|
private T binarySearchWithFallback<T>(IReadOnlyList<T> list, double time, T fallback)
|
||||||
where T : ControlPoint, new()
|
where T : ControlPoint
|
||||||
{
|
{
|
||||||
return binarySearch(list, time) ?? prePoint ?? new T();
|
return binarySearch(list, time) ?? fallback;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -7,6 +7,11 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
{
|
{
|
||||||
public class DifficultyControlPoint : ControlPoint
|
public class DifficultyControlPoint : ControlPoint
|
||||||
{
|
{
|
||||||
|
public static readonly DifficultyControlPoint DEFAULT = new DifficultyControlPoint
|
||||||
|
{
|
||||||
|
SpeedMultiplierBindable = { Disabled = true },
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The speed multiplier at this control point.
|
/// The speed multiplier at this control point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -7,6 +7,12 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
{
|
{
|
||||||
public class EffectControlPoint : ControlPoint
|
public class EffectControlPoint : ControlPoint
|
||||||
{
|
{
|
||||||
|
public static readonly EffectControlPoint DEFAULT = new EffectControlPoint
|
||||||
|
{
|
||||||
|
KiaiModeBindable = { Disabled = true },
|
||||||
|
OmitFirstBarLineBindable = { Disabled = true }
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether the first bar line of this control point is ignored.
|
/// Whether the first bar line of this control point is ignored.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -10,6 +10,12 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
{
|
{
|
||||||
public const string DEFAULT_BANK = "normal";
|
public const string DEFAULT_BANK = "normal";
|
||||||
|
|
||||||
|
public static readonly SampleControlPoint DEFAULT = new SampleControlPoint
|
||||||
|
{
|
||||||
|
SampleBankBindable = { Disabled = true },
|
||||||
|
SampleVolumeBindable = { Disabled = true }
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The default sample bank at this control point.
|
/// The default sample bank at this control point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -13,6 +13,21 @@ namespace osu.Game.Beatmaps.ControlPoints
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly Bindable<TimeSignatures> TimeSignatureBindable = new Bindable<TimeSignatures>(TimeSignatures.SimpleQuadruple) { Default = TimeSignatures.SimpleQuadruple };
|
public readonly Bindable<TimeSignatures> TimeSignatureBindable = new Bindable<TimeSignatures>(TimeSignatures.SimpleQuadruple) { Default = TimeSignatures.SimpleQuadruple };
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing.
|
||||||
|
/// </summary>
|
||||||
|
private const double default_beat_length = 60000.0 / 60.0;
|
||||||
|
|
||||||
|
public static readonly TimingControlPoint DEFAULT = new TimingControlPoint
|
||||||
|
{
|
||||||
|
BeatLengthBindable =
|
||||||
|
{
|
||||||
|
Value = default_beat_length,
|
||||||
|
Disabled = true
|
||||||
|
},
|
||||||
|
TimeSignatureBindable = { Disabled = true }
|
||||||
|
};
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The time signature at this control point.
|
/// The time signature at this control point.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -43,14 +43,6 @@ namespace osu.Game.Graphics.Containers
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public double MinimumBeatLength { get; set; }
|
public double MinimumBeatLength { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing.
|
|
||||||
/// </summary>
|
|
||||||
private const double default_beat_length = 60000.0 / 60.0;
|
|
||||||
|
|
||||||
private TimingControlPoint defaultTiming;
|
|
||||||
private EffectControlPoint defaultEffect;
|
|
||||||
|
|
||||||
protected bool IsBeatSyncedWithTrack { get; private set; }
|
protected bool IsBeatSyncedWithTrack { get; private set; }
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
@ -81,8 +73,8 @@ namespace osu.Game.Graphics.Containers
|
|||||||
if (timingPoint == null || !IsBeatSyncedWithTrack)
|
if (timingPoint == null || !IsBeatSyncedWithTrack)
|
||||||
{
|
{
|
||||||
currentTrackTime = Clock.CurrentTime;
|
currentTrackTime = Clock.CurrentTime;
|
||||||
timingPoint = defaultTiming;
|
timingPoint = TimingControlPoint.DEFAULT;
|
||||||
effectPoint = defaultEffect;
|
effectPoint = EffectControlPoint.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
double beatLength = timingPoint.BeatLength / Divisor;
|
double beatLength = timingPoint.BeatLength / Divisor;
|
||||||
@ -116,17 +108,6 @@ namespace osu.Game.Graphics.Containers
|
|||||||
private void load(IBindable<WorkingBeatmap> beatmap)
|
private void load(IBindable<WorkingBeatmap> beatmap)
|
||||||
{
|
{
|
||||||
Beatmap.BindTo(beatmap);
|
Beatmap.BindTo(beatmap);
|
||||||
|
|
||||||
defaultTiming = new TimingControlPoint
|
|
||||||
{
|
|
||||||
BeatLength = default_beat_length,
|
|
||||||
};
|
|
||||||
|
|
||||||
defaultEffect = new EffectControlPoint
|
|
||||||
{
|
|
||||||
KiaiMode = false,
|
|
||||||
OmitFirstBarLine = false
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
protected virtual void OnNewBeat(int beatIndex, TimingControlPoint timingPoint, EffectControlPoint effectPoint, ChannelAmplitudes amplitudes)
|
||||||
|
@ -67,6 +67,8 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
public bool OnPressed(GlobalAction action)
|
public bool OnPressed(GlobalAction action)
|
||||||
{
|
{
|
||||||
|
if (!HasFocus) return false;
|
||||||
|
|
||||||
if (action == GlobalAction.Back)
|
if (action == GlobalAction.Back)
|
||||||
{
|
{
|
||||||
if (Text.Length > 0)
|
if (Text.Length > 0)
|
||||||
|
@ -63,7 +63,8 @@ namespace osu.Game
|
|||||||
|
|
||||||
private ChannelManager channelManager;
|
private ChannelManager channelManager;
|
||||||
|
|
||||||
private NotificationOverlay notifications;
|
[NotNull]
|
||||||
|
private readonly NotificationOverlay notifications = new NotificationOverlay();
|
||||||
|
|
||||||
private NowPlayingOverlay nowPlaying;
|
private NowPlayingOverlay nowPlaying;
|
||||||
|
|
||||||
@ -82,7 +83,7 @@ namespace osu.Game
|
|||||||
|
|
||||||
public virtual Storage GetStorageForStableInstall() => null;
|
public virtual Storage GetStorageForStableInstall() => null;
|
||||||
|
|
||||||
public float ToolbarOffset => Toolbar.Position.Y + Toolbar.DrawHeight;
|
public float ToolbarOffset => (Toolbar?.Position.Y ?? 0) + (Toolbar?.DrawHeight ?? 0);
|
||||||
|
|
||||||
private IdleTracker idleTracker;
|
private IdleTracker idleTracker;
|
||||||
|
|
||||||
@ -250,7 +251,7 @@ namespace osu.Game
|
|||||||
case LinkAction.OpenEditorTimestamp:
|
case LinkAction.OpenEditorTimestamp:
|
||||||
case LinkAction.JoinMultiplayerMatch:
|
case LinkAction.JoinMultiplayerMatch:
|
||||||
case LinkAction.Spectate:
|
case LinkAction.Spectate:
|
||||||
waitForReady(() => notifications, _ => notifications?.Post(new SimpleNotification
|
waitForReady(() => notifications, _ => notifications.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
Text = @"This link type is not yet supported!",
|
Text = @"This link type is not yet supported!",
|
||||||
Icon = FontAwesome.Solid.LifeRing,
|
Icon = FontAwesome.Solid.LifeRing,
|
||||||
@ -536,14 +537,14 @@ namespace osu.Game
|
|||||||
MenuCursorContainer.CanShowCursor = menuScreen?.CursorVisible ?? false;
|
MenuCursorContainer.CanShowCursor = menuScreen?.CursorVisible ?? false;
|
||||||
|
|
||||||
// todo: all archive managers should be able to be looped here.
|
// todo: all archive managers should be able to be looped here.
|
||||||
SkinManager.PostNotification = n => notifications?.Post(n);
|
SkinManager.PostNotification = n => notifications.Post(n);
|
||||||
SkinManager.GetStableStorage = GetStorageForStableInstall;
|
SkinManager.GetStableStorage = GetStorageForStableInstall;
|
||||||
|
|
||||||
BeatmapManager.PostNotification = n => notifications?.Post(n);
|
BeatmapManager.PostNotification = n => notifications.Post(n);
|
||||||
BeatmapManager.GetStableStorage = GetStorageForStableInstall;
|
BeatmapManager.GetStableStorage = GetStorageForStableInstall;
|
||||||
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
|
BeatmapManager.PresentImport = items => PresentBeatmap(items.First());
|
||||||
|
|
||||||
ScoreManager.PostNotification = n => notifications?.Post(n);
|
ScoreManager.PostNotification = n => notifications.Post(n);
|
||||||
ScoreManager.GetStableStorage = GetStorageForStableInstall;
|
ScoreManager.GetStableStorage = GetStorageForStableInstall;
|
||||||
ScoreManager.PresentImport = items => PresentScore(items.First());
|
ScoreManager.PresentImport = items => PresentScore(items.First());
|
||||||
|
|
||||||
@ -615,12 +616,12 @@ namespace osu.Game
|
|||||||
|
|
||||||
loadComponentSingleFile(MusicController = new MusicController(), Add, true);
|
loadComponentSingleFile(MusicController = new MusicController(), Add, true);
|
||||||
|
|
||||||
loadComponentSingleFile(notifications = new NotificationOverlay
|
loadComponentSingleFile(notifications.With(d =>
|
||||||
{
|
{
|
||||||
GetToolbarHeight = () => ToolbarOffset,
|
d.GetToolbarHeight = () => ToolbarOffset;
|
||||||
Anchor = Anchor.TopRight,
|
d.Anchor = Anchor.TopRight;
|
||||||
Origin = Anchor.TopRight,
|
d.Origin = Anchor.TopRight;
|
||||||
}, rightFloatingOverlayContent.Add, true);
|
}), rightFloatingOverlayContent.Add, true);
|
||||||
|
|
||||||
loadComponentSingleFile(screenshotManager, Add);
|
loadComponentSingleFile(screenshotManager, Add);
|
||||||
|
|
||||||
|
@ -1,33 +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.
|
|
||||||
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Overlays.SearchableList;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Social
|
|
||||||
{
|
|
||||||
public class FilterControl : SearchableListFilterControl<SocialSortCriteria, SortDirection>
|
|
||||||
{
|
|
||||||
protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"47253a");
|
|
||||||
protected override SocialSortCriteria DefaultTab => SocialSortCriteria.Rank;
|
|
||||||
protected override SortDirection DefaultCategory => SortDirection.Ascending;
|
|
||||||
|
|
||||||
public FilterControl()
|
|
||||||
{
|
|
||||||
Tabs.Margin = new MarginPadding { Top = 10 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum SocialSortCriteria
|
|
||||||
{
|
|
||||||
Rank,
|
|
||||||
Name,
|
|
||||||
Location,
|
|
||||||
//[Description("Time Zone")]
|
|
||||||
//TimeZone,
|
|
||||||
//[Description("World Map")]
|
|
||||||
//WorldMap,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,67 +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.
|
|
||||||
|
|
||||||
using osu.Game.Overlays.SearchableList;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Social
|
|
||||||
{
|
|
||||||
public class Header : SearchableListHeader<SocialTab>
|
|
||||||
{
|
|
||||||
private OsuSpriteText browser;
|
|
||||||
|
|
||||||
protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"38202e");
|
|
||||||
|
|
||||||
protected override SocialTab DefaultTab => SocialTab.AllPlayers;
|
|
||||||
protected override IconUsage Icon => FontAwesome.Solid.Users;
|
|
||||||
|
|
||||||
protected override Drawable CreateHeaderText()
|
|
||||||
{
|
|
||||||
return new FillFlowContainer
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Both,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Children = new[]
|
|
||||||
{
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Text = "social ",
|
|
||||||
Font = OsuFont.GetFont(size: 25),
|
|
||||||
},
|
|
||||||
browser = new OsuSpriteText
|
|
||||||
{
|
|
||||||
Text = "browser",
|
|
||||||
Font = OsuFont.GetFont(size: 25, weight: FontWeight.Light),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colours)
|
|
||||||
{
|
|
||||||
browser.Colour = colours.Pink;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum SocialTab
|
|
||||||
{
|
|
||||||
[Description("All Players")]
|
|
||||||
AllPlayers,
|
|
||||||
|
|
||||||
[Description("Friends")]
|
|
||||||
Friends,
|
|
||||||
//[Description("Team Members")]
|
|
||||||
//TeamMembers,
|
|
||||||
//[Description("Chat Channels")]
|
|
||||||
//ChatChannels,
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,242 +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.
|
|
||||||
|
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Bindables;
|
|
||||||
using osuTK;
|
|
||||||
using osuTK.Graphics;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Online.API;
|
|
||||||
using osu.Game.Online.API.Requests;
|
|
||||||
using osu.Game.Overlays.SearchableList;
|
|
||||||
using osu.Game.Overlays.Social;
|
|
||||||
using osu.Game.Users;
|
|
||||||
using System.Threading;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
|
||||||
using osu.Framework.Threading;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays
|
|
||||||
{
|
|
||||||
public class SocialOverlay : SearchableListOverlay<SocialTab, SocialSortCriteria, SortDirection>
|
|
||||||
{
|
|
||||||
private readonly LoadingSpinner loading;
|
|
||||||
private FillFlowContainer<UserPanel> panels;
|
|
||||||
|
|
||||||
protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"60284b");
|
|
||||||
protected override Color4 TrianglesColourLight => Color4Extensions.FromHex(@"672b51");
|
|
||||||
protected override Color4 TrianglesColourDark => Color4Extensions.FromHex(@"5c2648");
|
|
||||||
|
|
||||||
protected override SearchableListHeader<SocialTab> CreateHeader() => new Header();
|
|
||||||
protected override SearchableListFilterControl<SocialSortCriteria, SortDirection> CreateFilterControl() => new FilterControl();
|
|
||||||
|
|
||||||
private User[] users = Array.Empty<User>();
|
|
||||||
|
|
||||||
public User[] Users
|
|
||||||
{
|
|
||||||
get => users;
|
|
||||||
set
|
|
||||||
{
|
|
||||||
if (users == value)
|
|
||||||
return;
|
|
||||||
|
|
||||||
users = value ?? Array.Empty<User>();
|
|
||||||
|
|
||||||
if (LoadState >= LoadState.Ready)
|
|
||||||
recreatePanels();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public SocialOverlay()
|
|
||||||
: base(OverlayColourScheme.Pink)
|
|
||||||
{
|
|
||||||
Add(loading = new LoadingSpinner());
|
|
||||||
|
|
||||||
Filter.Search.Current.ValueChanged += text =>
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrEmpty(text.NewValue))
|
|
||||||
{
|
|
||||||
// force searching in players until searching for friends is supported
|
|
||||||
Header.Tabs.Current.Value = SocialTab.AllPlayers;
|
|
||||||
|
|
||||||
if (Filter.Tabs.Current.Value != SocialSortCriteria.Rank)
|
|
||||||
Filter.Tabs.Current.Value = SocialSortCriteria.Rank;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Header.Tabs.Current.ValueChanged += _ => queueUpdate();
|
|
||||||
Filter.Tabs.Current.ValueChanged += _ => onFilterUpdate();
|
|
||||||
|
|
||||||
Filter.DisplayStyleControl.DisplayStyle.ValueChanged += _ => recreatePanels();
|
|
||||||
Filter.Dropdown.Current.ValueChanged += _ => recreatePanels();
|
|
||||||
|
|
||||||
currentQuery.BindTo(Filter.Search.Current);
|
|
||||||
currentQuery.ValueChanged += query =>
|
|
||||||
{
|
|
||||||
queryChangedDebounce?.Cancel();
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(query.NewValue))
|
|
||||||
queueUpdate();
|
|
||||||
else
|
|
||||||
queryChangedDebounce = Scheduler.AddDelayed(updateSearch, 500);
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
recreatePanels();
|
|
||||||
}
|
|
||||||
|
|
||||||
private APIRequest getUsersRequest;
|
|
||||||
|
|
||||||
private readonly Bindable<string> currentQuery = new Bindable<string>();
|
|
||||||
|
|
||||||
private ScheduledDelegate queryChangedDebounce;
|
|
||||||
|
|
||||||
private void queueUpdate() => Scheduler.AddOnce(updateSearch);
|
|
||||||
|
|
||||||
private CancellationTokenSource loadCancellation;
|
|
||||||
|
|
||||||
private void updateSearch()
|
|
||||||
{
|
|
||||||
queryChangedDebounce?.Cancel();
|
|
||||||
|
|
||||||
if (!IsLoaded)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Users = null;
|
|
||||||
clearPanels();
|
|
||||||
getUsersRequest?.Cancel();
|
|
||||||
|
|
||||||
if (API?.IsLoggedIn != true)
|
|
||||||
return;
|
|
||||||
|
|
||||||
switch (Header.Tabs.Current.Value)
|
|
||||||
{
|
|
||||||
case SocialTab.Friends:
|
|
||||||
var friendRequest = new GetFriendsRequest(); // TODO filter arguments?
|
|
||||||
friendRequest.Success += users => Users = users.ToArray();
|
|
||||||
API.Queue(getUsersRequest = friendRequest);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
var userRequest = new GetUsersRequest(); // TODO filter arguments!
|
|
||||||
userRequest.Success += res => Users = res.Users.Select(r => r.User).ToArray();
|
|
||||||
API.Queue(getUsersRequest = userRequest);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void recreatePanels()
|
|
||||||
{
|
|
||||||
clearPanels();
|
|
||||||
|
|
||||||
if (Users == null)
|
|
||||||
{
|
|
||||||
loading.Hide();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IEnumerable<User> sortedUsers = Users;
|
|
||||||
|
|
||||||
switch (Filter.Tabs.Current.Value)
|
|
||||||
{
|
|
||||||
case SocialSortCriteria.Location:
|
|
||||||
sortedUsers = sortedUsers.OrderBy(u => u.Country.FullName);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SocialSortCriteria.Name:
|
|
||||||
sortedUsers = sortedUsers.OrderBy(u => u.Username);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Filter.Dropdown.Current.Value == SortDirection.Descending)
|
|
||||||
sortedUsers = sortedUsers.Reverse();
|
|
||||||
|
|
||||||
var newPanels = new FillFlowContainer<UserPanel>
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Spacing = new Vector2(10f),
|
|
||||||
Margin = new MarginPadding { Top = 10 },
|
|
||||||
ChildrenEnumerable = sortedUsers.Select(u =>
|
|
||||||
{
|
|
||||||
UserPanel panel;
|
|
||||||
|
|
||||||
switch (Filter.DisplayStyleControl.DisplayStyle.Value)
|
|
||||||
{
|
|
||||||
case PanelDisplayStyle.Grid:
|
|
||||||
panel = new UserGridPanel(u)
|
|
||||||
{
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Width = 290,
|
|
||||||
};
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
panel = new UserListPanel(u);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
panel.Status.BindTo(u.Status);
|
|
||||||
panel.Activity.BindTo(u.Activity);
|
|
||||||
return panel;
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
LoadComponentAsync(newPanels, f =>
|
|
||||||
{
|
|
||||||
if (panels != null)
|
|
||||||
ScrollFlow.Remove(panels);
|
|
||||||
|
|
||||||
loading.Hide();
|
|
||||||
ScrollFlow.Add(panels = newPanels);
|
|
||||||
}, (loadCancellation = new CancellationTokenSource()).Token);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onFilterUpdate()
|
|
||||||
{
|
|
||||||
if (Filter.Tabs.Current.Value == SocialSortCriteria.Rank)
|
|
||||||
{
|
|
||||||
queueUpdate();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
recreatePanels();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void clearPanels()
|
|
||||||
{
|
|
||||||
loading.Show();
|
|
||||||
|
|
||||||
loadCancellation?.Cancel();
|
|
||||||
|
|
||||||
if (panels != null)
|
|
||||||
{
|
|
||||||
panels.Expire();
|
|
||||||
panels = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void APIStateChanged(IAPIProvider api, APIState state)
|
|
||||||
{
|
|
||||||
switch (state)
|
|
||||||
{
|
|
||||||
case APIState.Online:
|
|
||||||
queueUpdate();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
Users = null;
|
|
||||||
clearPanels();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -64,6 +64,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
DragBox = CreateDragBox(select),
|
DragBox = CreateDragBox(select),
|
||||||
selectionHandler,
|
selectionHandler,
|
||||||
SelectionBlueprints = CreateSelectionBlueprintContainer(),
|
SelectionBlueprints = CreateSelectionBlueprintContainer(),
|
||||||
|
selectionHandler.CreateProxy(),
|
||||||
DragBox.CreateProxy().With(p => p.Depth = float.MinValue)
|
DragBox.CreateProxy().With(p => p.Depth = float.MinValue)
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -121,14 +122,19 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
return e.Button == MouseButton.Left;
|
return e.Button == MouseButton.Left;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private SelectionBlueprint clickedBlueprint;
|
||||||
|
|
||||||
protected override bool OnClick(ClickEvent e)
|
protected override bool OnClick(ClickEvent e)
|
||||||
{
|
{
|
||||||
if (e.Button == MouseButton.Right)
|
if (e.Button == MouseButton.Right)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
|
// store for double-click handling
|
||||||
|
clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered);
|
||||||
|
|
||||||
// Deselection should only occur if no selected blueprints are hovered
|
// Deselection should only occur if no selected blueprints are hovered
|
||||||
// A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection
|
// A special case for when a blueprint was selected via this click is added since OnClick() may occur outside the hitobject and should not trigger deselection
|
||||||
if (endClickSelection() || selectionHandler.SelectedBlueprints.Any(b => b.IsHovered))
|
if (endClickSelection() || clickedBlueprint != null)
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
deselectAll();
|
deselectAll();
|
||||||
@ -140,9 +146,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
if (e.Button == MouseButton.Right)
|
if (e.Button == MouseButton.Right)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
SelectionBlueprint clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered);
|
// ensure the blueprint which was hovered for the first click is still the hovered blueprint.
|
||||||
|
if (clickedBlueprint == null || selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != clickedBlueprint)
|
||||||
if (clickedBlueprint == null)
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
editorClock?.SeekTo(clickedBlueprint.HitObject.StartTime);
|
editorClock?.SeekTo(clickedBlueprint.HitObject.StartTime);
|
||||||
|
@ -15,6 +15,7 @@ using osu.Framework.Input.Bindings;
|
|||||||
using osu.Framework.Input.States;
|
using osu.Framework.Input.States;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -35,7 +36,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
|
|
||||||
public IEnumerable<HitObject> SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject);
|
public IEnumerable<HitObject> SelectedHitObjects => selectedBlueprints.Select(b => b.HitObject);
|
||||||
|
|
||||||
private Drawable outline;
|
private Drawable content;
|
||||||
|
|
||||||
|
private OsuSpriteText selectionDetailsText;
|
||||||
|
|
||||||
[Resolved(CanBeNull = true)]
|
[Resolved(CanBeNull = true)]
|
||||||
protected EditorBeatmap EditorBeatmap { get; private set; }
|
protected EditorBeatmap EditorBeatmap { get; private set; }
|
||||||
@ -55,17 +58,43 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OsuColour colours)
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
InternalChild = outline = new Container
|
InternalChild = content = new Container
|
||||||
{
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
BorderThickness = BORDER_RADIUS,
|
BorderThickness = BORDER_RADIUS,
|
||||||
BorderColour = colours.Yellow,
|
BorderColour = colours.YellowDark,
|
||||||
Child = new Box
|
Child = new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
AlwaysPresent = true,
|
AlwaysPresent = true,
|
||||||
Alpha = 0
|
Alpha = 0
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
Name = "info text",
|
||||||
|
AutoSizeAxes = Axes.Both,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new Box
|
||||||
|
{
|
||||||
|
Colour = colours.YellowDark,
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
},
|
||||||
|
selectionDetailsText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Padding = new MarginPadding(2),
|
||||||
|
Colour = colours.Gray0,
|
||||||
|
Font = OsuFont.Default.With(size: 11)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,8 +160,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
selectedBlueprints.Remove(blueprint);
|
selectedBlueprints.Remove(blueprint);
|
||||||
EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject);
|
EditorBeatmap.SelectedHitObjects.Remove(blueprint.HitObject);
|
||||||
|
|
||||||
// We don't want to update visibility if > 0, since we may be deselecting blueprints during drag-selection
|
|
||||||
if (selectedBlueprints.Count == 0)
|
|
||||||
UpdateVisibility();
|
UpdateVisibility();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -179,7 +206,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal void UpdateVisibility()
|
internal void UpdateVisibility()
|
||||||
{
|
{
|
||||||
if (selectedBlueprints.Count > 0)
|
int count = selectedBlueprints.Count;
|
||||||
|
|
||||||
|
selectionDetailsText.Text = count > 0 ? count.ToString() : string.Empty;
|
||||||
|
|
||||||
|
if (count > 0)
|
||||||
Show();
|
Show();
|
||||||
else
|
else
|
||||||
Hide();
|
Hide();
|
||||||
@ -205,8 +236,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
topLeft -= new Vector2(5);
|
topLeft -= new Vector2(5);
|
||||||
bottomRight += new Vector2(5);
|
bottomRight += new Vector2(5);
|
||||||
|
|
||||||
outline.Size = bottomRight - topLeft;
|
content.Size = bottomRight - topLeft;
|
||||||
outline.Position = topLeft;
|
content.Position = topLeft;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
@ -275,11 +275,22 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
protected override bool OnScroll(ScrollEvent e)
|
protected override bool OnScroll(ScrollEvent e)
|
||||||
{
|
{
|
||||||
scrollAccumulation += (e.ScrollDelta.X + e.ScrollDelta.Y) * (e.IsPrecise ? 0.1 : 1);
|
const double precision = 1;
|
||||||
|
|
||||||
const int precision = 1;
|
double scrollComponent = e.ScrollDelta.X + e.ScrollDelta.Y;
|
||||||
|
|
||||||
while (Math.Abs(scrollAccumulation) > precision)
|
double scrollDirection = Math.Sign(scrollComponent);
|
||||||
|
|
||||||
|
// this is a special case to handle the "pivot" scenario.
|
||||||
|
// if we are precise scrolling in one direction then change our mind and scroll backwards,
|
||||||
|
// the existing accumulation should be applied in the inverse direction to maintain responsiveness.
|
||||||
|
if (Math.Sign(scrollAccumulation) != scrollDirection)
|
||||||
|
scrollAccumulation = scrollDirection * (precision - Math.Abs(scrollAccumulation));
|
||||||
|
|
||||||
|
scrollAccumulation += scrollComponent * (e.IsPrecise ? 0.1 : 1);
|
||||||
|
|
||||||
|
// because we are doing snapped seeking, we need to add up precise scrolls until they accumulate to an arbitrary cut-off.
|
||||||
|
while (Math.Abs(scrollAccumulation) >= precision)
|
||||||
{
|
{
|
||||||
if (scrollAccumulation > 0)
|
if (scrollAccumulation > 0)
|
||||||
seek(e, -1);
|
seek(e, -1);
|
||||||
|
@ -118,9 +118,14 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
seekTime = timingPoint.Time + closestBeat * seekAmount;
|
seekTime = timingPoint.Time + closestBeat * seekAmount;
|
||||||
|
|
||||||
|
// limit forward seeking to only up to the next timing point's start time.
|
||||||
|
var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time);
|
||||||
|
if (seekTime > nextTimingPoint?.Time)
|
||||||
|
seekTime = nextTimingPoint.Time;
|
||||||
|
|
||||||
// Due to the rounding above, we may end up on the current beat. This will effectively cause 0 seeking to happen, but we don't want this.
|
// Due to the rounding above, we may end up on the current beat. This will effectively cause 0 seeking to happen, but we don't want this.
|
||||||
// Instead, we'll go to the next beat in the direction when this is the case
|
// Instead, we'll go to the next beat in the direction when this is the case
|
||||||
if (Precision.AlmostEquals(current, seekTime))
|
if (Precision.AlmostEquals(current, seekTime, 0.5f))
|
||||||
{
|
{
|
||||||
closestBeat += direction > 0 ? 1 : -1;
|
closestBeat += direction > 0 ? 1 : -1;
|
||||||
seekTime = timingPoint.Time + closestBeat * seekAmount;
|
seekTime = timingPoint.Time + closestBeat * seekAmount;
|
||||||
@ -129,10 +134,6 @@ namespace osu.Game.Screens.Edit
|
|||||||
if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First())
|
if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First())
|
||||||
seekTime = timingPoint.Time;
|
seekTime = timingPoint.Time;
|
||||||
|
|
||||||
var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time);
|
|
||||||
if (seekTime > nextTimingPoint?.Time)
|
|
||||||
seekTime = nextTimingPoint.Time;
|
|
||||||
|
|
||||||
// Ensure the sought point is within the boundaries
|
// Ensure the sought point is within the boundaries
|
||||||
seekTime = Math.Clamp(seekTime, 0, TrackLength);
|
seekTime = Math.Clamp(seekTime, 0, TrackLength);
|
||||||
SeekTo(seekTime);
|
SeekTo(seekTime);
|
||||||
|
@ -5,6 +5,7 @@ using System.Collections.Generic;
|
|||||||
using osu.Framework.Audio.Sample;
|
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.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -21,7 +22,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
public override Drawable GetDrawableComponent(ISkinComponent component) => null;
|
public override Drawable GetDrawableComponent(ISkinComponent component) => null;
|
||||||
|
|
||||||
public override Texture GetTexture(string componentName) => null;
|
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => null;
|
||||||
|
|
||||||
public override SampleChannel GetSample(ISampleInfo sampleInfo) => null;
|
public override SampleChannel GetSample(ISampleInfo sampleInfo) => null;
|
||||||
|
|
||||||
|
@ -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 osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Timing;
|
using osu.Framework.Timing;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
@ -11,7 +12,7 @@ namespace osu.Game.Skinning
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// This should not be used to start an animation immediately at the current time.
|
/// This should not be used to start an animation immediately at the current time.
|
||||||
/// To do so, use <see cref="LegacySkinExtensions.GetAnimation"/> with <code>startAtCurrentTime = true</code> instead.
|
/// To do so, use <see cref="LegacySkinExtensions.GetAnimation(ISkin, string, WrapMode, WrapMode, bool, bool, bool, string, bool, double?)"/> with <code>startAtCurrentTime = true</code> instead.
|
||||||
/// </remarks>
|
/// </remarks>
|
||||||
[Cached]
|
[Cached]
|
||||||
public interface IAnimationTimeReference
|
public interface IAnimationTimeReference
|
||||||
|
@ -5,6 +5,7 @@ using JetBrains.Annotations;
|
|||||||
using osu.Framework.Audio.Sample;
|
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.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
|
||||||
@ -29,7 +30,17 @@ namespace osu.Game.Skinning
|
|||||||
/// <param name="componentName">The requested texture.</param>
|
/// <param name="componentName">The requested texture.</param>
|
||||||
/// <returns>A matching texture, or null if unavailable.</returns>
|
/// <returns>A matching texture, or null if unavailable.</returns>
|
||||||
[CanBeNull]
|
[CanBeNull]
|
||||||
Texture GetTexture(string componentName);
|
Texture GetTexture(string componentName) => GetTexture(componentName, default, default);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieve a <see cref="Texture"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="componentName">The requested texture.</param>
|
||||||
|
/// <param name="wrapModeS">The texture wrap mode in horizontal direction.</param>
|
||||||
|
/// <param name="wrapModeT">The texture wrap mode in vertical direction.</param>
|
||||||
|
/// <returns>A matching texture, or null if unavailable.</returns>
|
||||||
|
[CanBeNull]
|
||||||
|
Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieve a <see cref="SampleChannel"/>.
|
/// Retrieve a <see cref="SampleChannel"/>.
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Audio.Sample;
|
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.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
@ -311,17 +312,17 @@ namespace osu.Game.Skinning
|
|||||||
return this.GetAnimation(component.LookupName, false, false);
|
return this.GetAnimation(component.LookupName, false, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Texture GetTexture(string componentName)
|
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||||
{
|
{
|
||||||
foreach (var name in getFallbackNames(componentName))
|
foreach (var name in getFallbackNames(componentName))
|
||||||
{
|
{
|
||||||
float ratio = 2;
|
float ratio = 2;
|
||||||
var texture = Textures?.Get($"{name}@2x");
|
var texture = Textures?.Get($"{name}@2x", wrapModeS, wrapModeT);
|
||||||
|
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
{
|
{
|
||||||
ratio = 1;
|
ratio = 1;
|
||||||
texture = Textures?.Get(name);
|
texture = Textures?.Get(name, wrapModeS, wrapModeT);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (texture == null)
|
if (texture == null)
|
||||||
|
@ -6,6 +6,7 @@ using System.Linq;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
|
|
||||||
@ -15,6 +16,11 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-",
|
public static Drawable GetAnimation(this ISkin source, string componentName, bool animatable, bool looping, bool applyConfigFrameRate = false, string animationSeparator = "-",
|
||||||
bool startAtCurrentTime = true, double? frameLength = null)
|
bool startAtCurrentTime = true, double? frameLength = null)
|
||||||
|
=> source.GetAnimation(componentName, default, default, animatable, looping, applyConfigFrameRate, animationSeparator, startAtCurrentTime, frameLength);
|
||||||
|
|
||||||
|
public static Drawable GetAnimation(this ISkin source, string componentName, WrapMode wrapModeS, WrapMode wrapModeT, bool animatable, bool looping, bool applyConfigFrameRate = false,
|
||||||
|
string animationSeparator = "-",
|
||||||
|
bool startAtCurrentTime = true, double? frameLength = null)
|
||||||
{
|
{
|
||||||
Texture texture;
|
Texture texture;
|
||||||
|
|
||||||
@ -38,7 +44,7 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if an animation was not allowed or not found, fall back to a sprite retrieval.
|
// if an animation was not allowed or not found, fall back to a sprite retrieval.
|
||||||
if ((texture = source.GetTexture(componentName)) != null)
|
if ((texture = source.GetTexture(componentName, wrapModeS, wrapModeT)) != null)
|
||||||
return new Sprite { Texture = texture };
|
return new Sprite { Texture = texture };
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
@ -47,7 +53,7 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
for (int i = 0; true; i++)
|
for (int i = 0; true; i++)
|
||||||
{
|
{
|
||||||
if ((texture = source.GetTexture($"{componentName}{animationSeparator}{i}")) == null)
|
if ((texture = source.GetTexture($"{componentName}{animationSeparator}{i}", wrapModeS, wrapModeT)) == null)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
yield return texture;
|
yield return texture;
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
using osu.Framework.Audio.Sample;
|
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.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
using osu.Game.Rulesets.Objects.Legacy;
|
using osu.Game.Rulesets.Objects.Legacy;
|
||||||
@ -27,7 +28,10 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
public abstract Drawable GetDrawableComponent(ISkinComponent component);
|
public abstract Drawable GetDrawableComponent(ISkinComponent component);
|
||||||
|
|
||||||
public Texture GetTexture(string componentName) => Source.GetTexture(componentName);
|
public Texture GetTexture(string componentName) => GetTexture(componentName, default, default);
|
||||||
|
|
||||||
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||||
|
=> Source.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||||
|
|
||||||
public virtual SampleChannel GetSample(ISampleInfo sampleInfo)
|
public virtual SampleChannel GetSample(ISampleInfo sampleInfo)
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@ using System;
|
|||||||
using osu.Framework.Audio.Sample;
|
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.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
|
||||||
@ -20,7 +21,9 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
public abstract SampleChannel GetSample(ISampleInfo sampleInfo);
|
public abstract SampleChannel GetSample(ISampleInfo sampleInfo);
|
||||||
|
|
||||||
public abstract Texture GetTexture(string componentName);
|
public Texture GetTexture(string componentName) => GetTexture(componentName, default, default);
|
||||||
|
|
||||||
|
public abstract Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT);
|
||||||
|
|
||||||
public abstract IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup);
|
public abstract IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup);
|
||||||
|
|
||||||
|
@ -13,6 +13,7 @@ using osu.Framework.Audio;
|
|||||||
using osu.Framework.Audio.Sample;
|
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.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
using osu.Framework.Platform;
|
using osu.Framework.Platform;
|
||||||
@ -130,7 +131,7 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
public Drawable GetDrawableComponent(ISkinComponent component) => CurrentSkin.Value.GetDrawableComponent(component);
|
public Drawable GetDrawableComponent(ISkinComponent component) => CurrentSkin.Value.GetDrawableComponent(component);
|
||||||
|
|
||||||
public Texture GetTexture(string componentName) => CurrentSkin.Value.GetTexture(componentName);
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT) => CurrentSkin.Value.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||||
|
|
||||||
public SampleChannel GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo);
|
public SampleChannel GetSample(ISampleInfo sampleInfo) => CurrentSkin.Value.GetSample(sampleInfo);
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@ 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;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Game.Audio;
|
using osu.Game.Audio;
|
||||||
|
|
||||||
@ -47,13 +48,13 @@ namespace osu.Game.Skinning
|
|||||||
return fallbackSource?.GetDrawableComponent(component);
|
return fallbackSource?.GetDrawableComponent(component);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Texture GetTexture(string componentName)
|
public Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||||
{
|
{
|
||||||
Texture sourceTexture;
|
Texture sourceTexture;
|
||||||
if (AllowTextureLookup(componentName) && (sourceTexture = skin?.GetTexture(componentName)) != null)
|
if (AllowTextureLookup(componentName) && (sourceTexture = skin?.GetTexture(componentName, wrapModeS, wrapModeT)) != null)
|
||||||
return sourceTexture;
|
return sourceTexture;
|
||||||
|
|
||||||
return fallbackSource?.GetTexture(componentName);
|
return fallbackSource?.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public SampleChannel GetSample(ISampleInfo sampleInfo)
|
public SampleChannel GetSample(ISampleInfo sampleInfo)
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.OpenGL.Textures;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Textures;
|
using osu.Framework.Graphics.Textures;
|
||||||
using osu.Framework.IO.Stores;
|
using osu.Framework.IO.Stores;
|
||||||
@ -157,7 +158,7 @@ namespace osu.Game.Tests.Visual
|
|||||||
this.extrapolateAnimations = extrapolateAnimations;
|
this.extrapolateAnimations = extrapolateAnimations;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Texture GetTexture(string componentName)
|
public override Texture GetTexture(string componentName, WrapMode wrapModeS, WrapMode wrapModeT)
|
||||||
{
|
{
|
||||||
// extrapolate frames to test longer animations
|
// extrapolate frames to test longer animations
|
||||||
if (extrapolateAnimations)
|
if (extrapolateAnimations)
|
||||||
@ -165,10 +166,10 @@ namespace osu.Game.Tests.Visual
|
|||||||
var match = Regex.Match(componentName, "-([0-9]*)");
|
var match = Regex.Match(componentName, "-([0-9]*)");
|
||||||
|
|
||||||
if (match.Length > 0 && int.TryParse(match.Groups[1].Value, out var number) && number < 60)
|
if (match.Length > 0 && int.TryParse(match.Groups[1].Value, out var number) && number < 60)
|
||||||
return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"));
|
return base.GetTexture(componentName.Replace($"-{number}", $"-{number % 2}"), wrapModeS, wrapModeT);
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.GetTexture(componentName);
|
return base.GetTexture(componentName, wrapModeS, wrapModeT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -783,6 +783,7 @@ See the LICENCE file in the repository root for full licence text.
|
|||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002ECSharpPlaceAttributeOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateThisQualifierSettings/@EntryIndexedValue">True</s:Boolean>
|
||||||
|
<s:String x:Key="/Default/Environment/UnitTesting/NUnitProvider/SetCurrentDirectoryTo/@EntryValue">TestFolder</s:String>
|
||||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/@KeyIndexDefined">True</s:Boolean>
|
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/@KeyIndexDefined">True</s:Boolean>
|
||||||
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Applicability/=Live/@EntryIndexedValue">True</s:Boolean>
|
||||||
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Description/@EntryValue">o!f – Object Initializer: Anchor&Origin</s:String>
|
<s:String x:Key="/Default/PatternsAndTemplates/LiveTemplates/Template/=28A2A5FC43E07C488A4BC7430879479E/Description/@EntryValue">o!f – Object Initializer: Anchor&Origin</s:String>
|
||||||
|
Loading…
Reference in New Issue
Block a user