diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index c8227c0887..2482be90ee 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -103,6 +103,9 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Alt, InputKey.Up }, GlobalAction.IncreaseVolume), new KeyBinding(new[] { InputKey.Alt, InputKey.Down }, GlobalAction.DecreaseVolume), + new KeyBinding(new[] { InputKey.Alt, InputKey.Left }, GlobalAction.PreviousVolumeMeter), + new KeyBinding(new[] { InputKey.Alt, InputKey.Right }, GlobalAction.NextVolumeMeter), + new KeyBinding(new[] { InputKey.Control, InputKey.F4 }, GlobalAction.ToggleMute), new KeyBinding(InputKey.TrackPrevious, GlobalAction.MusicPrev), @@ -263,5 +266,11 @@ namespace osu.Game.Input.Bindings [Description("Toggle skin editor")] ToggleSkinEditor, + + [Description("Previous volume meter")] + PreviousVolumeMeter, + + [Description("Next volume meter")] + NextVolumeMeter, } } diff --git a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs index ae9c2eb394..b24214ff3d 100644 --- a/osu.Game/Overlays/Volume/VolumeControlReceptor.cs +++ b/osu.Game/Overlays/Volume/VolumeControlReceptor.cs @@ -30,6 +30,8 @@ namespace osu.Game.Overlays.Volume return true; case GlobalAction.ToggleMute: + case GlobalAction.NextVolumeMeter: + case GlobalAction.PreviousVolumeMeter: ActionRequested?.Invoke(action); return true; } diff --git a/osu.Game/Overlays/Volume/VolumeMeter.cs b/osu.Game/Overlays/Volume/VolumeMeter.cs index 532b0f4a81..df8f1b7012 100644 --- a/osu.Game/Overlays/Volume/VolumeMeter.cs +++ b/osu.Game/Overlays/Volume/VolumeMeter.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Transforms; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; @@ -27,6 +28,11 @@ namespace osu.Game.Overlays.Volume { public class VolumeMeter : Container, IKeyBindingHandler { + [Resolved(canBeNull: true)] + private Bindable focusedMeter { get; set; } + + private bool isFocused => focusedMeter == null || focusedMeter.Value == this; + private CircularProgress volumeCircle; private CircularProgress volumeCircleGlow; @@ -41,6 +47,8 @@ namespace osu.Game.Overlays.Volume private Sample sample; private double sampleLastPlaybackTime; + public Action RequestFocus; + public VolumeMeter(string name, float circleSize, Color4 meterColour) { this.circleSize = circleSize; @@ -310,20 +318,32 @@ namespace osu.Game.Overlays.Volume private const float transition_length = 500; + public void Focus() + { + if (focusedMeter != null) + focusedMeter.Value = this; + + this.ScaleTo(1.04f, transition_length, Easing.OutExpo); + } + + public void Unfocus() + { + this.ScaleTo(1f, transition_length, Easing.OutExpo); + } + protected override bool OnHover(HoverEvent e) { - this.ScaleTo(1.04f, transition_length, Easing.OutExpo); + Focus(); return false; } protected override void OnHoverLost(HoverLostEvent e) { - this.ScaleTo(1f, transition_length, Easing.OutExpo); } public bool OnPressed(GlobalAction action) { - if (!IsHovered) + if (!IsHovered || !isFocused) return false; switch (action) diff --git a/osu.Game/Overlays/VolumeOverlay.cs b/osu.Game/Overlays/VolumeOverlay.cs index eb639431ae..750d54e091 100644 --- a/osu.Game/Overlays/VolumeOverlay.cs +++ b/osu.Game/Overlays/VolumeOverlay.cs @@ -19,6 +19,7 @@ using osuTK.Graphics; namespace osu.Game.Overlays { + [Cached] public class VolumeOverlay : VisibilityContainer { private const float offset = 10; @@ -32,6 +33,8 @@ namespace osu.Game.Overlays public Bindable IsMuted { get; } = new Bindable(); + private FillFlowContainer volumeMeters; + [BackgroundDependencyLoader] private void load(AudioManager audio, OsuColour colours) { @@ -53,7 +56,7 @@ namespace osu.Game.Overlays Margin = new MarginPadding(10), Current = { BindTarget = IsMuted } }, - new FillFlowContainer + volumeMeters = new FillFlowContainer { Direction = FillDirection.Vertical, AutoSizeAxes = Axes.Both, @@ -61,7 +64,7 @@ namespace osu.Game.Overlays Origin = Anchor.CentreLeft, Spacing = new Vector2(0, offset), Margin = new MarginPadding { Left = offset }, - Children = new Drawable[] + Children = new VolumeMeter[] { volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker), volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker), @@ -81,8 +84,13 @@ namespace osu.Game.Overlays else audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); }); + + focusedMeter.BindValueChanged(meter => meter.OldValue?.Unfocus()); } + [Cached] + private Bindable focusedMeter = new Bindable(); + protected override void LoadComplete() { base.LoadComplete(); @@ -93,6 +101,23 @@ namespace osu.Game.Overlays muteButton.Current.ValueChanged += _ => Show(); } + public bool HandleAction(GlobalAction action) + { + if (!IsLoaded) return false; + + switch (action) + { + case GlobalAction.DecreaseVolume: + case GlobalAction.IncreaseVolume: + return Adjust(action); + case GlobalAction.NextVolumeMeter: + return true; + + } + + return true; + } + public bool Adjust(GlobalAction action, float amount = 1, bool isPrecise = false) { if (!IsLoaded) return false; @@ -102,25 +127,32 @@ namespace osu.Game.Overlays case GlobalAction.DecreaseVolume: if (State.Value == Visibility.Hidden) Show(); - else if (volumeMeterMusic.IsHovered) - volumeMeterMusic.Decrease(amount, isPrecise); - else if (volumeMeterEffect.IsHovered) - volumeMeterEffect.Decrease(amount, isPrecise); else - volumeMeterMaster.Decrease(amount, isPrecise); + focusedMeter.Value.Decrease(amount, isPrecise); return true; case GlobalAction.IncreaseVolume: if (State.Value == Visibility.Hidden) Show(); - else if (volumeMeterMusic.IsHovered) - volumeMeterMusic.Increase(amount, isPrecise); - else if (volumeMeterEffect.IsHovered) - volumeMeterEffect.Increase(amount, isPrecise); else - volumeMeterMaster.Increase(amount, isPrecise); + focusedMeter.Value.Increase(amount, isPrecise); return true; + case GlobalAction.NextVolumeMeter: + if (State.Value == Visibility.Hidden) + Show(); + else + focusShift(1); + return true; + + case GlobalAction.PreviousVolumeMeter: + if (State.Value == Visibility.Hidden) + Show(); + else + focusShift(-1); + return true; + + case GlobalAction.ToggleMute: Show(); muteButton.Current.Value = !muteButton.Current.Value; @@ -130,10 +162,23 @@ namespace osu.Game.Overlays return false; } + private void focusShift(int direction = 1) + { + Show(); + var newIndex = volumeMeters.IndexOf(focusedMeter.Value) + direction; + if (newIndex < 0) + newIndex += volumeMeters.Count; + + volumeMeters.Children[newIndex % volumeMeters.Count].Focus(); + } + private ScheduledDelegate popOutDelegate; public override void Show() { + if (State.Value == Visibility.Hidden) + volumeMeterMaster.Focus(); + if (State.Value == Visibility.Visible) schedulePopOut();