1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-07 13:07:25 +08:00

Merge pull request #22654 from ItsShamed/gameplay/key-counter-abstraction

Introduce common structure for key counters
This commit is contained in:
Bartłomiej Dach 2023-04-05 21:07:30 +02:00 committed by GitHub
commit a4e68e1845
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 420 additions and 310 deletions

View File

@ -21,7 +21,7 @@ using osu.Game.Configuration;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Tests.Visual;
using osuTK;
using osuTK.Graphics;
@ -34,9 +34,9 @@ namespace osu.Game.Rulesets.Osu.Tests
[Resolved]
private OsuConfigManager config { get; set; } = null!;
private TestActionKeyCounter leftKeyCounter = null!;
private DefaultKeyCounter leftKeyCounter = null!;
private TestActionKeyCounter rightKeyCounter = null!;
private DefaultKeyCounter rightKeyCounter = null!;
private OsuInputManager osuInputManager = null!;
@ -59,14 +59,14 @@ namespace osu.Game.Rulesets.Osu.Tests
Origin = Anchor.Centre,
Children = new Drawable[]
{
leftKeyCounter = new TestActionKeyCounter(OsuAction.LeftButton)
leftKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.LeftButton))
{
Anchor = Anchor.Centre,
Origin = Anchor.CentreRight,
Depth = float.MinValue,
X = -100,
},
rightKeyCounter = new TestActionKeyCounter(OsuAction.RightButton)
rightKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.RightButton))
{
Anchor = Anchor.Centre,
Origin = Anchor.CentreLeft,
@ -598,8 +598,8 @@ namespace osu.Game.Rulesets.Osu.Tests
private void assertKeyCounter(int left, int right)
{
AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses, () => Is.EqualTo(left));
AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses, () => Is.EqualTo(right));
AddAssert($"The left key was pressed {left} times", () => leftKeyCounter.CountPresses.Value, () => Is.EqualTo(left));
AddAssert($"The right key was pressed {right} times", () => rightKeyCounter.CountPresses.Value, () => Is.EqualTo(right));
}
private void releaseAllTouches()
@ -615,11 +615,11 @@ namespace osu.Game.Rulesets.Osu.Tests
private void checkNotPressed(OsuAction action) => AddAssert($"Not pressing {action}", () => !osuInputManager.PressedActions.Contains(action));
private void checkPressed(OsuAction action) => AddAssert($"Is pressing {action}", () => osuInputManager.PressedActions.Contains(action));
public partial class TestActionKeyCounter : KeyCounter, IKeyBindingHandler<OsuAction>
public partial class TestActionKeyCounterTrigger : InputTrigger, IKeyBindingHandler<OsuAction>
{
public OsuAction Action { get; }
public TestActionKeyCounter(OsuAction action)
public TestActionKeyCounterTrigger(OsuAction action)
: base(action.ToString())
{
Action = action;
@ -629,8 +629,7 @@ namespace osu.Game.Rulesets.Osu.Tests
{
if (e.Action == Action)
{
IsLit = true;
Increment();
Activate();
}
return false;
@ -638,7 +637,8 @@ namespace osu.Game.Rulesets.Osu.Tests
public void OnReleased(KeyBindingReleaseEvent<OsuAction> e)
{
if (e.Action == Action) IsLit = false;
if (e.Action == Action)
Deactivate();
}
}

View File

@ -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.Children.Any(kc => kc.CountPresses > 2));
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 2));
seekTo(referenceBeatmap.Breaks[0].StartTime);
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting);
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.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.Children.All(kc => kc.CountPresses == 0));
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0));
seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);

View File

@ -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.Children.Select(kc => kc.CountPresses).Sum() == 15);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Select(kc => kc.CountPresses.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.Children.All(kc => kc.CountPresses == 0));
AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0));
AddAssert("no results triggered", () => Player.Results.Count == 0);
}

View File

@ -45,7 +45,7 @@ namespace osu.Game.Tests.Visual.Gameplay
// best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<DefaultKeyCounter>>().Single();
[BackgroundDependencyLoader]
private void load()
@ -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 KeyCounterKeyboard(Key.Space));
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
scoreProcessor.Combo.Value = 1;

View File

@ -7,7 +7,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Utils;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Gameplay
@ -17,28 +17,29 @@ namespace osu.Game.Tests.Visual.Gameplay
{
public TestSceneKeyCounter()
{
KeyCounterKeyboard testCounter;
KeyCounterDisplay kc = new KeyCounterDisplay
KeyCounterDisplay kc = new DefaultKeyCounterDisplay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Children = new KeyCounter[]
{
testCounter = new KeyCounterKeyboard(Key.X),
new KeyCounterKeyboard(Key.X),
new KeyCounterMouse(MouseButton.Left),
new KeyCounterMouse(MouseButton.Right),
},
};
kc.AddRange(new InputTrigger[]
{
new KeyCounterKeyboardTrigger(Key.X),
new KeyCounterKeyboardTrigger(Key.X),
new KeyCounterMouseTrigger(MouseButton.Left),
new KeyCounterMouseTrigger(MouseButton.Right),
});
var testCounter = (DefaultKeyCounter)kc.Counters.First();
AddStep("Add random", () =>
{
Key key = (Key)((int)Key.A + RNG.Next(26));
kc.Add(new KeyCounterKeyboard(key));
kc.Add(new KeyCounterKeyboardTrigger(key));
});
Key testKey = ((KeyCounterKeyboard)kc.Children.First()).Key;
Key testKey = ((KeyCounterKeyboardTrigger)kc.Counters.First().Trigger).Key;
void addPressKeyStep()
{
@ -46,12 +47,12 @@ namespace osu.Game.Tests.Visual.Gameplay
}
addPressKeyStep();
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 1);
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1);
addPressKeyStep();
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses == 2);
AddStep("Disable counting", () => testCounter.IsCounting = false);
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2);
AddStep("Disable counting", () => testCounter.IsCounting.Value = false);
addPressKeyStep();
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses == 2);
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2);
Add(kc);
}

View File

@ -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.Children.Any(kc => kc.CountPresses > 0));
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 0));
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
}

View File

@ -14,6 +14,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Tests.Gameplay;
using osuTK.Input;
@ -57,7 +58,7 @@ namespace osu.Game.Tests.Visual.Gameplay
};
// Add any key just to display the key counter visually.
hudOverlay.KeyCounter.Add(new KeyCounterKeyboard(Key.Space));
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
scoreProcessor.Combo.Value = 1;
return new Container

View File

@ -18,6 +18,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Tests.Gameplay;
using osuTK.Input;
@ -43,7 +44,7 @@ namespace osu.Game.Tests.Visual.Gameplay
// best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter;
private FillFlowContainer<KeyCounter> keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().First();
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<DefaultKeyCounter>>().Single();
[Test]
public void TestComboCounterIncrementing()
@ -88,7 +89,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 KeyCounterKeyboard(Key.Space));
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space));
action?.Invoke(hudOverlay);

View File

@ -14,6 +14,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Overlays.Settings.Sections.Input;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Select;
using osu.Game.Tests.Beatmaps.IO;
using osuTK.Input;
@ -69,10 +70,10 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("wait for gameplay", () => player?.IsBreakTime.Value == false);
AddStep("press 'z'", () => InputManager.Key(Key.Z));
AddAssert("key counter didn't increase", () => keyCounter.CountPresses == 0);
AddAssert("key counter didn't increase", () => keyCounter.CountPresses.Value == 0);
AddStep("press 's'", () => InputManager.Key(Key.S));
AddAssert("key counter did increase", () => keyCounter.CountPresses == 1);
AddAssert("key counter did increase", () => keyCounter.CountPresses.Value == 1);
}
private KeyBindingsSubsection osuBindingSubsection => keyBindingPanel

View File

@ -30,6 +30,7 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
using osuTK;

View File

@ -19,7 +19,7 @@ using osu.Game.Input;
using osu.Game.Input.Bindings;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
using static osu.Game.Input.Handlers.ReplayInputHandler;
@ -169,7 +169,7 @@ namespace osu.Game.Rulesets.UI
.Select(b => b.GetAction<T>())
.Distinct()
.OrderBy(action => action)
.Select(action => new KeyCounterAction<T>(action)));
.Select(action => new KeyCounterActionTrigger<T>(action)));
}
private partial class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler<T>
@ -179,11 +179,14 @@ namespace osu.Game.Rulesets.UI
{
}
public bool OnPressed(KeyBindingPressEvent<T> e) => Target.Children.OfType<KeyCounterAction<T>>().Any(c => c.OnPressed(e.Action, Clock.Rate >= 0));
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.Children.OfType<KeyCounterAction<T>>())
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);
}
}

View File

@ -1,8 +1,6 @@
// 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.
#nullable disable
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -13,70 +11,23 @@ using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Play
namespace osu.Game.Screens.Play.HUD
{
public abstract partial class KeyCounter : Container
public partial class DefaultKeyCounter : KeyCounter
{
private Sprite buttonSprite;
private Sprite glowSprite;
private Container textLayer;
private SpriteText countSpriteText;
public bool IsCounting { get; set; } = true;
private int countPresses;
public int CountPresses
{
get => countPresses;
private set
{
if (countPresses != value)
{
countPresses = value;
countSpriteText.Text = value.ToString(@"#,0");
}
}
}
private bool isLit;
public bool IsLit
{
get => isLit;
protected set
{
if (isLit != value)
{
isLit = value;
updateGlowSprite(value);
}
}
}
public void Increment()
{
if (!IsCounting)
return;
CountPresses++;
}
public void Decrement()
{
if (!IsCounting)
return;
CountPresses--;
}
private Sprite buttonSprite = null!;
private Sprite glowSprite = null!;
private Container textLayer = null!;
private SpriteText countSpriteText = null!;
//further: change default values here and in KeyCounterCollection if needed, instead of passing them in every constructor
public Color4 KeyDownTextColor { get; set; } = Color4.DarkGray;
public Color4 KeyUpTextColor { get; set; } = Color4.White;
public double FadeTime { get; set; }
protected KeyCounter(string name)
public DefaultKeyCounter(InputTrigger trigger)
: base(trigger)
{
Name = name;
}
[BackgroundDependencyLoader(true)]
@ -116,7 +67,7 @@ namespace osu.Game.Screens.Play
},
countSpriteText = new OsuSpriteText
{
Text = CountPresses.ToString(@"#,0"),
Text = CountPresses.Value.ToString(@"#,0"),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativePositionAxes = Axes.Both,
@ -130,6 +81,9 @@ namespace osu.Game.Screens.Play
// so the size can be changing between buttonSprite and glowSprite.
Height = buttonSprite.DrawHeight;
Width = buttonSprite.DrawWidth;
IsActive.BindValueChanged(e => updateGlowSprite(e.NewValue), true);
CountPresses.BindValueChanged(e => countSpriteText.Text = e.NewValue.ToString(@"#,0"), true);
}
private void updateGlowSprite(bool show)

View File

@ -0,0 +1,83 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using osuTK.Graphics;
namespace osu.Game.Screens.Play.HUD
{
public partial class DefaultKeyCounterDisplay : KeyCounterDisplay
{
private const int duration = 100;
private const double key_fade_time = 80;
private readonly FillFlowContainer<DefaultKeyCounter> keyFlow;
public override IEnumerable<KeyCounter> Counters => keyFlow;
public DefaultKeyCounterDisplay()
{
InternalChild = keyFlow = new FillFlowContainer<DefaultKeyCounter>
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
Alpha = 0,
};
}
protected override void Update()
{
base.Update();
// Don't use autosize as it will shrink to zero when KeyFlow is hidden.
// In turn this can cause the display to be masked off screen and never become visible again.
Size = keyFlow.Size;
}
public override void Add(InputTrigger trigger) =>
keyFlow.Add(new DefaultKeyCounter(trigger)
{
FadeTime = key_fade_time,
KeyDownTextColor = KeyDownTextColor,
KeyUpTextColor = KeyUpTextColor,
});
protected override void UpdateVisibility() =>
// Isolate changing visibility of the key counters from fading this component.
keyFlow.FadeTo(AlwaysVisible.Value || ConfigVisibility.Value ? 1 : 0, duration);
private Color4 keyDownTextColor = Color4.DarkGray;
public Color4 KeyDownTextColor
{
get => keyDownTextColor;
set
{
if (value != keyDownTextColor)
{
keyDownTextColor = value;
foreach (var child in keyFlow)
child.KeyDownTextColor = value;
}
}
}
private Color4 keyUpTextColor = Color4.White;
public Color4 KeyUpTextColor
{
get => keyUpTextColor;
set
{
if (value != keyUpTextColor)
{
keyUpTextColor = value;
foreach (var child in keyFlow)
child.KeyUpTextColor = value;
}
}
}
}
}

View 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 osu.Framework.Graphics;
namespace osu.Game.Screens.Play.HUD
{
/// <summary>
/// An event trigger which can be used with <see cref="KeyCounter"/> to create visual tracking of button/key presses.
/// </summary>
public abstract partial class InputTrigger : Component
{
/// <summary>
/// Callback to invoke when the associated input has been activated.
/// </summary>
/// <param name="forwardPlayback">Whether gameplay is progressing in the forward direction time-wise.</param>
public delegate void OnActivateCallback(bool forwardPlayback);
/// <summary>
/// Callback to invoke when the associated input has been deactivated.
/// </summary>
/// <param name="forwardPlayback">Whether gameplay is progressing in the forward direction time-wise.</param>
public delegate void OnDeactivateCallback(bool forwardPlayback);
public event OnActivateCallback? OnActivate;
public event OnDeactivateCallback? OnDeactivate;
protected InputTrigger(string name)
{
Name = name;
}
protected void Activate(bool forwardPlayback = true) => OnActivate?.Invoke(forwardPlayback);
protected void Deactivate(bool forwardPlayback = true) => OnDeactivate?.Invoke(forwardPlayback);
}
}

View File

@ -0,0 +1,98 @@
// 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;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Screens.Play.HUD
{
/// <summary>
/// An individual key display which is intended to be displayed within a <see cref="KeyCounterDisplay"/>.
/// </summary>
public abstract partial class KeyCounter : Container
{
/// <summary>
/// The <see cref="InputTrigger"/> which activates and deactivates this <see cref="KeyCounter"/>.
/// </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;
/// <summary>
/// Whether this <see cref="KeyCounter"/> is currently in the "activated" state because the associated key is currently pressed.
/// </summary>
protected readonly Bindable<bool> IsActive = new BindableBool();
protected KeyCounter(InputTrigger trigger)
{
InternalChildren = new Drawable[]
{
content = new Container
{
RelativeSizeAxes = Axes.Both
},
Trigger = trigger,
};
Trigger.OnActivate += Activate;
Trigger.OnDeactivate += Deactivate;
Name = trigger.Name;
}
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;
}
}
}

View File

@ -1,18 +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.
#nullable disable
using System.Collections.Generic;
namespace osu.Game.Screens.Play
namespace osu.Game.Screens.Play.HUD
{
public partial class KeyCounterAction<T> : KeyCounter
public partial class KeyCounterActionTrigger<T> : InputTrigger
where T : struct
{
public T Action { get; }
public KeyCounterAction(T action)
public KeyCounterActionTrigger(T action)
: base($"B{(int)(object)action + 1}")
{
Action = action;
@ -23,9 +21,7 @@ namespace osu.Game.Screens.Play
if (!EqualityComparer<T>.Default.Equals(action, Action))
return false;
IsLit = true;
if (forwards)
Increment();
Activate(forwards);
return false;
}
@ -34,9 +30,7 @@ namespace osu.Game.Screens.Play
if (!EqualityComparer<T>.Default.Equals(action, Action))
return;
IsLit = false;
if (!forwards)
Decrement();
Deactivate(forwards);
}
}
}

View File

@ -0,0 +1,109 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.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;
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
{
/// <summary>
/// Whether the key counter should be visible regardless of the configuration value.
/// This is true by default, but can be changed.
/// </summary>
public Bindable<bool> AlwaysVisible { get; } = new Bindable<bool>(true);
/// <summary>
/// The <see cref="KeyCounter"/>s contained in this <see cref="KeyCounterDisplay"/>.
/// </summary>
public abstract IEnumerable<KeyCounter> Counters { 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>();
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 abstract void Add(InputTrigger trigger);
/// <summary>
/// Add a range of <see cref="InputTrigger"/> to this display.
/// </summary>
public void AddRange(IEnumerable<InputTrigger> triggers) => triggers.ForEach(Add);
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
config.BindWith(OsuSetting.KeyOverlay, ConfigVisibility);
}
protected override void LoadComplete()
{
base.LoadComplete();
AlwaysVisible.BindValueChanged(_ => UpdateVisibility());
ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true);
}
public override bool HandleNonPositionalInput => receptor == null;
public override bool HandlePositionalInput => receptor == null;
public partial class Receptor : Drawable
{
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);
}
}
}
}

View File

@ -1,18 +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.
#nullable disable
using osu.Framework.Input.Events;
using osuTK.Input;
namespace osu.Game.Screens.Play
namespace osu.Game.Screens.Play.HUD
{
public partial class KeyCounterKeyboard : KeyCounter
public partial class KeyCounterKeyboardTrigger : InputTrigger
{
public Key Key { get; }
public KeyCounterKeyboard(Key key)
public KeyCounterKeyboardTrigger(Key key)
: base(key.ToString())
{
Key = key;
@ -22,8 +20,7 @@ namespace osu.Game.Screens.Play
{
if (e.Key == Key)
{
IsLit = true;
Increment();
Activate();
}
return base.OnKeyDown(e);
@ -31,7 +28,9 @@ namespace osu.Game.Screens.Play
protected override void OnKeyUp(KeyUpEvent e)
{
if (e.Key == Key) IsLit = false;
if (e.Key == Key)
Deactivate();
base.OnKeyUp(e);
}
}

View File

@ -1,19 +1,17 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using osu.Framework.Input.Events;
using osuTK.Input;
using osuTK;
using osuTK.Input;
namespace osu.Game.Screens.Play
namespace osu.Game.Screens.Play.HUD
{
public partial class KeyCounterMouse : KeyCounter
public partial class KeyCounterMouseTrigger : InputTrigger
{
public MouseButton Button { get; }
public KeyCounterMouse(MouseButton button)
public KeyCounterMouseTrigger(MouseButton button)
: base(getStringRepresentation(button))
{
Button = button;
@ -39,17 +37,16 @@ namespace osu.Game.Screens.Play
protected override bool OnMouseDown(MouseDownEvent e)
{
if (e.Button == Button)
{
IsLit = true;
Increment();
}
Activate();
return base.OnMouseDown(e);
}
protected override void OnMouseUp(MouseUpEvent e)
{
if (e.Button == Button) IsLit = false;
if (e.Button == Button)
Deactivate();
base.OnMouseUp(e);
}
}

View File

@ -331,7 +331,7 @@ namespace osu.Game.Screens.Play
ShowHealth = { BindTarget = ShowHealthBar }
};
protected KeyCounterDisplay CreateKeyCounter() => new KeyCounterDisplay
protected KeyCounterDisplay CreateKeyCounter() => new DefaultKeyCounterDisplay
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,

View File

@ -1,172 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
#nullable disable
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Configuration;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Play
{
public partial class KeyCounterDisplay : Container<KeyCounter>
{
private const int duration = 100;
private const double key_fade_time = 80;
private readonly Bindable<bool> configVisibility = new Bindable<bool>();
protected readonly FillFlowContainer<KeyCounter> KeyFlow;
protected override Container<KeyCounter> Content => KeyFlow;
/// <summary>
/// Whether the key counter should be visible regardless of the configuration value.
/// This is true by default, but can be changed.
/// </summary>
public readonly Bindable<bool> AlwaysVisible = new Bindable<bool>(true);
public KeyCounterDisplay()
{
InternalChild = KeyFlow = new FillFlowContainer<KeyCounter>
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
Alpha = 0,
};
}
protected override void Update()
{
base.Update();
// Don't use autosize as it will shrink to zero when KeyFlow is hidden.
// In turn this can cause the display to be masked off screen and never become visible again.
Size = KeyFlow.Size;
}
public override void Add(KeyCounter key)
{
ArgumentNullException.ThrowIfNull(key);
base.Add(key);
key.IsCounting = IsCounting;
key.FadeTime = key_fade_time;
key.KeyDownTextColor = KeyDownTextColor;
key.KeyUpTextColor = KeyUpTextColor;
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
config.BindWith(OsuSetting.KeyOverlay, configVisibility);
}
protected override void LoadComplete()
{
base.LoadComplete();
AlwaysVisible.BindValueChanged(_ => updateVisibility());
configVisibility.BindValueChanged(_ => updateVisibility(), true);
}
private bool isCounting = true;
public bool IsCounting
{
get => isCounting;
set
{
if (value == isCounting) return;
isCounting = value;
foreach (var child in Children)
child.IsCounting = value;
}
}
private Color4 keyDownTextColor = Color4.DarkGray;
public Color4 KeyDownTextColor
{
get => keyDownTextColor;
set
{
if (value != keyDownTextColor)
{
keyDownTextColor = value;
foreach (var child in Children)
child.KeyDownTextColor = value;
}
}
}
private Color4 keyUpTextColor = Color4.White;
public Color4 KeyUpTextColor
{
get => keyUpTextColor;
set
{
if (value != keyUpTextColor)
{
keyUpTextColor = value;
foreach (var child in Children)
child.KeyUpTextColor = value;
}
}
}
private void updateVisibility() =>
// Isolate changing visibility of the key counters from fading this component.
KeyFlow.FadeTo(AlwaysVisible.Value || configVisibility.Value ? 1 : 0, duration);
public override bool HandleNonPositionalInput => receptor == null;
public override bool HandlePositionalInput => receptor == null;
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;
}
public partial class Receptor : Drawable
{
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.Children.Any(c => c.TriggerEvent(e));
}
return base.Handle(e);
}
}
}
}

View File

@ -437,8 +437,11 @@ namespace osu.Game.Screens.Play
},
KeyCounter =
{
IsCounting =
{
Value = false
},
AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded },
IsCounting = false
},
Anchor = Anchor.Centre,
Origin = Anchor.Centre
@ -478,7 +481,7 @@ namespace osu.Game.Screens.Play
{
updateGameplayState();
updatePauseOnFocusLostState();
HUDOverlay.KeyCounter.IsCounting = !isBreakTime.NewValue;
HUDOverlay.KeyCounter.IsCounting.Value = !isBreakTime.NewValue;
}
private void updateGameplayState()