mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 18:42:56 +08:00
Merge branch 'master' into skin-editor-clipboard
This commit is contained in:
commit
ec1bf7b0b2
@ -60,26 +60,24 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestCatcherHyperStateReverted()
|
public void TestCatcherHyperStateReverted()
|
||||||
{
|
{
|
||||||
DrawableCatchHitObject drawableObject1 = null;
|
|
||||||
DrawableCatchHitObject drawableObject2 = null;
|
|
||||||
JudgementResult result1 = null;
|
JudgementResult result1 = null;
|
||||||
JudgementResult result2 = null;
|
JudgementResult result2 = null;
|
||||||
AddStep("catch hyper fruit", () =>
|
AddStep("catch hyper fruit", () =>
|
||||||
{
|
{
|
||||||
attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } }, out drawableObject1, out result1);
|
result1 = attemptCatch(new Fruit { HyperDashTarget = new Fruit { X = 100 } });
|
||||||
});
|
});
|
||||||
AddStep("catch normal fruit", () =>
|
AddStep("catch normal fruit", () =>
|
||||||
{
|
{
|
||||||
attemptCatch(new Fruit(), out drawableObject2, out result2);
|
result2 = attemptCatch(new Fruit());
|
||||||
});
|
});
|
||||||
AddStep("revert second result", () =>
|
AddStep("revert second result", () =>
|
||||||
{
|
{
|
||||||
catcher.OnRevertResult(drawableObject2, result2);
|
catcher.OnRevertResult(result2);
|
||||||
});
|
});
|
||||||
checkHyperDash(true);
|
checkHyperDash(true);
|
||||||
AddStep("revert first result", () =>
|
AddStep("revert first result", () =>
|
||||||
{
|
{
|
||||||
catcher.OnRevertResult(drawableObject1, result1);
|
catcher.OnRevertResult(result1);
|
||||||
});
|
});
|
||||||
checkHyperDash(false);
|
checkHyperDash(false);
|
||||||
}
|
}
|
||||||
@ -87,16 +85,15 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
[Test]
|
[Test]
|
||||||
public void TestCatcherAnimationStateReverted()
|
public void TestCatcherAnimationStateReverted()
|
||||||
{
|
{
|
||||||
DrawableCatchHitObject drawableObject = null;
|
|
||||||
JudgementResult result = null;
|
JudgementResult result = null;
|
||||||
AddStep("catch kiai fruit", () =>
|
AddStep("catch kiai fruit", () =>
|
||||||
{
|
{
|
||||||
attemptCatch(new TestKiaiFruit(), out drawableObject, out result);
|
result = attemptCatch(new TestKiaiFruit());
|
||||||
});
|
});
|
||||||
checkState(CatcherAnimationState.Kiai);
|
checkState(CatcherAnimationState.Kiai);
|
||||||
AddStep("revert result", () =>
|
AddStep("revert result", () =>
|
||||||
{
|
{
|
||||||
catcher.OnRevertResult(drawableObject, result);
|
catcher.OnRevertResult(result);
|
||||||
});
|
});
|
||||||
checkState(CatcherAnimationState.Idle);
|
checkState(CatcherAnimationState.Idle);
|
||||||
}
|
}
|
||||||
@ -268,23 +265,19 @@ namespace osu.Game.Rulesets.Catch.Tests
|
|||||||
|
|
||||||
private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state);
|
private void checkHyperDash(bool state) => AddAssert($"catcher is {(state ? "" : "not ")}hyper dashing", () => catcher.HyperDashing == state);
|
||||||
|
|
||||||
private void attemptCatch(CatchHitObject hitObject)
|
|
||||||
{
|
|
||||||
attemptCatch(() => hitObject, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void attemptCatch(Func<CatchHitObject> hitObject, int count)
|
private void attemptCatch(Func<CatchHitObject> hitObject, int count)
|
||||||
{
|
{
|
||||||
for (int i = 0; i < count; i++)
|
for (int i = 0; i < count; i++)
|
||||||
attemptCatch(hitObject(), out _, out _);
|
attemptCatch(hitObject());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void attemptCatch(CatchHitObject hitObject, out DrawableCatchHitObject drawableObject, out JudgementResult result)
|
private JudgementResult attemptCatch(CatchHitObject hitObject)
|
||||||
{
|
{
|
||||||
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
hitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
drawableObject = createDrawableObject(hitObject);
|
var drawableObject = createDrawableObject(hitObject);
|
||||||
result = createResult(hitObject);
|
var result = createResult(hitObject);
|
||||||
applyResult(drawableObject, result);
|
applyResult(drawableObject, result);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyResult(DrawableCatchHitObject drawableObject, JudgementResult result)
|
private void applyResult(DrawableCatchHitObject drawableObject, JudgementResult result)
|
||||||
|
@ -63,12 +63,12 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
updateCombo(result.ComboAtJudgement + 1, judgedObject.AccentColour.Value);
|
updateCombo(result.ComboAtJudgement + 1, judgedObject.AccentColour.Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnRevertResult(DrawableCatchHitObject judgedObject, JudgementResult result)
|
public void OnRevertResult(JudgementResult result)
|
||||||
{
|
{
|
||||||
if (!result.Type.AffectsCombo() || !result.HasResult)
|
if (!result.Type.AffectsCombo() || !result.HasResult)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
updateCombo(result.ComboAtJudgement, judgedObject.AccentColour.Value);
|
updateCombo(result.ComboAtJudgement, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateCombo(int newCombo, Color4? hitObjectColour)
|
private void updateCombo(int newCombo, Color4? hitObjectColour)
|
||||||
|
@ -103,7 +103,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onNewResult(DrawableHitObject judgedObject, JudgementResult result)
|
||||||
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
|
=> CatcherArea.OnNewResult((DrawableCatchHitObject)judgedObject, result);
|
||||||
|
|
||||||
private void onRevertResult(DrawableHitObject judgedObject, JudgementResult result)
|
private void onRevertResult(JudgementResult result)
|
||||||
=> CatcherArea.OnRevertResult((DrawableCatchHitObject)judgedObject, result);
|
=> CatcherArea.OnRevertResult(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -254,7 +254,7 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnRevertResult(DrawableCatchHitObject drawableObject, JudgementResult result)
|
public void OnRevertResult(JudgementResult result)
|
||||||
{
|
{
|
||||||
var catchResult = (CatchJudgementResult)result;
|
var catchResult = (CatchJudgementResult)result;
|
||||||
|
|
||||||
@ -268,8 +268,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
SetHyperDashState();
|
SetHyperDashState();
|
||||||
}
|
}
|
||||||
|
|
||||||
caughtObjectContainer.RemoveAll(d => d.HitObject == drawableObject.HitObject, false);
|
caughtObjectContainer.RemoveAll(d => d.HitObject == result.HitObject, false);
|
||||||
droppedObjectTarget.RemoveAll(d => d.HitObject == drawableObject.HitObject, false);
|
droppedObjectTarget.RemoveAll(d => d.HitObject == result.HitObject, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -73,10 +73,10 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
comboDisplay.OnNewResult(hitObject, result);
|
comboDisplay.OnNewResult(hitObject, result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnRevertResult(DrawableCatchHitObject hitObject, JudgementResult result)
|
public void OnRevertResult(JudgementResult result)
|
||||||
{
|
{
|
||||||
comboDisplay.OnRevertResult(hitObject, result);
|
comboDisplay.OnRevertResult(result);
|
||||||
Catcher.OnRevertResult(hitObject, result);
|
Catcher.OnRevertResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
|
@ -0,0 +1,67 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Rulesets.Mania.UI;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
||||||
|
{
|
||||||
|
public partial class TestSceneObjectPlacement : EditorTestScene
|
||||||
|
{
|
||||||
|
protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPlacementBeforeTrackStart()
|
||||||
|
{
|
||||||
|
AddStep("Seek to 0", () => EditorClock.Seek(0));
|
||||||
|
AddStep("Select note", () => InputManager.Key(Key.Number2));
|
||||||
|
AddStep("Hover negative span", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(this.ChildrenOfType<Container>().First(x => x.Name == "Icons").Children[0]);
|
||||||
|
});
|
||||||
|
AddStep("Click", () => InputManager.Click(MouseButton.Left));
|
||||||
|
AddAssert("No notes placed", () => EditorBeatmap.HitObjects.All(x => x.StartTime >= 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSeekOnNotePlacement()
|
||||||
|
{
|
||||||
|
double? initialTime = null;
|
||||||
|
|
||||||
|
AddStep("store initial time", () => initialTime = EditorClock.CurrentTime);
|
||||||
|
AddStep("change seek setting to true", () => config.SetValue(OsuSetting.EditorAutoSeekOnPlacement, true));
|
||||||
|
placeObject();
|
||||||
|
AddUntilStep("wait for seek to complete", () => !EditorClock.IsSeeking);
|
||||||
|
AddAssert("seeked forward to object", () => EditorClock.CurrentTime, () => Is.GreaterThan(initialTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNoSeekOnNotePlacement()
|
||||||
|
{
|
||||||
|
double? initialTime = null;
|
||||||
|
|
||||||
|
AddStep("store initial time", () => initialTime = EditorClock.CurrentTime);
|
||||||
|
AddStep("change seek setting to false", () => config.SetValue(OsuSetting.EditorAutoSeekOnPlacement, false));
|
||||||
|
placeObject();
|
||||||
|
AddAssert("not seeking", () => !EditorClock.IsSeeking);
|
||||||
|
AddAssert("time is unchanged", () => EditorClock.CurrentTime, () => Is.EqualTo(initialTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void placeObject()
|
||||||
|
{
|
||||||
|
AddStep("select note placement tool", () => InputManager.Key(Key.Number2));
|
||||||
|
AddStep("move mouse to centre of last column", () => InputManager.MoveMouseTo(this.ChildrenOfType<Column>().Last().ScreenSpaceDrawQuad.Centre));
|
||||||
|
AddStep("place note", () => InputManager.Click(MouseButton.Left));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,30 +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.Linq;
|
|
||||||
using NUnit.Framework;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Testing;
|
|
||||||
using osu.Game.Tests.Visual;
|
|
||||||
using osuTK.Input;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests.Editor
|
|
||||||
{
|
|
||||||
public partial class TestScenePlacementBeforeTrackStart : EditorTestScene
|
|
||||||
{
|
|
||||||
protected override Ruleset CreateEditorRuleset() => new ManiaRuleset();
|
|
||||||
|
|
||||||
[Test]
|
|
||||||
public void TestPlacement()
|
|
||||||
{
|
|
||||||
AddStep("Seek to 0", () => EditorClock.Seek(0));
|
|
||||||
AddStep("Select note", () => InputManager.Key(Key.Number2));
|
|
||||||
AddStep("Hover negative span", () =>
|
|
||||||
{
|
|
||||||
InputManager.MoveMouseTo(this.ChildrenOfType<Container>().First(x => x.Name == "Icons").Children[0]);
|
|
||||||
});
|
|
||||||
AddStep("Click", () => InputManager.Click(MouseButton.Left));
|
|
||||||
AddAssert("No notes placed", () => EditorBeatmap.HitObjects.All(x => x.StartTime >= 0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
156
osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs
Normal file
156
osu.Game.Rulesets.Osu.Tests/TestSceneHitCircleLateFade.cs
Normal file
@ -0,0 +1,156 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
|
using osu.Game.Tests.Visual;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
|
{
|
||||||
|
public partial class TestSceneHitCircleLateFade : OsuTestScene
|
||||||
|
{
|
||||||
|
private float? alphaAtMiss;
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitCircleClassicMod()
|
||||||
|
{
|
||||||
|
AddStep("Create hit circle", () =>
|
||||||
|
{
|
||||||
|
SelectedMods.Value = new Mod[] { new OsuModClassic() };
|
||||||
|
createCircle();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
|
||||||
|
AddAssert("Transparent when missed", () => alphaAtMiss == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitCircleClassicAndFullHiddenMods()
|
||||||
|
{
|
||||||
|
AddStep("Create hit circle", () =>
|
||||||
|
{
|
||||||
|
SelectedMods.Value = new Mod[] { new OsuModHidden(), new OsuModClassic() };
|
||||||
|
createCircle();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
|
||||||
|
AddAssert("Transparent when missed", () => alphaAtMiss == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitCircleClassicAndApproachCircleOnlyHiddenMods()
|
||||||
|
{
|
||||||
|
AddStep("Create hit circle", () =>
|
||||||
|
{
|
||||||
|
SelectedMods.Value = new Mod[] { new OsuModHidden { OnlyFadeApproachCircles = { Value = true } }, new OsuModClassic() };
|
||||||
|
createCircle();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
|
||||||
|
AddAssert("Transparent when missed", () => alphaAtMiss == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitCircleNoMod()
|
||||||
|
{
|
||||||
|
AddStep("Create hit circle", () =>
|
||||||
|
{
|
||||||
|
SelectedMods.Value = Array.Empty<Mod>();
|
||||||
|
createCircle();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Wait until circle is missed", () => alphaAtMiss.IsNotNull());
|
||||||
|
AddAssert("Opaque when missed", () => alphaAtMiss == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderClassicMod()
|
||||||
|
{
|
||||||
|
AddStep("Create slider", () =>
|
||||||
|
{
|
||||||
|
SelectedMods.Value = new Mod[] { new OsuModClassic() };
|
||||||
|
createSlider();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Wait until head circle is missed", () => alphaAtMiss.IsNotNull());
|
||||||
|
AddAssert("Head circle transparent when missed", () => alphaAtMiss == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSliderNoMod()
|
||||||
|
{
|
||||||
|
AddStep("Create slider", () =>
|
||||||
|
{
|
||||||
|
SelectedMods.Value = Array.Empty<Mod>();
|
||||||
|
createSlider();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Wait until head circle is missed", () => alphaAtMiss.IsNotNull());
|
||||||
|
AddAssert("Head circle opaque when missed", () => alphaAtMiss == 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createCircle()
|
||||||
|
{
|
||||||
|
alphaAtMiss = null;
|
||||||
|
|
||||||
|
DrawableHitCircle drawableHitCircle = new DrawableHitCircle(new HitCircle
|
||||||
|
{
|
||||||
|
StartTime = Time.Current + 500,
|
||||||
|
Position = new Vector2(250)
|
||||||
|
});
|
||||||
|
|
||||||
|
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObject>())
|
||||||
|
mod.ApplyToDrawableHitObject(drawableHitCircle);
|
||||||
|
|
||||||
|
drawableHitCircle.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
drawableHitCircle.OnNewResult += (_, _) =>
|
||||||
|
{
|
||||||
|
alphaAtMiss = drawableHitCircle.Alpha;
|
||||||
|
};
|
||||||
|
|
||||||
|
Child = drawableHitCircle;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createSlider()
|
||||||
|
{
|
||||||
|
alphaAtMiss = null;
|
||||||
|
|
||||||
|
DrawableSlider drawableSlider = new DrawableSlider(new Slider
|
||||||
|
{
|
||||||
|
StartTime = Time.Current + 500,
|
||||||
|
Position = new Vector2(250),
|
||||||
|
Path = new SliderPath(PathType.Linear, new[]
|
||||||
|
{
|
||||||
|
Vector2.Zero,
|
||||||
|
new Vector2(0, 100),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
drawableSlider.HitObject.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
|
||||||
|
|
||||||
|
drawableSlider.OnLoadComplete += _ =>
|
||||||
|
{
|
||||||
|
foreach (var mod in SelectedMods.Value.OfType<IApplicableToDrawableHitObject>())
|
||||||
|
mod.ApplyToDrawableHitObject(drawableSlider.HeadCircle);
|
||||||
|
|
||||||
|
drawableSlider.HeadCircle.OnNewResult += (_, _) =>
|
||||||
|
{
|
||||||
|
alphaAtMiss = drawableSlider.HeadCircle.Alpha;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
Child = drawableSlider;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -4,6 +4,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
@ -11,6 +12,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Game.Rulesets.Osu.Objects;
|
using osu.Game.Rulesets.Osu.Objects;
|
||||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Osu.UI;
|
using osu.Game.Rulesets.Osu.UI;
|
||||||
|
using osu.Game.Rulesets.Scoring;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Mods
|
namespace osu.Game.Rulesets.Osu.Mods
|
||||||
@ -31,6 +33,11 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
[SettingSource("Always play a slider's tail sample", "Always plays a slider's tail sample regardless of whether it was hit or not.")]
|
[SettingSource("Always play a slider's tail sample", "Always plays a slider's tail sample regardless of whether it was hit or not.")]
|
||||||
public Bindable<bool> AlwaysPlayTailSample { get; } = new BindableBool(true);
|
public Bindable<bool> AlwaysPlayTailSample { get; } = new BindableBool(true);
|
||||||
|
|
||||||
|
[SettingSource("Fade out hit circles earlier", "Make hit circles fade out into a miss, rather than after it.")]
|
||||||
|
public Bindable<bool> FadeHitCircleEarly { get; } = new Bindable<bool>(true);
|
||||||
|
|
||||||
|
private bool usingHiddenFading;
|
||||||
|
|
||||||
public void ApplyToHitObject(HitObject hitObject)
|
public void ApplyToHitObject(HitObject hitObject)
|
||||||
{
|
{
|
||||||
switch (hitObject)
|
switch (hitObject)
|
||||||
@ -51,6 +58,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
|
|
||||||
if (ClassicNoteLock.Value)
|
if (ClassicNoteLock.Value)
|
||||||
osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy();
|
osuRuleset.Playfield.HitPolicy = new ObjectOrderedHitPolicy();
|
||||||
|
|
||||||
|
usingHiddenFading = drawableRuleset.Mods.OfType<OsuModHidden>().SingleOrDefault()?.OnlyFadeApproachCircles.Value == false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void ApplyToDrawableHitObject(DrawableHitObject obj)
|
public void ApplyToDrawableHitObject(DrawableHitObject obj)
|
||||||
@ -59,12 +68,32 @@ namespace osu.Game.Rulesets.Osu.Mods
|
|||||||
{
|
{
|
||||||
case DrawableSliderHead head:
|
case DrawableSliderHead head:
|
||||||
head.TrackFollowCircle = !NoSliderHeadMovement.Value;
|
head.TrackFollowCircle = !NoSliderHeadMovement.Value;
|
||||||
|
if (FadeHitCircleEarly.Value && !usingHiddenFading)
|
||||||
|
applyEarlyFading(head);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case DrawableSliderTail tail:
|
case DrawableSliderTail tail:
|
||||||
tail.SamplePlaysOnlyOnHit = !AlwaysPlayTailSample.Value;
|
tail.SamplePlaysOnlyOnHit = !AlwaysPlayTailSample.Value;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case DrawableHitCircle circle:
|
||||||
|
if (FadeHitCircleEarly.Value && !usingHiddenFading)
|
||||||
|
applyEarlyFading(circle);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyEarlyFading(DrawableHitCircle circle)
|
||||||
|
{
|
||||||
|
circle.ApplyCustomUpdateState += (o, _) =>
|
||||||
|
{
|
||||||
|
using (o.BeginAbsoluteSequence(o.StateUpdateTime))
|
||||||
|
{
|
||||||
|
double okWindow = o.HitObject.HitWindows.WindowFor(HitResult.Ok);
|
||||||
|
double lateMissFadeTime = o.HitObject.HitWindows.WindowFor(HitResult.Meh) - okWindow;
|
||||||
|
o.Delay(okWindow).FadeOut(lateMissFadeTime);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
public partial class DrawableOsuJudgement : DrawableJudgement
|
public partial class DrawableOsuJudgement : DrawableJudgement
|
||||||
{
|
{
|
||||||
protected SkinnableLighting Lighting { get; private set; }
|
internal SkinnableLighting Lighting { get; private set; }
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; }
|
||||||
|
@ -10,7 +10,7 @@ using osuTK.Graphics;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
||||||
{
|
{
|
||||||
public partial class SkinnableLighting : SkinnableSprite
|
internal partial class SkinnableLighting : SkinnableSprite
|
||||||
{
|
{
|
||||||
private DrawableHitObject targetObject;
|
private DrawableHitObject targetObject;
|
||||||
private JudgementResult targetResult;
|
private JudgementResult targetResult;
|
||||||
|
@ -66,9 +66,9 @@ namespace osu.Game.Tests.Skins
|
|||||||
{
|
{
|
||||||
var skin = new TestSkin(new SkinInfo(), null, storage);
|
var skin = new TestSkin(new SkinInfo(), null, storage);
|
||||||
|
|
||||||
foreach (var target in skin.DrawableComponentInfo)
|
foreach (var target in skin.LayoutInfos)
|
||||||
{
|
{
|
||||||
foreach (var info in target.Value)
|
foreach (var info in target.Value.AllDrawables)
|
||||||
instantiatedTypes.Add(info.Type);
|
instantiatedTypes.Add(info.Type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -87,8 +87,8 @@ namespace osu.Game.Tests.Skins
|
|||||||
{
|
{
|
||||||
var skin = new TestSkin(new SkinInfo(), null, storage);
|
var skin = new TestSkin(new SkinInfo(), null, storage);
|
||||||
|
|
||||||
Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
|
Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
|
||||||
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents], Has.Length.EqualTo(9));
|
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(9));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,11 +100,11 @@ namespace osu.Game.Tests.Skins
|
|||||||
{
|
{
|
||||||
var skin = new TestSkin(new SkinInfo(), null, storage);
|
var skin = new TestSkin(new SkinInfo(), null, storage);
|
||||||
|
|
||||||
Assert.That(skin.DrawableComponentInfo, Has.Count.EqualTo(2));
|
Assert.That(skin.LayoutInfos, Has.Count.EqualTo(2));
|
||||||
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents], Has.Length.EqualTo(6));
|
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(6));
|
||||||
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.SongSelect], Has.Length.EqualTo(1));
|
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.SongSelect].AllDrawables.ToArray(), Has.Length.EqualTo(1));
|
||||||
|
|
||||||
var skinnableInfo = skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.SongSelect].First();
|
var skinnableInfo = skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.SongSelect].AllDrawables.First();
|
||||||
|
|
||||||
Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite)));
|
Assert.That(skinnableInfo.Type, Is.EqualTo(typeof(SkinnableSprite)));
|
||||||
Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name"));
|
Assert.That(skinnableInfo.Settings.First().Key, Is.EqualTo("sprite_name"));
|
||||||
@ -115,10 +115,10 @@ namespace osu.Game.Tests.Skins
|
|||||||
using (var storage = new ZipArchiveReader(stream))
|
using (var storage = new ZipArchiveReader(stream))
|
||||||
{
|
{
|
||||||
var skin = new TestSkin(new SkinInfo(), null, storage);
|
var skin = new TestSkin(new SkinInfo(), null, storage);
|
||||||
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents], Has.Length.EqualTo(8));
|
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.ToArray(), Has.Length.EqualTo(8));
|
||||||
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
|
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(UnstableRateCounter)));
|
||||||
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
|
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(ColourHitErrorMeter)));
|
||||||
Assert.That(skin.DrawableComponentInfo[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
|
Assert.That(skin.LayoutInfos[SkinComponentsContainerLookup.TargetArea.MainHUDComponents].AllDrawables.Select(i => i.Type), Contains.Item(typeof(LegacySongProgress)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,7 +19,6 @@ using osu.Framework.Utils;
|
|||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Rulesets;
|
using osu.Game.Rulesets;
|
||||||
using osu.Game.Rulesets.Difficulty;
|
using osu.Game.Rulesets.Difficulty;
|
||||||
using osu.Game.Rulesets.Judgements;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -37,6 +36,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private TestDrawablePoolingRuleset drawableRuleset;
|
private TestDrawablePoolingRuleset drawableRuleset;
|
||||||
|
|
||||||
|
private TestPlayfield playfield => (TestPlayfield)drawableRuleset.Playfield;
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestReusedWithHitObjectsSpacedFarApart()
|
public void TestReusedWithHitObjectsSpacedFarApart()
|
||||||
{
|
{
|
||||||
@ -133,29 +134,49 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddUntilStep("no DHOs shown", () => !this.ChildrenOfType<DrawableTestHitObject>().Any());
|
AddUntilStep("no DHOs shown", () => !this.ChildrenOfType<DrawableTestHitObject>().Any());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRevertResult()
|
||||||
|
{
|
||||||
|
ManualClock clock = null;
|
||||||
|
Beatmap beatmap;
|
||||||
|
|
||||||
|
createTest(beatmap = new Beatmap
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new TestHitObject { StartTime = 0 },
|
||||||
|
new TestHitObject { StartTime = 500 },
|
||||||
|
new TestHitObject { StartTime = 1000 },
|
||||||
|
}
|
||||||
|
}, 10, () => new FramedClock(clock = new ManualClock()));
|
||||||
|
|
||||||
|
AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100);
|
||||||
|
AddUntilStep("all judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(3));
|
||||||
|
|
||||||
|
AddStep("rewind to middle", () => clock.CurrentTime = beatmap.HitObjects[1].StartTime - 100);
|
||||||
|
AddUntilStep("some results reverted", () => playfield.JudgedObjects.Count, () => Is.EqualTo(1));
|
||||||
|
|
||||||
|
AddStep("fast forward to end", () => clock.CurrentTime = beatmap.HitObjects[^1].GetEndTime() + 100);
|
||||||
|
AddUntilStep("all judged", () => playfield.JudgedObjects.Count, () => Is.EqualTo(3));
|
||||||
|
|
||||||
|
AddStep("disable frame stability", () => drawableRuleset.FrameStablePlayback = false);
|
||||||
|
AddStep("instant seek to start", () => clock.CurrentTime = beatmap.HitObjects[0].StartTime - 100);
|
||||||
|
AddAssert("all results reverted", () => playfield.JudgedObjects.Count, () => Is.EqualTo(0));
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestApplyHitResultOnKilled()
|
public void TestApplyHitResultOnKilled()
|
||||||
{
|
{
|
||||||
ManualClock clock = null;
|
ManualClock clock = null;
|
||||||
bool anyJudged = false;
|
|
||||||
|
|
||||||
void onNewResult(JudgementResult _) => anyJudged = true;
|
|
||||||
|
|
||||||
var beatmap = new Beatmap();
|
var beatmap = new Beatmap();
|
||||||
beatmap.HitObjects.Add(new TestKilledHitObject { Duration = 20 });
|
beatmap.HitObjects.Add(new TestKilledHitObject { Duration = 20 });
|
||||||
|
|
||||||
createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock()));
|
createTest(beatmap, 10, () => new FramedClock(clock = new ManualClock()));
|
||||||
|
|
||||||
AddStep("subscribe to new result", () =>
|
|
||||||
{
|
|
||||||
anyJudged = false;
|
|
||||||
drawableRuleset.NewResult += onNewResult;
|
|
||||||
});
|
|
||||||
AddStep("skip past object", () => clock.CurrentTime = beatmap.HitObjects[0].GetEndTime() + 1000);
|
AddStep("skip past object", () => clock.CurrentTime = beatmap.HitObjects[0].GetEndTime() + 1000);
|
||||||
|
|
||||||
AddAssert("object judged", () => anyJudged);
|
AddAssert("object judged", () => playfield.JudgedObjects.Count == 1);
|
||||||
|
|
||||||
AddStep("clean up", () => drawableRuleset.NewResult -= onNewResult);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createTest(IBeatmap beatmap, int poolSize, Func<IFrameBasedClock> createClock = null)
|
private void createTest(IBeatmap beatmap, int poolSize, Func<IFrameBasedClock> createClock = null)
|
||||||
@ -212,12 +233,24 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
|
|
||||||
private partial class TestPlayfield : Playfield
|
private partial class TestPlayfield : Playfield
|
||||||
{
|
{
|
||||||
|
public readonly HashSet<HitObject> JudgedObjects = new HashSet<HitObject>();
|
||||||
|
|
||||||
private readonly int poolSize;
|
private readonly int poolSize;
|
||||||
|
|
||||||
public TestPlayfield(int poolSize)
|
public TestPlayfield(int poolSize)
|
||||||
{
|
{
|
||||||
this.poolSize = poolSize;
|
this.poolSize = poolSize;
|
||||||
AddInternal(HitObjectContainer);
|
AddInternal(HitObjectContainer);
|
||||||
|
NewResult += (_, r) =>
|
||||||
|
{
|
||||||
|
Assert.That(JudgedObjects, Has.No.Member(r.HitObject));
|
||||||
|
JudgedObjects.Add(r.HitObject);
|
||||||
|
};
|
||||||
|
RevertResult += r =>
|
||||||
|
{
|
||||||
|
Assert.That(JudgedObjects, Has.Member(r.HitObject));
|
||||||
|
JudgedObjects.Remove(r.HitObject);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
|
@ -22,12 +22,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
[Cached(typeof(ScoreProcessor))]
|
[Cached(typeof(ScoreProcessor))]
|
||||||
private TestScoreProcessor scoreProcessor = new TestScoreProcessor();
|
private TestScoreProcessor scoreProcessor = new TestScoreProcessor();
|
||||||
|
|
||||||
private readonly OsuHitWindows hitWindows = new OsuHitWindows();
|
private readonly OsuHitWindows hitWindows;
|
||||||
|
|
||||||
private UnstableRateCounter counter;
|
private UnstableRateCounter counter;
|
||||||
|
|
||||||
private double prev;
|
private double prev;
|
||||||
|
|
||||||
|
public TestSceneUnstableRateCounter()
|
||||||
|
{
|
||||||
|
hitWindows = new OsuHitWindows();
|
||||||
|
hitWindows.SetDifficulty(5);
|
||||||
|
}
|
||||||
|
|
||||||
[SetUpSteps]
|
[SetUpSteps]
|
||||||
public void SetUp()
|
public void SetUp()
|
||||||
{
|
{
|
||||||
|
@ -178,6 +178,7 @@ namespace osu.Game.Configuration
|
|||||||
SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 0.75f, 0.25f);
|
SetDefault(OsuSetting.EditorDim, 0.25f, 0f, 0.75f, 0.25f);
|
||||||
SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f);
|
SetDefault(OsuSetting.EditorWaveformOpacity, 0.25f, 0f, 1f, 0.25f);
|
||||||
SetDefault(OsuSetting.EditorShowHitMarkers, true);
|
SetDefault(OsuSetting.EditorShowHitMarkers, true);
|
||||||
|
SetDefault(OsuSetting.EditorAutoSeekOnPlacement, true);
|
||||||
|
|
||||||
SetDefault(OsuSetting.LastProcessedMetadataId, -1);
|
SetDefault(OsuSetting.LastProcessedMetadataId, -1);
|
||||||
|
|
||||||
@ -374,6 +375,7 @@ namespace osu.Game.Configuration
|
|||||||
SeasonalBackgroundMode,
|
SeasonalBackgroundMode,
|
||||||
EditorWaveformOpacity,
|
EditorWaveformOpacity,
|
||||||
EditorShowHitMarkers,
|
EditorShowHitMarkers,
|
||||||
|
EditorAutoSeekOnPlacement,
|
||||||
DiscordRichPresence,
|
DiscordRichPresence,
|
||||||
AutomaticallyDownloadWhenSpectating,
|
AutomaticallyDownloadWhenSpectating,
|
||||||
ShowOnlineExplicitContent,
|
ShowOnlineExplicitContent,
|
||||||
|
@ -19,6 +19,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString ShowHitMarkers => new TranslatableString(getKey(@"show_hit_markers"), @"Show hit markers");
|
public static LocalisableString ShowHitMarkers => new TranslatableString(getKey(@"show_hit_markers"), @"Show hit markers");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Automatically seek after placing objects"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString AutoSeekOnPlacement => new TranslatableString(getKey(@"auto_seek_on_placement"), @"Automatically seek after placing objects");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "Timing"
|
/// "Timing"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Backgrounds;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -27,7 +28,14 @@ namespace osu.Game.Overlays.Mods
|
|||||||
public Color4 AccentColour
|
public Color4 AccentColour
|
||||||
{
|
{
|
||||||
get => headerBackground.Colour;
|
get => headerBackground.Colour;
|
||||||
set => headerBackground.Colour = value;
|
set
|
||||||
|
{
|
||||||
|
headerBackground.Colour = value;
|
||||||
|
|
||||||
|
var hsv = new Colour4(value.R, value.G, value.B, 1f).ToHSV();
|
||||||
|
var trianglesColour = Colour4.FromHSV(hsv.X, hsv.Y + 0.2f, hsv.Z - 0.1f);
|
||||||
|
triangles.Colour = ColourInfo.GradientVertical(trianglesColour, trianglesColour.MultiplyAlpha(0f));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -44,6 +52,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
private readonly Box headerBackground;
|
private readonly Box headerBackground;
|
||||||
private readonly Container contentContainer;
|
private readonly Container contentContainer;
|
||||||
private readonly Box contentBackground;
|
private readonly Box contentBackground;
|
||||||
|
private readonly TrianglesV2 triangles;
|
||||||
|
|
||||||
private const float header_height = 42;
|
private const float header_height = 42;
|
||||||
|
|
||||||
@ -73,6 +82,13 @@ namespace osu.Game.Overlays.Mods
|
|||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = header_height + ModSelectPanel.CORNER_RADIUS
|
Height = header_height + ModSelectPanel.CORNER_RADIUS
|
||||||
},
|
},
|
||||||
|
triangles = new TrianglesV2
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = header_height,
|
||||||
|
Shear = new Vector2(-ShearedOverlayContainer.SHEAR, 0),
|
||||||
|
Velocity = 0.7f,
|
||||||
|
},
|
||||||
headerText = new OsuTextFlowContainer(t =>
|
headerText = new OsuTextFlowContainer(t =>
|
||||||
{
|
{
|
||||||
t.Font = OsuFont.TorusAlternate.With(size: 17);
|
t.Font = OsuFont.TorusAlternate.With(size: 17);
|
||||||
|
@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Notifications
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string CompletionText { get; set; } = "Task has completed!";
|
public LocalisableString CompletionText { get; set; } = "Task has completed!";
|
||||||
|
|
||||||
private float progress;
|
private float progress;
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Input.Handlers.Tablet;
|
using osu.Framework.Input.Handlers.Tablet;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
@ -66,7 +67,7 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Colour = colour.Gray1,
|
Colour = colour.Gray1,
|
||||||
},
|
},
|
||||||
usableAreaContainer = new Container
|
usableAreaContainer = new UsableAreaContainer(handler)
|
||||||
{
|
{
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
@ -225,4 +226,28 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
tabletContainer.Scale = new Vector2(1 / adjust);
|
tabletContainer.Scale = new Vector2(1 / adjust);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public partial class UsableAreaContainer : Container
|
||||||
|
{
|
||||||
|
private readonly Bindable<Vector2> areaOffset;
|
||||||
|
|
||||||
|
public UsableAreaContainer(ITabletHandler tabletHandler)
|
||||||
|
{
|
||||||
|
areaOffset = tabletHandler.AreaOffset.GetBoundCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDragStart(DragStartEvent e) => true;
|
||||||
|
|
||||||
|
protected override void OnDrag(DragEvent e)
|
||||||
|
{
|
||||||
|
var newPos = Position + e.Delta;
|
||||||
|
this.MoveTo(Vector2.Clamp(newPos, Vector2.Zero, Parent.Size));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
|
{
|
||||||
|
areaOffset.Value = Position;
|
||||||
|
base.OnDragEnd(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,12 +5,16 @@ using System;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Rulesets.Edit;
|
using osu.Game.Rulesets.Edit;
|
||||||
|
using osu.Game.Screens.Edit.Compose.Components;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -21,16 +25,25 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
{
|
{
|
||||||
private Container box = null!;
|
private Container box = null!;
|
||||||
|
|
||||||
private Container outlineBox = null!;
|
|
||||||
|
|
||||||
private AnchorOriginVisualiser anchorOriginVisualiser = null!;
|
private AnchorOriginVisualiser anchorOriginVisualiser = null!;
|
||||||
|
|
||||||
|
private OsuSpriteText label = null!;
|
||||||
|
|
||||||
private Drawable drawable => (Drawable)Item;
|
private Drawable drawable => (Drawable)Item;
|
||||||
|
|
||||||
protected override bool ShouldBeAlive => drawable.IsAlive && Item.IsPresent;
|
protected override bool ShouldBeAlive => drawable.IsAlive && Item.IsPresent;
|
||||||
|
|
||||||
[Resolved]
|
private Quad drawableQuad;
|
||||||
private OsuColour colours { get; set; } = null!;
|
|
||||||
|
public override Quad ScreenSpaceDrawQuad => drawableQuad;
|
||||||
|
public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad;
|
||||||
|
|
||||||
|
public override bool Contains(Vector2 screenSpacePos) => drawableQuad.Contains(screenSpacePos);
|
||||||
|
|
||||||
|
public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(drawable.OriginPosition);
|
||||||
|
|
||||||
|
protected override bool ReceivePositionalInputAtSubTree(Vector2 screenSpacePos) =>
|
||||||
|
drawableQuad.Contains(screenSpacePos);
|
||||||
|
|
||||||
public SkinBlueprint(ISerialisableDrawable component)
|
public SkinBlueprint(ISerialisableDrawable component)
|
||||||
: base(component)
|
: base(component)
|
||||||
@ -38,7 +51,7 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load(OsuColour colours)
|
||||||
{
|
{
|
||||||
InternalChildren = new Drawable[]
|
InternalChildren = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -46,23 +59,26 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
{
|
{
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
outlineBox = new Container
|
new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
BorderThickness = 3,
|
CornerRadius = 3,
|
||||||
BorderColour = Color4.White,
|
BorderThickness = SelectionBox.BORDER_RADIUS / 2,
|
||||||
|
BorderColour = ColourInfo.GradientVertical(colours.Pink4.Darken(0.4f), colours.Pink4),
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
new Box
|
new Box
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Alpha = 0f,
|
Blending = BlendingParameters.Additive,
|
||||||
|
Alpha = 0.2f,
|
||||||
|
Colour = ColourInfo.GradientVertical(colours.Pink2, colours.Pink4),
|
||||||
AlwaysPresent = true,
|
AlwaysPresent = true,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
new OsuSpriteText
|
label = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Text = Item.GetType().Name,
|
Text = Item.GetType().Name,
|
||||||
Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold),
|
Font = OsuFont.Default.With(size: 10, weight: FontWeight.Bold),
|
||||||
@ -86,6 +102,18 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
this.FadeInFromZero(200, Easing.OutQuint);
|
this.FadeInFromZero(200, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
updateSelectedState();
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
updateSelectedState();
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnSelected()
|
protected override void OnSelected()
|
||||||
{
|
{
|
||||||
// base logic hides selected blueprints when not selected, but skin blueprints don't do that.
|
// base logic hides selected blueprints when not selected, but skin blueprints don't do that.
|
||||||
@ -100,73 +128,73 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
|
|
||||||
private void updateSelectedState()
|
private void updateSelectedState()
|
||||||
{
|
{
|
||||||
outlineBox.FadeColour(colours.Pink.Opacity(IsSelected ? 1 : 0.5f), 200, Easing.OutQuint);
|
|
||||||
outlineBox.Child.FadeTo(IsSelected ? 0.2f : 0, 200, Easing.OutQuint);
|
|
||||||
|
|
||||||
anchorOriginVisualiser.FadeTo(IsSelected ? 1 : 0, 200, Easing.OutQuint);
|
anchorOriginVisualiser.FadeTo(IsSelected ? 1 : 0, 200, Easing.OutQuint);
|
||||||
|
label.FadeTo(IsSelected || IsHovered ? 1 : 0, 200, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Quad drawableQuad;
|
|
||||||
|
|
||||||
public override Quad ScreenSpaceDrawQuad => drawableQuad;
|
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
|
|
||||||
drawableQuad = drawable.ScreenSpaceDrawQuad;
|
drawableQuad = drawable.ToScreenSpace(
|
||||||
var quad = ToLocalSpace(drawable.ScreenSpaceDrawQuad);
|
drawable.DrawRectangle
|
||||||
|
.Inflate(SkinSelectionHandler.INFLATE_SIZE));
|
||||||
|
|
||||||
box.Position = drawable.ToSpaceOfOtherDrawable(Vector2.Zero, this);
|
var localSpaceQuad = ToLocalSpace(drawableQuad);
|
||||||
box.Size = quad.Size;
|
|
||||||
|
box.Position = localSpaceQuad.TopLeft;
|
||||||
|
box.Size = localSpaceQuad.Size;
|
||||||
box.Rotation = drawable.Rotation;
|
box.Rotation = drawable.Rotation;
|
||||||
box.Scale = new Vector2(MathF.Sign(drawable.Scale.X), MathF.Sign(drawable.Scale.Y));
|
box.Scale = new Vector2(MathF.Sign(drawable.Scale.X), MathF.Sign(drawable.Scale.Y));
|
||||||
}
|
}
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => drawable.ReceivePositionalInputAt(screenSpacePos);
|
|
||||||
|
|
||||||
public override Vector2 ScreenSpaceSelectionPoint => drawable.ToScreenSpace(drawable.OriginPosition);
|
|
||||||
|
|
||||||
public override Quad SelectionQuad => drawable.ScreenSpaceDrawQuad;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal partial class AnchorOriginVisualiser : CompositeDrawable
|
internal partial class AnchorOriginVisualiser : CompositeDrawable
|
||||||
{
|
{
|
||||||
private readonly Drawable drawable;
|
private readonly Drawable drawable;
|
||||||
|
|
||||||
private readonly Box originBox;
|
private Drawable originBox = null!;
|
||||||
|
|
||||||
private readonly Box anchorBox;
|
private Drawable anchorBox = null!;
|
||||||
private readonly Box anchorLine;
|
private Drawable anchorLine = null!;
|
||||||
|
|
||||||
public AnchorOriginVisualiser(Drawable drawable)
|
public AnchorOriginVisualiser(Drawable drawable)
|
||||||
{
|
{
|
||||||
this.drawable = drawable;
|
this.drawable = drawable;
|
||||||
|
}
|
||||||
|
|
||||||
InternalChildren = new Drawable[]
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
Color4 anchorColour = colours.Red1;
|
||||||
|
Color4 originColour = colours.Red3;
|
||||||
|
|
||||||
|
InternalChildren = new[]
|
||||||
{
|
{
|
||||||
anchorLine = new Box
|
anchorLine = new Circle
|
||||||
{
|
{
|
||||||
Height = 2,
|
Height = 3f,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Colour = Color4.Yellow,
|
Colour = ColourInfo.GradientHorizontal(originColour.Opacity(0.5f), originColour),
|
||||||
EdgeSmoothness = Vector2.One
|
|
||||||
},
|
},
|
||||||
originBox = new Box
|
originBox = new Circle
|
||||||
{
|
{
|
||||||
Colour = Color4.Red,
|
Colour = originColour,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(5),
|
Size = new Vector2(7),
|
||||||
},
|
},
|
||||||
anchorBox = new Box
|
anchorBox = new Circle
|
||||||
{
|
{
|
||||||
Colour = Color4.Red,
|
Colour = anchorColour,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Size = new Vector2(5),
|
Size = new Vector2(10),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Vector2? anchorPosition;
|
||||||
|
private Vector2? originPositionInDrawableSpace;
|
||||||
|
|
||||||
protected override void Update()
|
protected override void Update()
|
||||||
{
|
{
|
||||||
base.Update();
|
base.Update();
|
||||||
@ -174,8 +202,13 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
if (drawable.Parent == null)
|
if (drawable.Parent == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
originBox.Position = drawable.ToSpaceOfOtherDrawable(drawable.OriginPosition, this);
|
var newAnchor = drawable.Parent.ToSpaceOfOtherDrawable(drawable.AnchorPosition, this);
|
||||||
anchorBox.Position = drawable.Parent.ToSpaceOfOtherDrawable(drawable.AnchorPosition, this);
|
anchorPosition = tweenPosition(anchorPosition ?? newAnchor, newAnchor);
|
||||||
|
anchorBox.Position = anchorPosition.Value;
|
||||||
|
|
||||||
|
// for the origin, tween in the drawable's local space to avoid unwanted tweening when the drawable is being dragged.
|
||||||
|
originPositionInDrawableSpace = originPositionInDrawableSpace != null ? tweenPosition(originPositionInDrawableSpace.Value, drawable.OriginPosition) : drawable.OriginPosition;
|
||||||
|
originBox.Position = drawable.ToSpaceOfOtherDrawable(originPositionInDrawableSpace.Value, this);
|
||||||
|
|
||||||
var point1 = ToLocalSpace(anchorBox.ScreenSpaceDrawQuad.Centre);
|
var point1 = ToLocalSpace(anchorBox.ScreenSpaceDrawQuad.Centre);
|
||||||
var point2 = ToLocalSpace(originBox.ScreenSpaceDrawQuad.Centre);
|
var point2 = ToLocalSpace(originBox.ScreenSpaceDrawQuad.Centre);
|
||||||
@ -184,5 +217,11 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
anchorLine.Width = (point2 - point1).Length;
|
anchorLine.Width = (point2 - point1).Length;
|
||||||
anchorLine.Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X));
|
anchorLine.Rotation = MathHelper.RadiansToDegrees(MathF.Atan2(point2.Y - point1.Y, point2.X - point1.X));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Vector2 tweenPosition(Vector2 oldPosition, Vector2 newPosition)
|
||||||
|
=> new Vector2(
|
||||||
|
(float)Interpolation.DampContinuously(oldPosition.X, newPosition.X, 25, Clock.ElapsedFrameTime),
|
||||||
|
(float)Interpolation.DampContinuously(oldPosition.Y, newPosition.Y, 25, Clock.ElapsedFrameTime)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,13 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Graphics;
|
using osu.Framework.Threading;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
@ -64,7 +65,8 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
|
|
||||||
fill.Add(new ToolboxComponentButton(instance, target)
|
fill.Add(new ToolboxComponentButton(instance, target)
|
||||||
{
|
{
|
||||||
RequestPlacement = t => RequestPlacement?.Invoke(t)
|
RequestPlacement = t => RequestPlacement?.Invoke(t),
|
||||||
|
Expanding = contractOtherButtons,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
catch (DependencyNotRegisteredException)
|
catch (DependencyNotRegisteredException)
|
||||||
@ -78,15 +80,29 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void contractOtherButtons(ToolboxComponentButton obj)
|
||||||
|
{
|
||||||
|
foreach (var b in fill.OfType<ToolboxComponentButton>())
|
||||||
|
{
|
||||||
|
if (b == obj)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
b.Contract();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public partial class ToolboxComponentButton : OsuButton
|
public partial class ToolboxComponentButton : OsuButton
|
||||||
{
|
{
|
||||||
public Action<Type>? RequestPlacement;
|
public Action<Type>? RequestPlacement;
|
||||||
|
public Action<ToolboxComponentButton>? Expanding;
|
||||||
|
|
||||||
private readonly Drawable component;
|
private readonly Drawable component;
|
||||||
private readonly CompositeDrawable? dependencySource;
|
private readonly CompositeDrawable? dependencySource;
|
||||||
|
|
||||||
private Container innerContainer = null!;
|
private Container innerContainer = null!;
|
||||||
|
|
||||||
|
private ScheduledDelegate? expandContractAction;
|
||||||
|
|
||||||
private const float contracted_size = 60;
|
private const float contracted_size = 60;
|
||||||
private const float expanded_size = 120;
|
private const float expanded_size = 120;
|
||||||
|
|
||||||
@ -101,20 +117,45 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
Height = contracted_size;
|
Height = contracted_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private const double animation_duration = 500;
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
{
|
{
|
||||||
this.Delay(300).ResizeHeightTo(expanded_size, 500, Easing.OutQuint);
|
expandContractAction?.Cancel();
|
||||||
|
expandContractAction = Scheduler.AddDelayed(() =>
|
||||||
|
{
|
||||||
|
this.ResizeHeightTo(expanded_size, animation_duration, Easing.OutQuint);
|
||||||
|
Expanding?.Invoke(this);
|
||||||
|
}, 100);
|
||||||
|
|
||||||
return base.OnHover(e);
|
return base.OnHover(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnHoverLost(HoverLostEvent e)
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
{
|
{
|
||||||
base.OnHoverLost(e);
|
base.OnHoverLost(e);
|
||||||
this.ResizeHeightTo(contracted_size, 500, Easing.OutQuint);
|
|
||||||
|
expandContractAction?.Cancel();
|
||||||
|
// If no other component is selected for too long, force a contract.
|
||||||
|
// Otherwise we will generally contract when Contract() is called from outside.
|
||||||
|
expandContractAction = Scheduler.AddDelayed(Contract, 1000);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Contract()
|
||||||
|
{
|
||||||
|
// Cheap debouncing to avoid stacking animations.
|
||||||
|
// The only place this is nulled is at the end of this method.
|
||||||
|
if (expandContractAction == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
this.ResizeHeightTo(contracted_size, animation_duration, Easing.OutQuint);
|
||||||
|
|
||||||
|
expandContractAction?.Cancel();
|
||||||
|
expandContractAction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider, OsuColour colours)
|
private void load(OverlayColourProvider colourProvider)
|
||||||
{
|
{
|
||||||
BackgroundColour = colourProvider.Background3;
|
BackgroundColour = colourProvider.Background3;
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ using osu.Framework.Input;
|
|||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Rulesets.Configuration;
|
using osu.Game.Rulesets.Configuration;
|
||||||
using osu.Game.Rulesets.Edit.Tools;
|
using osu.Game.Rulesets.Edit.Tools;
|
||||||
@ -70,6 +71,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
private FillFlowContainer togglesCollection;
|
private FillFlowContainer togglesCollection;
|
||||||
|
|
||||||
private IBindable<bool> hasTiming;
|
private IBindable<bool> hasTiming;
|
||||||
|
private Bindable<bool> autoSeekOnPlacement;
|
||||||
|
|
||||||
protected HitObjectComposer(Ruleset ruleset)
|
protected HitObjectComposer(Ruleset ruleset)
|
||||||
: base(ruleset)
|
: base(ruleset)
|
||||||
@ -80,8 +82,10 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider)
|
private void load(OverlayColourProvider colourProvider, OsuConfigManager config)
|
||||||
{
|
{
|
||||||
|
autoSeekOnPlacement = config.GetBindable<bool>(OsuSetting.EditorAutoSeekOnPlacement);
|
||||||
|
|
||||||
Config = Dependencies.Get<IRulesetConfigCache>().GetConfigFor(Ruleset);
|
Config = Dependencies.Get<IRulesetConfigCache>().GetConfigFor(Ruleset);
|
||||||
|
|
||||||
try
|
try
|
||||||
@ -365,7 +369,7 @@ namespace osu.Game.Rulesets.Edit
|
|||||||
{
|
{
|
||||||
EditorBeatmap.Add(hitObject);
|
EditorBeatmap.Add(hitObject);
|
||||||
|
|
||||||
if (EditorClock.CurrentTime < hitObject.StartTime)
|
if (autoSeekOnPlacement.Value && EditorClock.CurrentTime < hitObject.StartTime)
|
||||||
EditorClock.SeekSmoothlyTo(hitObject.StartTime);
|
EditorClock.SeekSmoothlyTo(hitObject.StartTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
@ -33,16 +34,30 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
public readonly Judgement Judgement;
|
public readonly Judgement Judgement;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The offset from a perfect hit at which this <see cref="JudgementResult"/> occurred.
|
/// The time at which this <see cref="JudgementResult"/> occurred.
|
||||||
/// Populated when this <see cref="JudgementResult"/> is applied via <see cref="DrawableHitObject.ApplyResult"/>.
|
/// Populated when this <see cref="JudgementResult"/> is applied via <see cref="DrawableHitObject.ApplyResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double TimeOffset { get; internal set; }
|
/// <remarks>
|
||||||
|
/// This is used instead of <see cref="TimeAbsolute"/> to check whether this <see cref="JudgementResult"/> should be reverted.
|
||||||
|
/// </remarks>
|
||||||
|
internal double? RawTime { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The absolute time at which this <see cref="JudgementResult"/> occurred.
|
/// The offset of <see cref="TimeAbsolute"/> from the end time of <see cref="HitObject"/>, clamped by <see cref="osu.Game.Rulesets.Objects.HitObject.MaximumJudgementOffset"/>.
|
||||||
/// Equal to the (end) time of the <see cref="HitObject"/> + <see cref="TimeOffset"/>.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public double TimeAbsolute => HitObject.GetEndTime() + TimeOffset;
|
public double TimeOffset
|
||||||
|
{
|
||||||
|
get => RawTime != null ? Math.Min(RawTime.Value - HitObject.GetEndTime(), HitObject.MaximumJudgementOffset) : 0;
|
||||||
|
internal set => RawTime = HitObject.GetEndTime() + value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The absolute time at which this <see cref="JudgementResult"/> occurred, clamped by the end time of <see cref="HitObject"/> plus <see cref="osu.Game.Rulesets.Objects.HitObject.MaximumJudgementOffset"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The end time of <see cref="HitObject"/> is returned if this result is not populated yet.
|
||||||
|
/// </remarks>
|
||||||
|
public double TimeAbsolute => RawTime != null ? Math.Min(RawTime.Value, HitObject.GetEndTime() + HitObject.MaximumJudgementOffset) : HitObject.GetEndTime();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The combo prior to this <see cref="JudgementResult"/> occurring.
|
/// The combo prior to this <see cref="JudgementResult"/> occurring.
|
||||||
@ -83,6 +98,13 @@ namespace osu.Game.Rulesets.Judgements
|
|||||||
{
|
{
|
||||||
HitObject = hitObject;
|
HitObject = hitObject;
|
||||||
Judgement = judgement;
|
Judgement = judgement;
|
||||||
|
Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void Reset()
|
||||||
|
{
|
||||||
|
Type = HitResult.None;
|
||||||
|
RawTime = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override string ToString() => $"{Type} (Score:{Judgement.NumericResultFor(this)} HP:{Judgement.HealthIncreaseFor(this)} {Judgement})";
|
public override string ToString() => $"{Type} (Score:{Judgement.NumericResultFor(this)} HP:{Judgement.HealthIncreaseFor(this)} {Judgement})";
|
||||||
|
@ -82,6 +82,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked by this or a nested <see cref="DrawableHitObject"/> prior to a <see cref="JudgementResult"/> being reverted.
|
/// Invoked by this or a nested <see cref="DrawableHitObject"/> prior to a <see cref="JudgementResult"/> being reverted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This is only invoked if this <see cref="DrawableHitObject"/> is alive when the result is reverted.
|
||||||
|
/// </remarks>
|
||||||
public event Action<DrawableHitObject, JudgementResult> OnRevertResult;
|
public event Action<DrawableHitObject, JudgementResult> OnRevertResult;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -222,6 +225,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
ensureEntryHasResult();
|
ensureEntryHasResult();
|
||||||
|
|
||||||
|
entry.RevertResult += onRevertResult;
|
||||||
|
|
||||||
foreach (var h in HitObject.NestedHitObjects)
|
foreach (var h in HitObject.NestedHitObjects)
|
||||||
{
|
{
|
||||||
var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h, this);
|
var pooledDrawableNested = pooledObjectProvider?.GetPooledDrawableRepresentation(h, this);
|
||||||
@ -234,7 +239,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
OnNestedDrawableCreated?.Invoke(drawableNested);
|
OnNestedDrawableCreated?.Invoke(drawableNested);
|
||||||
|
|
||||||
drawableNested.OnNewResult += onNewResult;
|
drawableNested.OnNewResult += onNewResult;
|
||||||
drawableNested.OnRevertResult += onRevertResult;
|
|
||||||
drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState;
|
drawableNested.ApplyCustomUpdateState += onApplyCustomUpdateState;
|
||||||
|
|
||||||
// This is only necessary for non-pooled DHOs. For pooled DHOs, this is handled inside GetPooledDrawableRepresentation().
|
// This is only necessary for non-pooled DHOs. For pooled DHOs, this is handled inside GetPooledDrawableRepresentation().
|
||||||
@ -308,7 +312,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
foreach (var obj in nestedHitObjects)
|
foreach (var obj in nestedHitObjects)
|
||||||
{
|
{
|
||||||
obj.OnNewResult -= onNewResult;
|
obj.OnNewResult -= onNewResult;
|
||||||
obj.OnRevertResult -= onRevertResult;
|
|
||||||
obj.ApplyCustomUpdateState -= onApplyCustomUpdateState;
|
obj.ApplyCustomUpdateState -= onApplyCustomUpdateState;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -317,6 +320,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
HitObject.DefaultsApplied -= onDefaultsApplied;
|
HitObject.DefaultsApplied -= onDefaultsApplied;
|
||||||
|
|
||||||
|
entry.RevertResult -= onRevertResult;
|
||||||
|
|
||||||
OnFree();
|
OnFree();
|
||||||
|
|
||||||
ParentHitObject = null;
|
ParentHitObject = null;
|
||||||
@ -365,7 +370,11 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
private void onNewResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnNewResult?.Invoke(drawableHitObject, result);
|
private void onNewResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnNewResult?.Invoke(drawableHitObject, result);
|
||||||
|
|
||||||
private void onRevertResult(DrawableHitObject drawableHitObject, JudgementResult result) => OnRevertResult?.Invoke(drawableHitObject, result);
|
private void onRevertResult()
|
||||||
|
{
|
||||||
|
updateState(ArmedState.Idle);
|
||||||
|
OnRevertResult?.Invoke(this, Result);
|
||||||
|
}
|
||||||
|
|
||||||
private void onApplyCustomUpdateState(DrawableHitObject drawableHitObject, ArmedState state) => ApplyCustomUpdateState?.Invoke(drawableHitObject, state);
|
private void onApplyCustomUpdateState(DrawableHitObject drawableHitObject, ArmedState state) => ApplyCustomUpdateState?.Invoke(drawableHitObject, state);
|
||||||
|
|
||||||
@ -577,26 +586,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
protected override void Update()
|
|
||||||
{
|
|
||||||
base.Update();
|
|
||||||
|
|
||||||
if (Result != null && Result.HasResult)
|
|
||||||
{
|
|
||||||
double endTime = HitObject.GetEndTime();
|
|
||||||
|
|
||||||
if (Result.TimeOffset + endTime > Time.Current)
|
|
||||||
{
|
|
||||||
OnRevertResult?.Invoke(this, Result);
|
|
||||||
|
|
||||||
Result.TimeOffset = 0;
|
|
||||||
Result.Type = HitResult.None;
|
|
||||||
|
|
||||||
updateState(ArmedState.Idle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false;
|
public override bool UpdateSubTreeMasking(Drawable source, RectangleF maskingBounds) => false;
|
||||||
|
|
||||||
protected override void UpdateAfterChildren()
|
protected override void UpdateAfterChildren()
|
||||||
@ -671,7 +660,7 @@ namespace osu.Game.Rulesets.Objects.Drawables
|
|||||||
$"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}]).");
|
$"{GetType().ReadableName()} applied an invalid hit result (was: {Result.Type}, expected: [{Result.Judgement.MinResult} ... {Result.Judgement.MaxResult}]).");
|
||||||
}
|
}
|
||||||
|
|
||||||
Result.TimeOffset = Math.Min(HitObject.MaximumJudgementOffset, Time.Current - HitObject.GetEndTime());
|
Result.RawTime = Time.Current;
|
||||||
|
|
||||||
if (Result.HasResult)
|
if (Result.HasResult)
|
||||||
updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss);
|
updateState(Result.IsHit ? ArmedState.Hit : ArmedState.Miss);
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics.Performance;
|
using osu.Framework.Graphics.Performance;
|
||||||
using osu.Game.Rulesets.Judgements;
|
using osu.Game.Rulesets.Judgements;
|
||||||
@ -26,6 +27,8 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
|
|
||||||
private readonly IBindable<double> startTimeBindable = new BindableDouble();
|
private readonly IBindable<double> startTimeBindable = new BindableDouble();
|
||||||
|
|
||||||
|
internal event Action? RevertResult;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="HitObjectLifetimeEntry"/>.
|
/// Creates a new <see cref="HitObjectLifetimeEntry"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -95,5 +98,7 @@ namespace osu.Game.Rulesets.Objects
|
|||||||
/// Set <see cref="LifetimeEntry.LifetimeStart"/> using <see cref="InitialLifetimeOffset"/>.
|
/// Set <see cref="LifetimeEntry.LifetimeStart"/> using <see cref="InitialLifetimeOffset"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal void SetInitialLifetime() => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
|
internal void SetInitialLifetime() => LifetimeStart = HitObject.StartTime - InitialLifetimeOffset;
|
||||||
|
|
||||||
|
internal void OnRevertResult() => RevertResult?.Invoke();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
playfield = new Lazy<Playfield>(() => CreatePlayfield().With(p =>
|
playfield = new Lazy<Playfield>(() => CreatePlayfield().With(p =>
|
||||||
{
|
{
|
||||||
p.NewResult += (_, r) => NewResult?.Invoke(r);
|
p.NewResult += (_, r) => NewResult?.Invoke(r);
|
||||||
p.RevertResult += (_, r) => RevertResult?.Invoke(r);
|
p.RevertResult += r => RevertResult?.Invoke(r);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,11 +28,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<DrawableHitObject, JudgementResult> NewResult;
|
public event Action<DrawableHitObject, JudgementResult> NewResult;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Invoked when a <see cref="DrawableHitObject"/> judgement is reverted.
|
|
||||||
/// </summary>
|
|
||||||
public event Action<DrawableHitObject, JudgementResult> RevertResult;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="HitObject"/> becomes used by a <see cref="DrawableHitObject"/>.
|
/// Invoked when a <see cref="HitObject"/> becomes used by a <see cref="DrawableHitObject"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -111,7 +106,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
private void addDrawable(DrawableHitObject drawable)
|
private void addDrawable(DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
drawable.OnNewResult += onNewResult;
|
drawable.OnNewResult += onNewResult;
|
||||||
drawable.OnRevertResult += onRevertResult;
|
|
||||||
|
|
||||||
bindStartTime(drawable);
|
bindStartTime(drawable);
|
||||||
AddInternal(drawable);
|
AddInternal(drawable);
|
||||||
@ -120,7 +114,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
private void removeDrawable(DrawableHitObject drawable)
|
private void removeDrawable(DrawableHitObject drawable)
|
||||||
{
|
{
|
||||||
drawable.OnNewResult -= onNewResult;
|
drawable.OnNewResult -= onNewResult;
|
||||||
drawable.OnRevertResult -= onRevertResult;
|
|
||||||
|
|
||||||
unbindStartTime(drawable);
|
unbindStartTime(drawable);
|
||||||
|
|
||||||
@ -154,7 +147,6 @@ namespace osu.Game.Rulesets.UI
|
|||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
private void onNewResult(DrawableHitObject d, JudgementResult r) => NewResult?.Invoke(d, r);
|
private void onNewResult(DrawableHitObject d, JudgementResult r) => NewResult?.Invoke(d, r);
|
||||||
private void onRevertResult(DrawableHitObject d, JudgementResult r) => RevertResult?.Invoke(d, r);
|
|
||||||
|
|
||||||
#region Comparator + StartTime tracking
|
#region Comparator + StartTime tracking
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ using osu.Game.Rulesets.Objects.Drawables;
|
|||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Rulesets.Objects.Pooling;
|
using osu.Game.Rulesets.Objects.Pooling;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.UI
|
namespace osu.Game.Rulesets.UI
|
||||||
{
|
{
|
||||||
@ -35,9 +36,9 @@ namespace osu.Game.Rulesets.UI
|
|||||||
public event Action<DrawableHitObject, JudgementResult> NewResult;
|
public event Action<DrawableHitObject, JudgementResult> NewResult;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a <see cref="DrawableHitObject"/> judgement is reverted.
|
/// Invoked when a judgement result is reverted.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public event Action<DrawableHitObject, JudgementResult> RevertResult;
|
public event Action<JudgementResult> RevertResult;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="DrawableHitObject"/> contained in this Playfield.
|
/// The <see cref="DrawableHitObject"/> contained in this Playfield.
|
||||||
@ -98,6 +99,8 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
private readonly HitObjectEntryManager entryManager = new HitObjectEntryManager();
|
private readonly HitObjectEntryManager entryManager = new HitObjectEntryManager();
|
||||||
|
|
||||||
|
private readonly Stack<HitObjectLifetimeEntry> judgedEntries;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="Playfield"/>.
|
/// Creates a new <see cref="Playfield"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -107,14 +110,15 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
hitObjectContainerLazy = new Lazy<HitObjectContainer>(() => CreateHitObjectContainer().With(h =>
|
hitObjectContainerLazy = new Lazy<HitObjectContainer>(() => CreateHitObjectContainer().With(h =>
|
||||||
{
|
{
|
||||||
h.NewResult += (d, r) => NewResult?.Invoke(d, r);
|
h.NewResult += onNewResult;
|
||||||
h.RevertResult += (d, r) => RevertResult?.Invoke(d, r);
|
|
||||||
h.HitObjectUsageBegan += o => HitObjectUsageBegan?.Invoke(o);
|
h.HitObjectUsageBegan += o => HitObjectUsageBegan?.Invoke(o);
|
||||||
h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o);
|
h.HitObjectUsageFinished += o => HitObjectUsageFinished?.Invoke(o);
|
||||||
}));
|
}));
|
||||||
|
|
||||||
entryManager.OnEntryAdded += onEntryAdded;
|
entryManager.OnEntryAdded += onEntryAdded;
|
||||||
entryManager.OnEntryRemoved += onEntryRemoved;
|
entryManager.OnEntryRemoved += onEntryRemoved;
|
||||||
|
|
||||||
|
judgedEntries = new Stack<HitObjectLifetimeEntry>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -224,7 +228,7 @@ namespace osu.Game.Rulesets.UI
|
|||||||
otherPlayfield.DisplayJudgements.BindTo(DisplayJudgements);
|
otherPlayfield.DisplayJudgements.BindTo(DisplayJudgements);
|
||||||
|
|
||||||
otherPlayfield.NewResult += (d, r) => NewResult?.Invoke(d, r);
|
otherPlayfield.NewResult += (d, r) => NewResult?.Invoke(d, r);
|
||||||
otherPlayfield.RevertResult += (d, r) => RevertResult?.Invoke(d, r);
|
otherPlayfield.RevertResult += r => RevertResult?.Invoke(r);
|
||||||
otherPlayfield.HitObjectUsageBegan += h => HitObjectUsageBegan?.Invoke(h);
|
otherPlayfield.HitObjectUsageBegan += h => HitObjectUsageBegan?.Invoke(h);
|
||||||
otherPlayfield.HitObjectUsageFinished += h => HitObjectUsageFinished?.Invoke(h);
|
otherPlayfield.HitObjectUsageFinished += h => HitObjectUsageFinished?.Invoke(h);
|
||||||
|
|
||||||
@ -252,6 +256,18 @@ namespace osu.Game.Rulesets.UI
|
|||||||
updatable.Update(this);
|
updatable.Update(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// When rewinding, revert future judgements in the reverse order.
|
||||||
|
while (judgedEntries.Count > 0)
|
||||||
|
{
|
||||||
|
var result = judgedEntries.Peek().Result;
|
||||||
|
Debug.Assert(result?.RawTime != null);
|
||||||
|
|
||||||
|
if (Time.Current >= result.RawTime.Value)
|
||||||
|
break;
|
||||||
|
|
||||||
|
revertResult(judgedEntries.Pop());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -443,6 +459,25 @@ namespace osu.Game.Rulesets.UI
|
|||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
|
private void onNewResult(DrawableHitObject drawable, JudgementResult result)
|
||||||
|
{
|
||||||
|
Debug.Assert(result != null && drawable.Entry?.Result == result && result.RawTime != null);
|
||||||
|
judgedEntries.Push(drawable.Entry.AsNonNull());
|
||||||
|
|
||||||
|
NewResult?.Invoke(drawable, result);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void revertResult(HitObjectLifetimeEntry entry)
|
||||||
|
{
|
||||||
|
var result = entry.Result;
|
||||||
|
Debug.Assert(result != null);
|
||||||
|
|
||||||
|
RevertResult?.Invoke(result);
|
||||||
|
entry.OnRevertResult();
|
||||||
|
|
||||||
|
result.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
#region Editor logic
|
#region Editor logic
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -32,6 +32,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract partial class SelectionHandler<T> : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IKeyBindingHandler<GlobalAction>, IHasContextMenu
|
public abstract partial class SelectionHandler<T> : CompositeDrawable, IKeyBindingHandler<PlatformAction>, IKeyBindingHandler<GlobalAction>, IHasContextMenu
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// How much padding around the selection area is added.
|
||||||
|
/// </summary>
|
||||||
|
public const float INFLATE_SIZE = 5;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The currently selected blueprints.
|
/// The currently selected blueprints.
|
||||||
/// Should be used when operations are dealing directly with the visible blueprints.
|
/// Should be used when operations are dealing directly with the visible blueprints.
|
||||||
@ -346,7 +351,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
|||||||
for (int i = 1; i < selectedBlueprints.Count; i++)
|
for (int i = 1; i < selectedBlueprints.Count; i++)
|
||||||
selectionRect = RectangleF.Union(selectionRect, ToLocalSpace(selectedBlueprints[i].SelectionQuad).AABBFloat);
|
selectionRect = RectangleF.Union(selectionRect, ToLocalSpace(selectedBlueprints[i].SelectionQuad).AABBFloat);
|
||||||
|
|
||||||
selectionRect = selectionRect.Inflate(5f);
|
selectionRect = selectionRect.Inflate(INFLATE_SIZE);
|
||||||
|
|
||||||
SelectionBox.Position = selectionRect.Location;
|
SelectionBox.Position = selectionRect.Location;
|
||||||
SelectionBox.Size = selectionRect.Size;
|
SelectionBox.Size = selectionRect.Size;
|
||||||
|
@ -185,6 +185,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
private Bindable<float> editorBackgroundDim;
|
private Bindable<float> editorBackgroundDim;
|
||||||
private Bindable<bool> editorHitMarkers;
|
private Bindable<bool> editorHitMarkers;
|
||||||
|
private Bindable<bool> editorAutoSeekOnPlacement;
|
||||||
|
|
||||||
public Editor(EditorLoader loader = null)
|
public Editor(EditorLoader loader = null)
|
||||||
{
|
{
|
||||||
@ -272,6 +273,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
|
|
||||||
editorBackgroundDim = config.GetBindable<float>(OsuSetting.EditorDim);
|
editorBackgroundDim = config.GetBindable<float>(OsuSetting.EditorDim);
|
||||||
editorHitMarkers = config.GetBindable<bool>(OsuSetting.EditorShowHitMarkers);
|
editorHitMarkers = config.GetBindable<bool>(OsuSetting.EditorShowHitMarkers);
|
||||||
|
editorAutoSeekOnPlacement = config.GetBindable<bool>(OsuSetting.EditorAutoSeekOnPlacement);
|
||||||
|
|
||||||
AddInternal(new OsuContextMenuContainer
|
AddInternal(new OsuContextMenuContainer
|
||||||
{
|
{
|
||||||
@ -329,6 +331,10 @@ namespace osu.Game.Screens.Edit
|
|||||||
new ToggleMenuItem(EditorStrings.ShowHitMarkers)
|
new ToggleMenuItem(EditorStrings.ShowHitMarkers)
|
||||||
{
|
{
|
||||||
State = { BindTarget = editorHitMarkers },
|
State = { BindTarget = editorHitMarkers },
|
||||||
|
},
|
||||||
|
new ToggleMenuItem(EditorStrings.AutoSeekOnPlacement)
|
||||||
|
{
|
||||||
|
State = { BindTarget = editorAutoSeekOnPlacement },
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using JetBrains.Annotations;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
using osu.Framework.Extensions.EnumExtensions;
|
||||||
@ -26,6 +27,7 @@ using osu.Game.Screens.Play.HUD.JudgementCounter;
|
|||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Game.Localisation;
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Play
|
namespace osu.Game.Screens.Play
|
||||||
{
|
{
|
||||||
@ -100,20 +102,22 @@ namespace osu.Game.Screens.Play
|
|||||||
|
|
||||||
public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods, bool alwaysShowLeaderboard = true)
|
public HUDOverlay(DrawableRuleset drawableRuleset, IReadOnlyList<Mod> mods, bool alwaysShowLeaderboard = true)
|
||||||
{
|
{
|
||||||
|
Drawable rulesetComponents;
|
||||||
|
|
||||||
this.drawableRuleset = drawableRuleset;
|
this.drawableRuleset = drawableRuleset;
|
||||||
this.mods = mods;
|
this.mods = mods;
|
||||||
|
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
|
|
||||||
Children = new Drawable[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
CreateFailingLayer(),
|
CreateFailingLayer(),
|
||||||
//Needs to be initialized before skinnable drawables.
|
//Needs to be initialized before skinnable drawables.
|
||||||
tally = new JudgementTally(),
|
tally = new JudgementTally(),
|
||||||
mainComponents = new MainComponentsContainer
|
mainComponents = new HUDComponentsContainer { AlwaysPresent = true, },
|
||||||
{
|
rulesetComponents = drawableRuleset != null
|
||||||
AlwaysPresent = true,
|
? new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, }
|
||||||
},
|
: Empty(),
|
||||||
topRightElements = new FillFlowContainer
|
topRightElements = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
@ -155,7 +159,7 @@ namespace osu.Game.Screens.Play
|
|||||||
clicksPerSecondCalculator = new ClicksPerSecondCalculator(),
|
clicksPerSecondCalculator = new ClicksPerSecondCalculator(),
|
||||||
};
|
};
|
||||||
|
|
||||||
hideTargets = new List<Drawable> { mainComponents, KeyCounter, topRightElements };
|
hideTargets = new List<Drawable> { mainComponents, rulesetComponents, KeyCounter, topRightElements };
|
||||||
|
|
||||||
if (!alwaysShowLeaderboard)
|
if (!alwaysShowLeaderboard)
|
||||||
hideTargets.Add(LeaderboardFlow);
|
hideTargets.Add(LeaderboardFlow);
|
||||||
@ -390,15 +394,15 @@ namespace osu.Game.Screens.Play
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class MainComponentsContainer : SkinComponentsContainer
|
private partial class HUDComponentsContainer : SkinComponentsContainer
|
||||||
{
|
{
|
||||||
private Bindable<ScoringMode> scoringMode;
|
private Bindable<ScoringMode> scoringMode;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private OsuConfigManager config { get; set; }
|
private OsuConfigManager config { get; set; }
|
||||||
|
|
||||||
public MainComponentsContainer()
|
public HUDComponentsContainer([CanBeNull] RulesetInfo ruleset = null)
|
||||||
: base(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.MainHUDComponents))
|
: base(new SkinComponentsContainerLookup(SkinComponentsContainerLookup.TargetArea.MainHUDComponents, ruleset))
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
@ -91,6 +91,10 @@ namespace osu.Game.Skinning
|
|||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup containerLookup:
|
case SkinComponentsContainerLookup containerLookup:
|
||||||
|
// Only handle global level defaults for now.
|
||||||
|
if (containerLookup.Ruleset != null)
|
||||||
|
return null;
|
||||||
|
|
||||||
switch (containerLookup.Target)
|
switch (containerLookup.Target)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup.TargetArea.SongSelect:
|
case SkinComponentsContainerLookup.TargetArea.SongSelect:
|
||||||
|
@ -344,10 +344,14 @@ namespace osu.Game.Skinning
|
|||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup containerLookup:
|
case SkinComponentsContainerLookup containerLookup:
|
||||||
|
// Only handle global level defaults for now.
|
||||||
|
if (containerLookup.Ruleset != null)
|
||||||
|
return null;
|
||||||
|
|
||||||
switch (containerLookup.Target)
|
switch (containerLookup.Target)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
case SkinComponentsContainerLookup.TargetArea.MainHUDComponents:
|
||||||
var skinnableTargetWrapper = new DefaultSkinComponentsContainer(container =>
|
return new DefaultSkinComponentsContainer(container =>
|
||||||
{
|
{
|
||||||
var score = container.OfType<LegacyScoreCounter>().FirstOrDefault();
|
var score = container.OfType<LegacyScoreCounter>().FirstOrDefault();
|
||||||
var accuracy = container.OfType<GameplayAccuracyCounter>().FirstOrDefault();
|
var accuracy = container.OfType<GameplayAccuracyCounter>().FirstOrDefault();
|
||||||
@ -387,8 +391,6 @@ namespace osu.Game.Skinning
|
|||||||
new BarHitErrorMeter(),
|
new BarHitErrorMeter(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return skinnableTargetWrapper;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Logging;
|
using osu.Framework.Logging;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Extensions;
|
using osu.Game.Extensions;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
@ -100,13 +101,18 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Type[] GetAllAvailableDrawables()
|
/// <summary>
|
||||||
|
/// Retrieve all types available which support serialisation.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="ruleset">The ruleset to filter results to. If <c>null</c>, global components will be returned instead.</param>
|
||||||
|
public static Type[] GetAllAvailableDrawables(RulesetInfo? ruleset = null)
|
||||||
{
|
{
|
||||||
return typeof(OsuGame).Assembly.GetTypes()
|
return (ruleset?.CreateInstance().GetType() ?? typeof(OsuGame))
|
||||||
.Where(t => !t.IsInterface && !t.IsAbstract)
|
.Assembly.GetTypes()
|
||||||
.Where(t => typeof(ISerialisableDrawable).IsAssignableFrom(t))
|
.Where(t => !t.IsInterface && !t.IsAbstract && t.IsPublic)
|
||||||
.OrderBy(t => t.Name)
|
.Where(t => typeof(ISerialisableDrawable).IsAssignableFrom(t))
|
||||||
.ToArray();
|
.OrderBy(t => t.Name)
|
||||||
|
.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,9 +37,10 @@ namespace osu.Game.Skinning
|
|||||||
|
|
||||||
public SkinConfiguration Configuration { get; set; }
|
public SkinConfiguration Configuration { get; set; }
|
||||||
|
|
||||||
public IDictionary<SkinComponentsContainerLookup.TargetArea, SerialisedDrawableInfo[]> DrawableComponentInfo => drawableComponentInfo;
|
public IDictionary<SkinComponentsContainerLookup.TargetArea, SkinLayoutInfo> LayoutInfos => layoutInfos;
|
||||||
|
|
||||||
private readonly Dictionary<SkinComponentsContainerLookup.TargetArea, SerialisedDrawableInfo[]> drawableComponentInfo = new Dictionary<SkinComponentsContainerLookup.TargetArea, SerialisedDrawableInfo[]>();
|
private readonly Dictionary<SkinComponentsContainerLookup.TargetArea, SkinLayoutInfo> layoutInfos =
|
||||||
|
new Dictionary<SkinComponentsContainerLookup.TargetArea, SkinLayoutInfo>();
|
||||||
|
|
||||||
public abstract ISample? GetSample(ISampleInfo sampleInfo);
|
public abstract ISample? GetSample(ISampleInfo sampleInfo);
|
||||||
|
|
||||||
@ -113,18 +114,41 @@ namespace osu.Game.Skinning
|
|||||||
{
|
{
|
||||||
string jsonContent = Encoding.UTF8.GetString(bytes);
|
string jsonContent = Encoding.UTF8.GetString(bytes);
|
||||||
|
|
||||||
// handle namespace changes...
|
SkinLayoutInfo? layoutInfo = null;
|
||||||
|
|
||||||
// can be removed 2023-01-31
|
try
|
||||||
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress");
|
{
|
||||||
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter");
|
// First attempt to deserialise using the new SkinLayoutInfo format
|
||||||
|
layoutInfo = JsonConvert.DeserializeObject<SkinLayoutInfo>(jsonContent);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
var deserializedContent = JsonConvert.DeserializeObject<IEnumerable<SerialisedDrawableInfo>>(jsonContent);
|
// Of note, the migration code below runs on read of skins, but there's nothing to
|
||||||
|
// force a rewrite after migration. Let's not remove these migration rules until we
|
||||||
|
// have something in place to ensure we don't end up breaking skins of users that haven't
|
||||||
|
// manually saved their skin since a change was implemented.
|
||||||
|
|
||||||
if (deserializedContent == null)
|
// If deserialisation using SkinLayoutInfo fails, attempt to deserialise using the old naked list.
|
||||||
continue;
|
if (layoutInfo == null)
|
||||||
|
{
|
||||||
|
// handle namespace changes...
|
||||||
|
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.SongProgress", @"osu.Game.Screens.Play.HUD.DefaultSongProgress");
|
||||||
|
jsonContent = jsonContent.Replace(@"osu.Game.Screens.Play.HUD.LegacyComboCounter", @"osu.Game.Skinning.LegacyComboCounter");
|
||||||
|
|
||||||
DrawableComponentInfo[skinnableTarget] = deserializedContent.ToArray();
|
var deserializedContent = JsonConvert.DeserializeObject<IEnumerable<SerialisedDrawableInfo>>(jsonContent);
|
||||||
|
|
||||||
|
if (deserializedContent == null)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
layoutInfo = new SkinLayoutInfo();
|
||||||
|
layoutInfo.Update(null, deserializedContent.ToArray());
|
||||||
|
|
||||||
|
Logger.Log($"Ferrying {deserializedContent.Count()} components in {skinnableTarget} to global section of new {nameof(SkinLayoutInfo)} format");
|
||||||
|
}
|
||||||
|
|
||||||
|
LayoutInfos[skinnableTarget] = layoutInfo;
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@ -145,7 +169,7 @@ namespace osu.Game.Skinning
|
|||||||
/// <param name="targetContainer">The target container to reset.</param>
|
/// <param name="targetContainer">The target container to reset.</param>
|
||||||
public void ResetDrawableTarget(SkinComponentsContainer targetContainer)
|
public void ResetDrawableTarget(SkinComponentsContainer targetContainer)
|
||||||
{
|
{
|
||||||
DrawableComponentInfo.Remove(targetContainer.Lookup.Target);
|
LayoutInfos.Remove(targetContainer.Lookup.Target);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -154,7 +178,10 @@ namespace osu.Game.Skinning
|
|||||||
/// <param name="targetContainer">The target container to serialise to this skin.</param>
|
/// <param name="targetContainer">The target container to serialise to this skin.</param>
|
||||||
public void UpdateDrawableTarget(SkinComponentsContainer targetContainer)
|
public void UpdateDrawableTarget(SkinComponentsContainer targetContainer)
|
||||||
{
|
{
|
||||||
DrawableComponentInfo[targetContainer.Lookup.Target] = ((ISerialisableDrawableContainer)targetContainer).CreateSerialisedInfo().ToArray();
|
if (!LayoutInfos.TryGetValue(targetContainer.Lookup.Target, out var layoutInfo))
|
||||||
|
layoutInfos[targetContainer.Lookup.Target] = layoutInfo = new SkinLayoutInfo();
|
||||||
|
|
||||||
|
layoutInfo.Update(targetContainer.Lookup.Ruleset, ((ISerialisableDrawableContainer)targetContainer).CreateSerialisedInfo().ToArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
public virtual Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
public virtual Drawable? GetDrawableComponent(ISkinComponentLookup lookup)
|
||||||
@ -166,18 +193,16 @@ namespace osu.Game.Skinning
|
|||||||
return this.GetAnimation(sprite.LookupName, false, false);
|
return this.GetAnimation(sprite.LookupName, false, false);
|
||||||
|
|
||||||
case SkinComponentsContainerLookup containerLookup:
|
case SkinComponentsContainerLookup containerLookup:
|
||||||
if (!DrawableComponentInfo.TryGetValue(containerLookup.Target, out var skinnableInfo))
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var components = new List<Drawable>();
|
// It is important to return null if the user has not configured this yet.
|
||||||
|
// This allows skin transformers the opportunity to provide default components.
|
||||||
foreach (var i in skinnableInfo)
|
if (!LayoutInfos.TryGetValue(containerLookup.Target, out var layoutInfo)) return null;
|
||||||
components.Add(i.CreateInstance());
|
if (!layoutInfo.TryGetDrawableInfo(containerLookup.Ruleset, out var drawableInfos)) return null;
|
||||||
|
|
||||||
return new Container
|
return new Container
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Children = components,
|
ChildrenEnumerable = drawableInfos.Select(i => i.CreateInstance())
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -66,25 +66,20 @@ namespace osu.Game.Skinning
|
|||||||
components.Clear();
|
components.Clear();
|
||||||
ComponentsLoaded = false;
|
ComponentsLoaded = false;
|
||||||
|
|
||||||
if (componentsContainer == null)
|
content = componentsContainer ?? new Container
|
||||||
return;
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both
|
||||||
content = componentsContainer;
|
};
|
||||||
|
|
||||||
cancellationSource?.Cancel();
|
cancellationSource?.Cancel();
|
||||||
cancellationSource = null;
|
cancellationSource = null;
|
||||||
|
|
||||||
if (content != null)
|
LoadComponentAsync(content, wrapper =>
|
||||||
{
|
{
|
||||||
LoadComponentAsync(content, wrapper =>
|
AddInternal(wrapper);
|
||||||
{
|
components.AddRange(wrapper.Children.OfType<ISerialisableDrawable>());
|
||||||
AddInternal(wrapper);
|
|
||||||
components.AddRange(wrapper.Children.OfType<ISerialisableDrawable>());
|
|
||||||
ComponentsLoaded = true;
|
|
||||||
}, (cancellationSource = new CancellationTokenSource()).Token);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
ComponentsLoaded = true;
|
ComponentsLoaded = true;
|
||||||
|
}, (cancellationSource = new CancellationTokenSource()).Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="ISerialisableDrawableContainer"/>
|
/// <inheritdoc cref="ISerialisableDrawableContainer"/>
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
|
||||||
namespace osu.Game.Skinning
|
namespace osu.Game.Skinning
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -13,9 +15,16 @@ namespace osu.Game.Skinning
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public readonly TargetArea Target;
|
public readonly TargetArea Target;
|
||||||
|
|
||||||
public SkinComponentsContainerLookup(TargetArea target)
|
/// <summary>
|
||||||
|
/// The ruleset for which skin components should be returned.
|
||||||
|
/// A <see langword="null"/> value means that returned components are global and should be applied for all rulesets.
|
||||||
|
/// </summary>
|
||||||
|
public readonly RulesetInfo? Ruleset;
|
||||||
|
|
||||||
|
public SkinComponentsContainerLookup(TargetArea target, RulesetInfo? ruleset = null)
|
||||||
{
|
{
|
||||||
Target = target;
|
Target = target;
|
||||||
|
Ruleset = ruleset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
@ -201,7 +201,7 @@ namespace osu.Game.Skinning
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Then serialise each of the drawable component groups into respective files.
|
// Then serialise each of the drawable component groups into respective files.
|
||||||
foreach (var drawableInfo in skin.DrawableComponentInfo)
|
foreach (var drawableInfo in skin.LayoutInfos)
|
||||||
{
|
{
|
||||||
string json = JsonConvert.SerializeObject(drawableInfo.Value, new JsonSerializerSettings { Formatting = Formatting.Indented });
|
string json = JsonConvert.SerializeObject(drawableInfo.Value, new JsonSerializerSettings { Formatting = Formatting.Indented });
|
||||||
|
|
||||||
|
37
osu.Game/Skinning/SkinLayoutInfo.cs
Normal file
37
osu.Game/Skinning/SkinLayoutInfo.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// 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.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Linq;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using osu.Game.Rulesets;
|
||||||
|
|
||||||
|
namespace osu.Game.Skinning
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A serialisable model describing layout of a <see cref="SkinComponentsContainer"/>.
|
||||||
|
/// May contain multiple configurations for different rulesets, each of which should manifest their own <see cref="SkinComponentsContainer"/> as required.
|
||||||
|
/// </summary>
|
||||||
|
[Serializable]
|
||||||
|
public class SkinLayoutInfo
|
||||||
|
{
|
||||||
|
private const string global_identifier = @"global";
|
||||||
|
|
||||||
|
[JsonIgnore]
|
||||||
|
public IEnumerable<SerialisedDrawableInfo> AllDrawables => DrawableInfo.Values.SelectMany(v => v);
|
||||||
|
|
||||||
|
[JsonProperty]
|
||||||
|
public Dictionary<string, SerialisedDrawableInfo[]> DrawableInfo { get; set; } = new Dictionary<string, SerialisedDrawableInfo[]>();
|
||||||
|
|
||||||
|
public bool TryGetDrawableInfo(RulesetInfo? ruleset, [NotNullWhen(true)] out SerialisedDrawableInfo[]? components) =>
|
||||||
|
DrawableInfo.TryGetValue(ruleset?.ShortName ?? global_identifier, out components);
|
||||||
|
|
||||||
|
public void Reset(RulesetInfo? ruleset) =>
|
||||||
|
DrawableInfo.Remove(ruleset?.ShortName ?? global_identifier);
|
||||||
|
|
||||||
|
public void Update(RulesetInfo? ruleset, SerialisedDrawableInfo[] components) =>
|
||||||
|
DrawableInfo[ruleset?.ShortName ?? global_identifier] = components;
|
||||||
|
}
|
||||||
|
}
|
@ -69,6 +69,10 @@ namespace osu.Game.Skinning
|
|||||||
switch (lookup)
|
switch (lookup)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup containerLookup:
|
case SkinComponentsContainerLookup containerLookup:
|
||||||
|
// Only handle global level defaults for now.
|
||||||
|
if (containerLookup.Ruleset != null)
|
||||||
|
return null;
|
||||||
|
|
||||||
switch (containerLookup.Target)
|
switch (containerLookup.Target)
|
||||||
{
|
{
|
||||||
case SkinComponentsContainerLookup.TargetArea.SongSelect:
|
case SkinComponentsContainerLookup.TargetArea.SongSelect:
|
||||||
|
Loading…
Reference in New Issue
Block a user