mirror of
https://github.com/ppy/osu.git
synced 2025-01-28 04:02:57 +08:00
Merge pull request #23926 from ItsShamed/hud/kc-skinnable
Make key counter skinnable
This commit is contained in:
commit
eb6bdb5a38
@ -49,6 +49,9 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
|
||||
AddStep("Create tests", () =>
|
||||
{
|
||||
InputTrigger triggerLeft;
|
||||
InputTrigger triggerRight;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
osuInputManager = new OsuInputManager(new OsuRuleset().RulesetInfo)
|
||||
@ -59,29 +62,39 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
Origin = Anchor.Centre,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
leftKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.LeftButton))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreRight,
|
||||
Depth = float.MinValue,
|
||||
X = -100,
|
||||
},
|
||||
rightKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.RightButton))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Depth = float.MinValue,
|
||||
X = 100,
|
||||
},
|
||||
new OsuCursorContainer
|
||||
{
|
||||
Depth = float.MinValue,
|
||||
},
|
||||
triggerLeft = new TestActionKeyCounterTrigger(OsuAction.LeftButton)
|
||||
{
|
||||
Depth = float.MinValue
|
||||
},
|
||||
triggerRight = new TestActionKeyCounterTrigger(OsuAction.RightButton)
|
||||
{
|
||||
Depth = float.MinValue
|
||||
}
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
new TouchVisualiser(),
|
||||
};
|
||||
|
||||
mainContent.AddRange(new[]
|
||||
{
|
||||
leftKeyCounter = new DefaultKeyCounter(triggerLeft)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreRight,
|
||||
X = -100,
|
||||
},
|
||||
rightKeyCounter = new DefaultKeyCounter(triggerRight)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
X = 100,
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
Binary file not shown.
@ -51,6 +51,8 @@ namespace osu.Game.Tests.Skins
|
||||
"Archives/modified-default-20230117.osk",
|
||||
// Covers player avatar and flag.
|
||||
"Archives/modified-argon-20230305.osk",
|
||||
// Covers key counters
|
||||
"Archives/modified-argon-pro-20230618.osk"
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
@ -35,14 +35,14 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
|
||||
|
||||
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
|
||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 2));
|
||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.InputCountController.Triggers.Any(kc => kc.ActivationCount.Value > 2));
|
||||
|
||||
seekTo(referenceBeatmap.Breaks[0].StartTime);
|
||||
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting.Value);
|
||||
AddAssert("keys not counting", () => !Player.HUDOverlay.InputCountController.IsCounting.Value);
|
||||
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1);
|
||||
|
||||
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
|
||||
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0));
|
||||
AddUntilStep("key counter reset", () => Player.HUDOverlay.InputCountController.Triggers.All(kc => kc.ActivationCount.Value == 0));
|
||||
|
||||
seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
|
||||
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);
|
||||
|
@ -18,6 +18,7 @@ using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Skinning.Legacy;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Storyboards;
|
||||
|
||||
@ -77,7 +78,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
(typeof(ScoreProcessor), actualComponentsContainer.Dependencies.Get<ScoreProcessor>()),
|
||||
(typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get<HealthProcessor>()),
|
||||
(typeof(GameplayState), actualComponentsContainer.Dependencies.Get<GameplayState>()),
|
||||
(typeof(IGameplayClock), actualComponentsContainer.Dependencies.Get<IGameplayClock>())
|
||||
(typeof(IGameplayClock), actualComponentsContainer.Dependencies.Get<IGameplayClock>()),
|
||||
(typeof(InputCountController), actualComponentsContainer.Dependencies.Get<InputCountController>())
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public partial class TestSceneClicksPerSecondCalculator : OsuTestScene
|
||||
{
|
||||
private ClicksPerSecondCalculator calculator = null!;
|
||||
private ClicksPerSecondController controller = null!;
|
||||
|
||||
private TestGameplayClock manualGameplayClock = null!;
|
||||
|
||||
@ -34,11 +34,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
CachedDependencies = new (Type, object)[] { (typeof(IGameplayClock), manualGameplayClock) },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
calculator = new ClicksPerSecondCalculator(),
|
||||
controller = new ClicksPerSecondController(),
|
||||
new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) },
|
||||
CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondController), controller) },
|
||||
Child = new ClicksPerSecondCounter
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
checkClicksPerSecondValue(6);
|
||||
}
|
||||
|
||||
private void checkClicksPerSecondValue(int i) => AddAssert("clicks/s is correct", () => calculator.Value, () => Is.EqualTo(i));
|
||||
private void checkClicksPerSecondValue(int i) => AddAssert("clicks/s is correct", () => controller.Value, () => Is.EqualTo(i));
|
||||
|
||||
private void seekClockImmediately(double time) => manualGameplayClock.CurrentTime = time;
|
||||
|
||||
@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
foreach (double timestamp in inputs)
|
||||
{
|
||||
seekClockImmediately(timestamp);
|
||||
calculator.AddInputTimestamp();
|
||||
controller.AddInputTimestamp();
|
||||
}
|
||||
|
||||
seekClockImmediately(baseTime);
|
||||
|
@ -7,8 +7,8 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
@ -31,11 +31,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
|
||||
addSeekStep(3000);
|
||||
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
|
||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Select(kc => kc.CountPresses.Value).Sum() == 15);
|
||||
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.InputCountController.Triggers.Select(kc => kc.ActivationCount.Value).Sum() == 15);
|
||||
AddStep("clear results", () => Player.Results.Clear());
|
||||
addSeekStep(0);
|
||||
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
|
||||
AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0));
|
||||
AddUntilStep("key counters reset", () => Player.HUDOverlay.InputCountController.Triggers.All(kc => kc.ActivationCount.Value == 0));
|
||||
AddAssert("no results triggered", () => Player.Results.Count == 0);
|
||||
}
|
||||
|
||||
|
@ -44,8 +44,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock());
|
||||
|
||||
// best way to check without exposing.
|
||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
||||
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
|
||||
private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
|
||||
|
||||
AddAssert("hidetarget is visible", () => hideTarget.IsPresent);
|
||||
AddAssert("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
|
||||
AddAssert("key counter flow is visible", () => keyCounterFlow.IsPresent);
|
||||
AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent);
|
||||
}
|
||||
@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||
|
||||
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
|
||||
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
|
||||
|
||||
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
|
||||
@ -109,13 +109,13 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
|
||||
|
||||
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
|
||||
AddUntilStep("wait for fade", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||
|
||||
AddStep("trigger momentary show", () => InputManager.PressKey(Key.ControlLeft));
|
||||
AddUntilStep("wait for visible", () => hideTarget.IsPresent);
|
||||
AddUntilStep("wait for visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
|
||||
|
||||
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
|
||||
AddUntilStep("wait for fade", () => !hideTarget.IsPresent);
|
||||
AddUntilStep("wait for fade", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -138,16 +138,18 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddStep("hide key overlay", () =>
|
||||
{
|
||||
localConfig.SetValue(OsuSetting.KeyOverlay, false);
|
||||
hudOverlay.KeyCounter.AlwaysVisible.Value = false;
|
||||
var kcd = hudOverlay.ChildrenOfType<KeyCounterDisplay>().FirstOrDefault();
|
||||
if (kcd != null)
|
||||
kcd.AlwaysVisible.Value = false;
|
||||
});
|
||||
|
||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
|
||||
AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent);
|
||||
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||
AddUntilStep("key counters hidden", () => !keyCounterFlow.IsPresent);
|
||||
|
||||
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
|
||||
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent);
|
||||
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent);
|
||||
AddUntilStep("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
|
||||
AddUntilStep("key counters still hidden", () => !keyCounterFlow.IsPresent);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@ -169,7 +171,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
});
|
||||
|
||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
|
||||
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||
|
||||
AddStep("attempt activate", () =>
|
||||
{
|
||||
@ -209,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
});
|
||||
|
||||
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
|
||||
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
|
||||
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||
|
||||
AddStep("attempt seek", () =>
|
||||
{
|
||||
@ -234,7 +236,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
createNew();
|
||||
|
||||
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
|
||||
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0);
|
||||
AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded));
|
||||
|
||||
@ -253,7 +254,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
createNew();
|
||||
|
||||
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
|
||||
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0);
|
||||
|
||||
AddStep("reload components", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Reload());
|
||||
@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||
|
||||
// Add any key just to display the key counter visually.
|
||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||
hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||
|
||||
scoreProcessor.Combo.Value = 1;
|
||||
|
||||
@ -275,6 +275,9 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
Child = hudOverlay;
|
||||
});
|
||||
|
||||
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
|
||||
AddUntilStep("wait for components present", () => hudOverlay.ChildrenOfType<KeyCounterDisplay>().FirstOrDefault() != null);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public partial class TestSceneJudgementCounter : OsuTestScene
|
||||
{
|
||||
private ScoreProcessor scoreProcessor = null!;
|
||||
private JudgementTally judgementTally = null!;
|
||||
private JudgementCountController judgementCountController = null!;
|
||||
private TestJudgementCounterDisplay counterDisplay = null!;
|
||||
|
||||
private DependencyProvidingContainer content = null!;
|
||||
@ -47,11 +47,11 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
CachedDependencies = new (Type, object)[] { (typeof(ScoreProcessor), scoreProcessor), (typeof(Ruleset), ruleset) },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
judgementTally = new JudgementTally(),
|
||||
judgementCountController = new JudgementCountController(),
|
||||
content = new DependencyProvidingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
CachedDependencies = new (Type, object)[] { (typeof(JudgementTally), judgementTally) },
|
||||
CachedDependencies = new (Type, object)[] { (typeof(JudgementCountController), judgementCountController) },
|
||||
}
|
||||
},
|
||||
};
|
||||
|
@ -3,7 +3,9 @@
|
||||
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
@ -15,63 +17,66 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[TestFixture]
|
||||
public partial class TestSceneKeyCounter : OsuManualInputManagerTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly InputCountController controller;
|
||||
|
||||
public TestSceneKeyCounter()
|
||||
{
|
||||
KeyCounterDisplay defaultDisplay = new DefaultKeyCounterDisplay
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Position = new Vector2(0, 72.7f)
|
||||
controller = new InputCountController(),
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(72.7f),
|
||||
Children = new KeyCounterDisplay[]
|
||||
{
|
||||
new DefaultKeyCounterDisplay
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
},
|
||||
new ArgonKeyCounterDisplay
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
KeyCounterDisplay argonDisplay = new ArgonKeyCounterDisplay
|
||||
{
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Position = new Vector2(0, -72.7f)
|
||||
};
|
||||
|
||||
defaultDisplay.AddRange(new InputTrigger[]
|
||||
var inputTriggers = new InputTrigger[]
|
||||
{
|
||||
new KeyCounterKeyboardTrigger(Key.X),
|
||||
new KeyCounterKeyboardTrigger(Key.X),
|
||||
new KeyCounterMouseTrigger(MouseButton.Left),
|
||||
new KeyCounterMouseTrigger(MouseButton.Right),
|
||||
});
|
||||
};
|
||||
|
||||
argonDisplay.AddRange(new InputTrigger[]
|
||||
{
|
||||
new KeyCounterKeyboardTrigger(Key.X),
|
||||
new KeyCounterKeyboardTrigger(Key.X),
|
||||
new KeyCounterMouseTrigger(MouseButton.Left),
|
||||
new KeyCounterMouseTrigger(MouseButton.Right),
|
||||
});
|
||||
|
||||
var testCounter = (DefaultKeyCounter)defaultDisplay.Counters.First();
|
||||
AddRange(inputTriggers);
|
||||
controller.AddRange(inputTriggers);
|
||||
|
||||
AddStep("Add random", () =>
|
||||
{
|
||||
Key key = (Key)((int)Key.A + RNG.Next(26));
|
||||
defaultDisplay.Add(new KeyCounterKeyboardTrigger(key));
|
||||
argonDisplay.Add(new KeyCounterKeyboardTrigger(key));
|
||||
var trigger = new KeyCounterKeyboardTrigger(key);
|
||||
Add(trigger);
|
||||
controller.Add(trigger);
|
||||
});
|
||||
|
||||
Key testKey = ((KeyCounterKeyboardTrigger)defaultDisplay.Counters.First().Trigger).Key;
|
||||
InputTrigger testTrigger = controller.Triggers.First();
|
||||
Key testKey = ((KeyCounterKeyboardTrigger)testTrigger).Key;
|
||||
|
||||
addPressKeyStep();
|
||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1);
|
||||
AddAssert($"Check {testKey} counter after keypress", () => testTrigger.ActivationCount.Value == 1);
|
||||
addPressKeyStep();
|
||||
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2);
|
||||
AddStep("Disable counting", () =>
|
||||
{
|
||||
argonDisplay.IsCounting.Value = false;
|
||||
defaultDisplay.IsCounting.Value = false;
|
||||
});
|
||||
AddAssert($"Check {testKey} counter after keypress", () => testTrigger.ActivationCount.Value == 2);
|
||||
AddStep("Disable counting", () => controller.IsCounting.Value = false);
|
||||
addPressKeyStep();
|
||||
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2);
|
||||
|
||||
Add(defaultDisplay);
|
||||
Add(argonDisplay);
|
||||
AddAssert($"Check {testKey} count has not changed", () => testTrigger.ActivationCount.Value == 2);
|
||||
|
||||
void addPressKeyStep() => AddStep($"Press {testKey} key", () => InputManager.Key(testKey));
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
protected override void AddCheckSteps()
|
||||
{
|
||||
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
|
||||
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 0));
|
||||
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.InputCountController.Triggers.Any(kc => kc.ActivationCount.Value > 0));
|
||||
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
};
|
||||
|
||||
// Add any key just to display the key counter visually.
|
||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||
hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||
scoreProcessor.Combo.Value = 1;
|
||||
|
||||
return new Container
|
||||
|
@ -19,6 +19,7 @@ using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Skinning;
|
||||
using osu.Game.Tests.Gameplay;
|
||||
using osuTK.Input;
|
||||
|
||||
@ -43,8 +44,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();
|
||||
|
||||
// best way to check without exposing.
|
||||
private Drawable hideTarget => hudOverlay.KeyCounter;
|
||||
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
||||
private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
|
||||
private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
|
||||
|
||||
[Test]
|
||||
public void TestComboCounterIncrementing()
|
||||
@ -62,7 +63,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
float? initialAlpha = null;
|
||||
|
||||
createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha);
|
||||
AddUntilStep("wait for load", () => hudOverlay.IsAlive);
|
||||
AddAssert("initial alpha was less than 1", () => initialAlpha < 1);
|
||||
}
|
||||
|
||||
@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false));
|
||||
|
||||
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent);
|
||||
AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
|
||||
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
|
||||
|
||||
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
|
||||
@ -89,13 +89,16 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||
|
||||
// Add any key just to display the key counter visually.
|
||||
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||
hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||
|
||||
action?.Invoke(hudOverlay);
|
||||
|
||||
return hudOverlay;
|
||||
});
|
||||
});
|
||||
AddUntilStep("HUD overlay loaded", () => hudOverlay.IsAlive);
|
||||
AddUntilStep("components container loaded",
|
||||
() => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Any(scc => scc.ComponentsLoaded));
|
||||
}
|
||||
|
||||
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();
|
||||
|
@ -335,11 +335,11 @@ namespace osu.Game.Rulesets.UI
|
||||
/// <returns>The representing <see cref="DrawableHitObject{TObject}"/>.</returns>
|
||||
public abstract DrawableHitObject<TObject> CreateDrawableRepresentation(TObject h);
|
||||
|
||||
public void Attach(KeyCounterDisplay keyCounter) =>
|
||||
(KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(keyCounter);
|
||||
public void Attach(InputCountController inputCountController) =>
|
||||
(KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(inputCountController);
|
||||
|
||||
public void Attach(ClicksPerSecondCalculator calculator) =>
|
||||
(KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(calculator);
|
||||
public void Attach(ClicksPerSecondController controller) =>
|
||||
(KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(controller);
|
||||
|
||||
/// <summary>
|
||||
/// Creates a key conversion input manager. An exception will be thrown if a valid <see cref="RulesetInputManager{T}"/> is not returned.
|
||||
|
21
osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs
Normal file
21
osu.Game/Rulesets/UI/ICanAttachHUDPieces.cs
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// A target (generally always <see cref="DrawableRuleset"/>) which can attach various skinnable components.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Attach methods will give the target permission to prepare the component into a usable state, usually via
|
||||
/// calling methods on the component (attaching various gameplay devices).
|
||||
/// </remarks>
|
||||
public interface ICanAttachHUDPieces
|
||||
{
|
||||
void Attach(InputCountController inputCountController);
|
||||
void Attach(ClicksPerSecondController controller);
|
||||
}
|
||||
}
|
15
osu.Game/Rulesets/UI/IHasRecordingHandler.cs
Normal file
15
osu.Game/Rulesets/UI/IHasRecordingHandler.cs
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Input;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Expose the <see cref="ReplayRecorder"/> in a capable <see cref="InputManager"/>.
|
||||
/// </summary>
|
||||
public interface IHasRecordingHandler
|
||||
{
|
||||
public ReplayRecorder? Recorder { set; }
|
||||
}
|
||||
}
|
16
osu.Game/Rulesets/UI/IHasReplayHandler.cs
Normal file
16
osu.Game/Rulesets/UI/IHasReplayHandler.cs
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Input;
|
||||
using osu.Game.Input.Handlers;
|
||||
|
||||
namespace osu.Game.Rulesets.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// Expose the <see cref="ReplayInputHandler"/> in a capable <see cref="InputManager"/>.
|
||||
/// </summary>
|
||||
public interface IHasReplayHandler
|
||||
{
|
||||
ReplayInputHandler? ReplayInputHandler { get; set; }
|
||||
}
|
||||
}
|
@ -160,62 +160,37 @@ namespace osu.Game.Rulesets.UI
|
||||
|
||||
#region Key Counter Attachment
|
||||
|
||||
public void Attach(KeyCounterDisplay keyCounter)
|
||||
public void Attach(InputCountController inputCountController)
|
||||
{
|
||||
var receptor = new ActionReceptor(keyCounter);
|
||||
var triggers = KeyBindingContainer.DefaultKeyBindings
|
||||
.Select(b => b.GetAction<T>())
|
||||
.Distinct()
|
||||
.OrderBy(action => action)
|
||||
.Select(action => new KeyCounterActionTrigger<T>(action))
|
||||
.ToArray();
|
||||
|
||||
KeyBindingContainer.Add(receptor);
|
||||
|
||||
keyCounter.SetReceptor(receptor);
|
||||
keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings
|
||||
.Select(b => b.GetAction<T>())
|
||||
.Distinct()
|
||||
.OrderBy(action => action)
|
||||
.Select(action => new KeyCounterActionTrigger<T>(action)));
|
||||
}
|
||||
|
||||
private partial class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler<T>
|
||||
{
|
||||
public ActionReceptor(KeyCounterDisplay target)
|
||||
: base(target)
|
||||
{
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<T> e) => Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger<T>)
|
||||
.Select(c => (KeyCounterActionTrigger<T>)c.Trigger)
|
||||
.Any(c => c.OnPressed(e.Action, Clock.Rate >= 0));
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<T> e)
|
||||
{
|
||||
foreach (var c
|
||||
in Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger<T>).Select(c => (KeyCounterActionTrigger<T>)c.Trigger))
|
||||
c.OnReleased(e.Action, Clock.Rate >= 0);
|
||||
}
|
||||
KeyBindingContainer.AddRange(triggers);
|
||||
inputCountController.AddRange(triggers);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Keys per second Counter Attachment
|
||||
|
||||
public void Attach(ClicksPerSecondCalculator calculator)
|
||||
{
|
||||
var listener = new ActionListener(calculator);
|
||||
|
||||
KeyBindingContainer.Add(listener);
|
||||
}
|
||||
public void Attach(ClicksPerSecondController controller) => KeyBindingContainer.Add(new ActionListener(controller));
|
||||
|
||||
private partial class ActionListener : Component, IKeyBindingHandler<T>
|
||||
{
|
||||
private readonly ClicksPerSecondCalculator calculator;
|
||||
private readonly ClicksPerSecondController controller;
|
||||
|
||||
public ActionListener(ClicksPerSecondCalculator calculator)
|
||||
public ActionListener(ClicksPerSecondController controller)
|
||||
{
|
||||
this.calculator = calculator;
|
||||
this.controller = controller;
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<T> e)
|
||||
{
|
||||
calculator.AddInputTimestamp();
|
||||
controller.AddInputTimestamp();
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -247,29 +222,6 @@ namespace osu.Game.Rulesets.UI
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Expose the <see cref="ReplayInputHandler"/> in a capable <see cref="InputManager"/>.
|
||||
/// </summary>
|
||||
public interface IHasReplayHandler
|
||||
{
|
||||
ReplayInputHandler ReplayInputHandler { get; set; }
|
||||
}
|
||||
|
||||
public interface IHasRecordingHandler
|
||||
{
|
||||
public ReplayRecorder Recorder { set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Supports attaching various HUD pieces.
|
||||
/// Keys will be populated automatically and a receptor will be injected inside.
|
||||
/// </summary>
|
||||
public interface ICanAttachHUDPieces
|
||||
{
|
||||
void Attach(KeyCounterDisplay keyCounter);
|
||||
void Attach(ClicksPerSecondCalculator calculator);
|
||||
}
|
||||
|
||||
public class RulesetInputManagerInputState<T> : InputState
|
||||
where T : struct
|
||||
{
|
||||
|
@ -8,7 +8,7 @@ using osu.Game.Rulesets.UI;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
||||
{
|
||||
public partial class ClicksPerSecondCalculator : Component
|
||||
public partial class ClicksPerSecondController : Component
|
||||
{
|
||||
private readonly List<double> timestamps = new List<double>();
|
||||
|
||||
@ -22,7 +22,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
||||
|
||||
private IGameplayClock clock => frameStableClock ?? gameplayClock;
|
||||
|
||||
public ClicksPerSecondCalculator()
|
||||
public ClicksPerSecondController()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
}
|
@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
||||
public partial class ClicksPerSecondCounter : RollingCounter<int>, ISerialisableDrawable
|
||||
{
|
||||
[Resolved]
|
||||
private ClicksPerSecondCalculator calculator { get; set; } = null!;
|
||||
private ClicksPerSecondController controller { get; set; } = null!;
|
||||
|
||||
protected override double RollingDuration => 350;
|
||||
|
||||
@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
|
||||
{
|
||||
base.Update();
|
||||
|
||||
Current.Value = calculator.Value;
|
||||
Current.Value = controller.Value;
|
||||
}
|
||||
|
||||
protected override IHasText CreateText() => new TextComponent();
|
||||
|
33
osu.Game/Screens/Play/HUD/InputCountController.cs
Normal file
33
osu.Game/Screens/Play/HUD/InputCountController.cs
Normal file
@ -0,0 +1,33 @@
|
||||
// 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.Collections.Generic;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
/// <summary>
|
||||
/// Keeps track of key press counts for a current play session, exposing bindable counts which can
|
||||
/// be used for display purposes.
|
||||
/// </summary>
|
||||
public partial class InputCountController : Component
|
||||
{
|
||||
public readonly Bindable<bool> IsCounting = new BindableBool(true);
|
||||
|
||||
private readonly BindableList<InputTrigger> triggers = new BindableList<InputTrigger>();
|
||||
|
||||
public IBindableList<InputTrigger> Triggers => triggers;
|
||||
|
||||
public void AddRange(IEnumerable<InputTrigger> triggers) => triggers.ForEach(Add);
|
||||
|
||||
public void Add(InputTrigger trigger)
|
||||
{
|
||||
// Note that these triggers are not added to the hierarchy here. It is presumed they are added externally at a
|
||||
// more correct location (ie. inside a RulesetInputManager).
|
||||
triggers.Add(trigger);
|
||||
trigger.IsCounting.BindTo(IsCounting);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
@ -25,13 +26,38 @@ namespace osu.Game.Screens.Play.HUD
|
||||
public event OnActivateCallback? OnActivate;
|
||||
public event OnDeactivateCallback? OnDeactivate;
|
||||
|
||||
private readonly Bindable<int> activationCount = new BindableInt();
|
||||
private readonly Bindable<bool> isCounting = new BindableBool(true);
|
||||
|
||||
/// <summary>
|
||||
/// Number of times this <see cref="InputTrigger"/> has been activated.
|
||||
/// </summary>
|
||||
public IBindable<int> ActivationCount => activationCount;
|
||||
|
||||
/// <summary>
|
||||
/// Whether any activation or deactivation of this <see cref="InputTrigger"/> impacts its <see cref="ActivationCount"/>
|
||||
/// </summary>
|
||||
public IBindable<bool> IsCounting => isCounting;
|
||||
|
||||
protected InputTrigger(string name)
|
||||
{
|
||||
Name = name;
|
||||
}
|
||||
|
||||
protected void Activate(bool forwardPlayback = true) => OnActivate?.Invoke(forwardPlayback);
|
||||
protected void Activate(bool forwardPlayback = true)
|
||||
{
|
||||
if (forwardPlayback && isCounting.Value)
|
||||
activationCount.Value++;
|
||||
|
||||
protected void Deactivate(bool forwardPlayback = true) => OnDeactivate?.Invoke(forwardPlayback);
|
||||
OnActivate?.Invoke(forwardPlayback);
|
||||
}
|
||||
|
||||
protected void Deactivate(bool forwardPlayback = true)
|
||||
{
|
||||
if (!forwardPlayback && isCounting.Value)
|
||||
activationCount.Value--;
|
||||
|
||||
OnDeactivate?.Invoke(forwardPlayback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
@ -16,7 +16,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
/// Keeps track of judgements for a current play session, exposing bindable counts which can
|
||||
/// be used for display purposes.
|
||||
/// </summary>
|
||||
public partial class JudgementTally : CompositeDrawable
|
||||
public partial class JudgementCountController : Component
|
||||
{
|
||||
[Resolved]
|
||||
private ScoreProcessor scoreProcessor { get; set; } = null!;
|
@ -18,9 +18,9 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
public BindableBool ShowName = new BindableBool();
|
||||
public Bindable<FillDirection> Direction = new Bindable<FillDirection>();
|
||||
|
||||
public readonly JudgementTally.JudgementCount Result;
|
||||
public readonly JudgementCountController.JudgementCount Result;
|
||||
|
||||
public JudgementCounter(JudgementTally.JudgementCount result) => Result = result;
|
||||
public JudgementCounter(JudgementCountController.JudgementCount result) => Result = result;
|
||||
|
||||
public OsuSpriteText ResultName = null!;
|
||||
private FillFlowContainer flowContainer = null!;
|
||||
|
@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
public BindableBool ShowMaxJudgement { get; set; } = new BindableBool(true);
|
||||
|
||||
[Resolved]
|
||||
private JudgementTally tally { get; set; } = null!;
|
||||
private JudgementCountController judgementCountController { get; set; } = null!;
|
||||
|
||||
protected FillFlowContainer<JudgementCounter> CounterFlow = null!;
|
||||
|
||||
@ -49,7 +49,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
AutoSizeAxes = Axes.Both
|
||||
};
|
||||
|
||||
foreach (var result in tally.Results)
|
||||
foreach (var result in judgementCountController.Results)
|
||||
CounterFlow.Add(createCounter(result));
|
||||
}
|
||||
|
||||
@ -123,7 +123,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
|
||||
}
|
||||
}
|
||||
|
||||
private JudgementCounter createCounter(JudgementTally.JudgementCount info) =>
|
||||
private JudgementCounter createCounter(JudgementCountController.JudgementCount info) =>
|
||||
new JudgementCounter(info)
|
||||
{
|
||||
State = { Value = Visibility.Hidden },
|
||||
|
@ -2,7 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
@ -17,24 +16,10 @@ namespace osu.Game.Screens.Play.HUD
|
||||
/// </summary>
|
||||
public readonly InputTrigger Trigger;
|
||||
|
||||
/// <summary>
|
||||
/// Whether the actions reported by <see cref="Trigger"/> should be counted.
|
||||
/// </summary>
|
||||
public Bindable<bool> IsCounting { get; } = new BindableBool(true);
|
||||
|
||||
private readonly Bindable<int> countPresses = new BindableInt
|
||||
{
|
||||
MinValue = 0
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// The current count of registered key presses.
|
||||
/// </summary>
|
||||
public IBindable<int> CountPresses => countPresses;
|
||||
|
||||
private readonly Container content;
|
||||
|
||||
protected override Container<Drawable> Content => content;
|
||||
public IBindable<int> CountPresses => Trigger.ActivationCount;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this <see cref="KeyCounter"/> is currently in the "activated" state because the associated key is currently pressed.
|
||||
@ -43,52 +28,26 @@ namespace osu.Game.Screens.Play.HUD
|
||||
|
||||
protected KeyCounter(InputTrigger trigger)
|
||||
{
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
Trigger = trigger,
|
||||
};
|
||||
Trigger = trigger;
|
||||
|
||||
Trigger.OnActivate += Activate;
|
||||
Trigger.OnDeactivate += Deactivate;
|
||||
}
|
||||
|
||||
private void increment()
|
||||
{
|
||||
if (!IsCounting.Value)
|
||||
return;
|
||||
|
||||
countPresses.Value++;
|
||||
}
|
||||
|
||||
private void decrement()
|
||||
{
|
||||
if (!IsCounting.Value)
|
||||
return;
|
||||
|
||||
countPresses.Value--;
|
||||
}
|
||||
|
||||
protected virtual void Activate(bool forwardPlayback = true)
|
||||
{
|
||||
IsActive.Value = true;
|
||||
if (forwardPlayback)
|
||||
increment();
|
||||
}
|
||||
|
||||
protected virtual void Deactivate(bool forwardPlayback = true)
|
||||
{
|
||||
IsActive.Value = false;
|
||||
if (!forwardPlayback)
|
||||
decrement();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
Trigger.OnActivate -= Activate;
|
||||
Trigger.OnDeactivate -= Deactivate;
|
||||
}
|
||||
|
@ -2,10 +2,12 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
public partial class KeyCounterActionTrigger<T> : InputTrigger
|
||||
public partial class KeyCounterActionTrigger<T> : InputTrigger, IKeyBindingHandler<T>
|
||||
where T : struct
|
||||
{
|
||||
public T Action { get; }
|
||||
@ -16,21 +18,21 @@ namespace osu.Game.Screens.Play.HUD
|
||||
Action = action;
|
||||
}
|
||||
|
||||
public bool OnPressed(T action, bool forwards)
|
||||
public bool OnPressed(KeyBindingPressEvent<T> e)
|
||||
{
|
||||
if (!EqualityComparer<T>.Default.Equals(action, Action))
|
||||
if (!EqualityComparer<T>.Default.Equals(e.Action, Action))
|
||||
return false;
|
||||
|
||||
Activate(forwards);
|
||||
Activate(Clock.Rate >= 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(T action, bool forwards)
|
||||
public void OnReleased(KeyBindingReleaseEvent<T> e)
|
||||
{
|
||||
if (!EqualityComparer<T>.Default.Equals(action, Action))
|
||||
if (!EqualityComparer<T>.Default.Equals(e.Action, Action))
|
||||
return;
|
||||
|
||||
Deactivate(forwards);
|
||||
Deactivate(Clock.Rate >= 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,24 +1,20 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Collections.Specialized;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
using osuTK;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Skinning;
|
||||
|
||||
namespace osu.Game.Screens.Play.HUD
|
||||
{
|
||||
/// <summary>
|
||||
/// A flowing display of all gameplay keys. Individual keys can be added using <see cref="InputTrigger"/> implementations.
|
||||
/// </summary>
|
||||
public abstract partial class KeyCounterDisplay : CompositeDrawable
|
||||
public abstract partial class KeyCounterDisplay : CompositeDrawable, ISerialisableDrawable
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the key counter should be visible regardless of the configuration value.
|
||||
@ -26,95 +22,46 @@ namespace osu.Game.Screens.Play.HUD
|
||||
/// </summary>
|
||||
public Bindable<bool> AlwaysVisible { get; } = new Bindable<bool>(true);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="KeyCounter"/>s contained in this <see cref="KeyCounterDisplay"/>.
|
||||
/// </summary>
|
||||
public IEnumerable<KeyCounter> Counters => KeyFlow;
|
||||
|
||||
protected abstract FillFlowContainer<KeyCounter> KeyFlow { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the actions reported by all <see cref="InputTrigger"/>s within this <see cref="KeyCounterDisplay"/> should be counted.
|
||||
/// </summary>
|
||||
public Bindable<bool> IsCounting { get; } = new BindableBool(true);
|
||||
|
||||
protected readonly Bindable<bool> ConfigVisibility = new Bindable<bool>();
|
||||
|
||||
private readonly IBindableList<InputTrigger> triggers = new BindableList<InputTrigger>();
|
||||
|
||||
[Resolved]
|
||||
private InputCountController controller { get; set; } = null!;
|
||||
|
||||
protected abstract void UpdateVisibility();
|
||||
|
||||
private Receptor? receptor;
|
||||
|
||||
public void SetReceptor(Receptor receptor)
|
||||
{
|
||||
if (this.receptor != null)
|
||||
throw new InvalidOperationException("Cannot set a new receptor when one is already active");
|
||||
|
||||
this.receptor = receptor;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a <see cref="InputTrigger"/> to this display.
|
||||
/// </summary>
|
||||
public void Add(InputTrigger trigger)
|
||||
{
|
||||
var keyCounter = CreateCounter(trigger);
|
||||
|
||||
KeyFlow.Add(keyCounter);
|
||||
|
||||
IsCounting.BindTo(keyCounter.IsCounting);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add a range of <see cref="InputTrigger"/> to this display.
|
||||
/// </summary>
|
||||
public void AddRange(IEnumerable<InputTrigger> triggers) => triggers.ForEach(Add);
|
||||
|
||||
protected abstract KeyCounter CreateCounter(InputTrigger trigger);
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuConfigManager config)
|
||||
private void load(OsuConfigManager config, DrawableRuleset? drawableRuleset)
|
||||
{
|
||||
config.BindWith(OsuSetting.KeyOverlay, ConfigVisibility);
|
||||
|
||||
if (drawableRuleset != null)
|
||||
AlwaysVisible.BindTo(drawableRuleset.HasReplayLoaded);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
triggers.BindTo(controller.Triggers);
|
||||
triggers.BindCollectionChanged(triggersChanged, true);
|
||||
|
||||
AlwaysVisible.BindValueChanged(_ => UpdateVisibility());
|
||||
ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true);
|
||||
}
|
||||
|
||||
public override bool HandleNonPositionalInput => receptor == null;
|
||||
|
||||
public override bool HandlePositionalInput => receptor == null;
|
||||
|
||||
public partial class Receptor : Drawable
|
||||
private void triggersChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
protected readonly KeyCounterDisplay Target;
|
||||
|
||||
public Receptor(KeyCounterDisplay target)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Depth = float.MinValue;
|
||||
Target = target;
|
||||
}
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
protected override bool Handle(UIEvent e)
|
||||
{
|
||||
switch (e)
|
||||
{
|
||||
case KeyDownEvent:
|
||||
case KeyUpEvent:
|
||||
case MouseDownEvent:
|
||||
case MouseUpEvent:
|
||||
return Target.InternalChildren.Any(c => c.TriggerEvent(e));
|
||||
}
|
||||
|
||||
return base.Handle(e);
|
||||
}
|
||||
KeyFlow.Clear();
|
||||
foreach (var trigger in controller.Triggers)
|
||||
KeyFlow.Add(CreateCounter(trigger));
|
||||
}
|
||||
|
||||
public bool UsesFixedAnchor { get; set; }
|
||||
}
|
||||
}
|
||||
|
@ -16,8 +16,10 @@ using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.UI;
|
||||
@ -26,8 +28,6 @@ using osu.Game.Screens.Play.HUD.ClicksPerSecond;
|
||||
using osu.Game.Screens.Play.HUD.JudgementCounter;
|
||||
using osu.Game.Skinning;
|
||||
using osuTK;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Screens.Play
|
||||
{
|
||||
@ -54,16 +54,18 @@ namespace osu.Game.Screens.Play
|
||||
return child == bottomRightElements;
|
||||
}
|
||||
|
||||
public readonly KeyCounterDisplay KeyCounter;
|
||||
public readonly ModDisplay ModDisplay;
|
||||
public readonly HoldForMenuButton HoldToQuit;
|
||||
public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
|
||||
|
||||
[Cached]
|
||||
private readonly ClicksPerSecondCalculator clicksPerSecondCalculator;
|
||||
private readonly ClicksPerSecondController clicksPerSecondController;
|
||||
|
||||
[Cached]
|
||||
private readonly JudgementTally tally;
|
||||
public readonly InputCountController InputCountController;
|
||||
|
||||
[Cached]
|
||||
private readonly JudgementCountController judgementCountController;
|
||||
|
||||
public Bindable<bool> ShowHealthBar = new Bindable<bool>(true);
|
||||
|
||||
@ -113,7 +115,9 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
CreateFailingLayer(),
|
||||
//Needs to be initialized before skinnable drawables.
|
||||
tally = new JudgementTally(),
|
||||
judgementCountController = new JudgementCountController(),
|
||||
clicksPerSecondController = new ClicksPerSecondController(),
|
||||
InputCountController = new InputCountController(),
|
||||
mainComponents = new HUDComponentsContainer { AlwaysPresent = true, },
|
||||
rulesetComponents = drawableRuleset != null
|
||||
? new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, }
|
||||
@ -145,7 +149,6 @@ namespace osu.Game.Screens.Play
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
KeyCounter = CreateKeyCounter(),
|
||||
HoldToQuit = CreateHoldForMenuButton(),
|
||||
}
|
||||
},
|
||||
@ -156,10 +159,9 @@ namespace osu.Game.Screens.Play
|
||||
Padding = new MarginPadding(44), // enough margin to avoid the hit error display
|
||||
Spacing = new Vector2(5)
|
||||
},
|
||||
clicksPerSecondCalculator = new ClicksPerSecondCalculator(),
|
||||
};
|
||||
|
||||
hideTargets = new List<Drawable> { mainComponents, rulesetComponents, KeyCounter, topRightElements };
|
||||
hideTargets = new List<Drawable> { mainComponents, rulesetComponents, topRightElements };
|
||||
|
||||
if (!alwaysShowLeaderboard)
|
||||
hideTargets.Add(LeaderboardFlow);
|
||||
@ -303,13 +305,13 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
PlayerSettingsOverlay.Show();
|
||||
ModDisplay.FadeIn(200);
|
||||
KeyCounter.Margin = new MarginPadding(10) { Bottom = 30 };
|
||||
InputCountController.Margin = new MarginPadding(10) { Bottom = 30 };
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerSettingsOverlay.Hide();
|
||||
ModDisplay.Delay(2000).FadeOut(200);
|
||||
KeyCounter.Margin = new MarginPadding(10);
|
||||
InputCountController.Margin = new MarginPadding(10);
|
||||
}
|
||||
|
||||
updateVisibility();
|
||||
@ -319,8 +321,8 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
if (drawableRuleset is ICanAttachHUDPieces attachTarget)
|
||||
{
|
||||
attachTarget.Attach(KeyCounter);
|
||||
attachTarget.Attach(clicksPerSecondCalculator);
|
||||
attachTarget.Attach(InputCountController);
|
||||
attachTarget.Attach(clicksPerSecondController);
|
||||
}
|
||||
|
||||
replayLoaded.BindTo(drawableRuleset.HasReplayLoaded);
|
||||
@ -331,12 +333,6 @@ namespace osu.Game.Screens.Play
|
||||
ShowHealth = { BindTarget = ShowHealthBar }
|
||||
};
|
||||
|
||||
protected KeyCounterDisplay CreateKeyCounter() => new DefaultKeyCounterDisplay
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
};
|
||||
|
||||
protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
|
@ -429,13 +429,12 @@ namespace osu.Game.Screens.Play
|
||||
IsPaused = { BindTarget = GameplayClockContainer.IsPaused },
|
||||
ReplayLoaded = { BindTarget = DrawableRuleset.HasReplayLoaded },
|
||||
},
|
||||
KeyCounter =
|
||||
InputCountController =
|
||||
{
|
||||
IsCounting =
|
||||
{
|
||||
Value = false
|
||||
},
|
||||
AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded },
|
||||
},
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
@ -475,7 +474,7 @@ namespace osu.Game.Screens.Play
|
||||
{
|
||||
updateGameplayState();
|
||||
updatePauseOnFocusLostState();
|
||||
HUDOverlay.KeyCounter.IsCounting.Value = !isBreakTime.NewValue;
|
||||
HUDOverlay.InputCountController.IsCounting.Value = !isBreakTime.NewValue;
|
||||
}
|
||||
|
||||
private void updateGameplayState()
|
||||
|
@ -12,6 +12,7 @@ using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Screens.Play;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
using osuTK;
|
||||
@ -113,6 +114,7 @@ namespace osu.Game.Skinning
|
||||
var combo = container.OfType<DefaultComboCounter>().FirstOrDefault();
|
||||
var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault();
|
||||
var songProgress = container.OfType<ArgonSongProgress>().FirstOrDefault();
|
||||
var keyCounter = container.OfType<ArgonKeyCounterDisplay>().FirstOrDefault();
|
||||
|
||||
if (score != null)
|
||||
{
|
||||
@ -166,8 +168,20 @@ namespace osu.Game.Skinning
|
||||
|
||||
if (songProgress != null)
|
||||
{
|
||||
songProgress.Position = new Vector2(0, -10);
|
||||
const float padding = 10;
|
||||
|
||||
songProgress.Position = new Vector2(0, -padding);
|
||||
songProgress.Scale = new Vector2(0.9f, 1);
|
||||
|
||||
if (keyCounter != null && hitError != null)
|
||||
{
|
||||
// Hard to find this at runtime, so taken from the most expanded state during replay.
|
||||
const float song_progress_offset_height = 36 + padding;
|
||||
|
||||
keyCounter.Anchor = Anchor.BottomRight;
|
||||
keyCounter.Origin = Anchor.BottomRight;
|
||||
keyCounter.Position = new Vector2(-(hitError.Width + padding), -(padding * 2 + song_progress_offset_height));
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -179,6 +193,7 @@ namespace osu.Game.Skinning
|
||||
new DefaultAccuracyCounter(),
|
||||
new DefaultHealthDisplay(),
|
||||
new ArgonSongProgress(),
|
||||
new ArgonKeyCounterDisplay(),
|
||||
new BarHitErrorMeter(),
|
||||
new BarHitErrorMeter(),
|
||||
new PerformancePointsCounter()
|
||||
|
@ -22,6 +22,7 @@ using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Screens.Play.HUD;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Skinning
|
||||
@ -372,12 +373,22 @@ namespace osu.Game.Skinning
|
||||
}
|
||||
|
||||
var hitError = container.OfType<HitErrorMeter>().FirstOrDefault();
|
||||
var keyCounter = container.OfType<DefaultKeyCounterDisplay>().FirstOrDefault();
|
||||
|
||||
if (hitError != null)
|
||||
{
|
||||
hitError.Anchor = Anchor.BottomCentre;
|
||||
hitError.Origin = Anchor.CentreLeft;
|
||||
hitError.Rotation = -90;
|
||||
|
||||
if (keyCounter != null)
|
||||
{
|
||||
const float padding = 10;
|
||||
|
||||
keyCounter.Anchor = Anchor.BottomRight;
|
||||
keyCounter.Origin = Anchor.BottomRight;
|
||||
keyCounter.Position = new Vector2(-padding, -(padding + hitError.Width));
|
||||
}
|
||||
}
|
||||
})
|
||||
{
|
||||
@ -389,6 +400,7 @@ namespace osu.Game.Skinning
|
||||
new LegacyHealthDisplay(),
|
||||
new LegacySongProgress(),
|
||||
new BarHitErrorMeter(),
|
||||
new DefaultKeyCounterDisplay()
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -90,6 +90,8 @@ namespace osu.Game.Skinning
|
||||
var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault();
|
||||
var combo = container.OfType<DefaultComboCounter>().FirstOrDefault();
|
||||
var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault();
|
||||
var songProgress = container.OfType<DefaultSongProgress>().FirstOrDefault();
|
||||
var keyCounter = container.OfType<DefaultKeyCounterDisplay>().FirstOrDefault();
|
||||
|
||||
if (score != null)
|
||||
{
|
||||
@ -141,6 +143,18 @@ namespace osu.Game.Skinning
|
||||
hitError2.Origin = Anchor.CentreLeft;
|
||||
}
|
||||
}
|
||||
|
||||
if (songProgress != null && keyCounter != null)
|
||||
{
|
||||
const float padding = 10;
|
||||
|
||||
// Hard to find this at runtime, so taken from the most expanded state during replay.
|
||||
const float song_progress_offset_height = 73;
|
||||
|
||||
keyCounter.Anchor = Anchor.BottomRight;
|
||||
keyCounter.Origin = Anchor.BottomRight;
|
||||
keyCounter.Position = new Vector2(-padding, -(song_progress_offset_height + padding));
|
||||
}
|
||||
})
|
||||
{
|
||||
Children = new Drawable[]
|
||||
@ -150,6 +164,7 @@ namespace osu.Game.Skinning
|
||||
new DefaultAccuracyCounter(),
|
||||
new DefaultHealthDisplay(),
|
||||
new DefaultSongProgress(),
|
||||
new DefaultKeyCounterDisplay(),
|
||||
new BarHitErrorMeter(),
|
||||
new BarHitErrorMeter(),
|
||||
new PerformancePointsCounter()
|
||||
|
Loading…
Reference in New Issue
Block a user