1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-06 03:02:59 +08:00

Share item cycling logic with GameplayMenuOverlay

This commit is contained in:
Derrick Timmermans 2021-07-05 19:22:55 +02:00
parent 3fe875efb2
commit d495196b66
No known key found for this signature in database
GPG Key ID: 8681B60806EF4A17
7 changed files with 174 additions and 149 deletions

View File

@ -87,7 +87,7 @@ namespace osu.Game.Tests.Visual.Gameplay
showOverlay(); showOverlay();
AddStep("Up arrow", () => InputManager.Key(Key.Up)); AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value); AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected);
} }
/// <summary> /// <summary>
@ -99,7 +99,7 @@ namespace osu.Game.Tests.Visual.Gameplay
showOverlay(); showOverlay();
AddStep("Down arrow", () => InputManager.Key(Key.Down)); AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("First button selected", () => getButton(0).Selected.Value); AddAssert("First button selected", () => getButton(0).Selected);
} }
/// <summary> /// <summary>
@ -111,11 +111,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Show overlay", () => failOverlay.Show()); AddStep("Show overlay", () => failOverlay.Show());
AddStep("Up arrow", () => InputManager.Key(Key.Up)); AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
AddStep("Up arrow", () => InputManager.Key(Key.Up)); AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
AddStep("Up arrow", () => InputManager.Key(Key.Up)); AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
} }
/// <summary> /// <summary>
@ -127,11 +127,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Show overlay", () => failOverlay.Show()); AddStep("Show overlay", () => failOverlay.Show());
AddStep("Down arrow", () => InputManager.Key(Key.Down)); AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
AddStep("Down arrow", () => InputManager.Key(Key.Down)); AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value); AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected);
AddStep("Down arrow", () => InputManager.Key(Key.Down)); AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value); AddAssert("First button selected", () => failOverlay.Buttons.First().Selected);
} }
/// <summary> /// <summary>
@ -145,7 +145,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First())); AddStep("Hover first button", () => InputManager.MoveMouseTo(failOverlay.Buttons.First()));
AddStep("Hide overlay", () => failOverlay.Hide()); AddStep("Hide overlay", () => failOverlay.Hide());
AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected.Value)); AddAssert("Overlay state is reset", () => !failOverlay.Buttons.Any(b => b.Selected));
} }
/// <summary> /// <summary>
@ -162,11 +162,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Hide overlay", () => pauseOverlay.Hide()); AddStep("Hide overlay", () => pauseOverlay.Hide());
showOverlay(); showOverlay();
AddAssert("First button not selected", () => !getButton(0).Selected.Value); AddAssert("First button not selected", () => !getButton(0).Selected);
AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1))); AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1)));
AddAssert("First button selected", () => getButton(0).Selected.Value); AddAssert("First button selected", () => getButton(0).Selected);
} }
/// <summary> /// <summary>
@ -179,8 +179,8 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Down arrow", () => InputManager.Key(Key.Down)); AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddAssert("First button not selected", () => !getButton(0).Selected.Value); AddAssert("First button not selected", () => !getButton(0).Selected);
AddAssert("Second button selected", () => getButton(1).Selected.Value); AddAssert("Second button selected", () => getButton(1).Selected);
} }
/// <summary> /// <summary>
@ -196,8 +196,8 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Up arrow", () => InputManager.Key(Key.Up)); AddStep("Up arrow", () => InputManager.Key(Key.Up));
AddAssert("Second button not selected", () => !getButton(1).Selected.Value); AddAssert("Second button not selected", () => !getButton(1).Selected);
AddAssert("First button selected", () => getButton(0).Selected.Value); AddAssert("First button selected", () => getButton(0).Selected);
} }
/// <summary> /// <summary>
@ -211,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1))); AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero)); AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero));
AddStep("Down arrow", () => InputManager.Key(Key.Down)); AddStep("Down arrow", () => InputManager.Key(Key.Down));
AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition AddAssert("First button selected", () => getButton(0).Selected); // Initial state condition
} }
/// <summary> /// <summary>
@ -282,7 +282,7 @@ namespace osu.Game.Tests.Visual.Gameplay
showOverlay(); showOverlay();
AddAssert("No button selected", AddAssert("No button selected",
() => pauseOverlay.Buttons.All(button => !button.Selected.Value)); () => pauseOverlay.Buttons.All(button => !button.Selected));
} }
private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show()); private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show());

View File

@ -0,0 +1,55 @@
// 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;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Graphics.Containers
{
/// <summary>
/// A FillFlowContainer that provides functionality to cycle selection between children
/// The selection wraps around when overflowing past the first or last child.
/// </summary>
public class SelectionCycleFillFlowContainer<T> : FillFlowContainer<T> where T : Drawable, ISelectable
{
private int selectedIndex = -1;
private void setSelected(int value)
{
if (selectedIndex == value)
return;
// Deselect the previously-selected button
if (selectedIndex != -1)
this[selectedIndex].Selected = false;
selectedIndex = value;
// Select the newly-selected button
if (selectedIndex != -1)
this[selectedIndex].Selected = true;
}
public void SelectNext()
{
if (selectedIndex == -1 || selectedIndex == Count - 1)
setSelected(0);
else
setSelected(selectedIndex + 1);
}
public void SelectPrevious()
{
if (selectedIndex == -1 || selectedIndex == 0)
setSelected(Count - 1);
else
setSelected(selectedIndex - 1);
}
public void Deselect() => setSelected(-1);
public void Select(T item) => setSelected(IndexOf(item));
public T Selected => (selectedIndex >= 0 && selectedIndex < Count) ? this[selectedIndex] : null;
}
}

View File

@ -2,24 +2,24 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osuTK; using osu.Framework.Extensions.Color4Extensions;
using osuTK.Graphics;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Sprites;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics.Effects;
using osu.Game.Graphics.Containers;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Graphics.Backgrounds;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface
{ {
public class DialogButton : OsuClickableContainer public class DialogButton : OsuClickableContainer, ISelectable
{ {
private const float idle_width = 0.8f; private const float idle_width = 0.8f;
private const float hover_width = 0.9f; private const float hover_width = 0.9f;
@ -27,7 +27,13 @@ namespace osu.Game.Graphics.UserInterface
private const float hover_duration = 500; private const float hover_duration = 500;
private const float click_duration = 200; private const float click_duration = 200;
public readonly BindableBool Selected = new BindableBool(); public readonly BindableBool SelectedBindable = new BindableBool();
public bool Selected
{
get => SelectedBindable.Value;
set => SelectedBindable.Value = value;
}
private readonly Container backgroundContainer; private readonly Container backgroundContainer;
private readonly Container colourContainer; private readonly Container colourContainer;
@ -153,7 +159,7 @@ namespace osu.Game.Graphics.UserInterface
updateGlow(); updateGlow();
Selected.ValueChanged += selectionChanged; SelectedBindable.ValueChanged += selectionChanged;
} }
private Color4 buttonColour; private Color4 buttonColour;
@ -221,7 +227,7 @@ namespace osu.Game.Graphics.UserInterface
.OnComplete(_ => .OnComplete(_ =>
{ {
clickAnimating = false; clickAnimating = false;
Selected.TriggerChange(); SelectedBindable.TriggerChange();
}); });
return base.OnClick(e); return base.OnClick(e);
@ -235,7 +241,7 @@ namespace osu.Game.Graphics.UserInterface
protected override void OnMouseUp(MouseUpEvent e) protected override void OnMouseUp(MouseUpEvent e)
{ {
if (Selected.Value) if (Selected)
colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In); colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In);
base.OnMouseUp(e); base.OnMouseUp(e);
} }
@ -243,7 +249,7 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {
base.OnHover(e); base.OnHover(e);
Selected.Value = true; Selected = true;
return true; return true;
} }
@ -251,7 +257,7 @@ namespace osu.Game.Graphics.UserInterface
protected override void OnHoverLost(HoverLostEvent e) protected override void OnHoverLost(HoverLostEvent e)
{ {
base.OnHoverLost(e); base.OnHoverLost(e);
Selected.Value = false; Selected = false;
} }
private void selectionChanged(ValueChangedEvent<bool> args) private void selectionChanged(ValueChangedEvent<bool> args)

View File

@ -0,0 +1,10 @@
// 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.
namespace osu.Game.Graphics.UserInterface
{
public interface ISelectable
{
bool Selected { get; set; }
}
}

View File

@ -19,13 +19,14 @@ using osu.Framework.Threading;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Overlays.Volume namespace osu.Game.Overlays.Volume
{ {
public class VolumeMeter : Container, IKeyBindingHandler<GlobalAction> public class VolumeMeter : Container, IKeyBindingHandler<GlobalAction>, ISelectable
{ {
private CircularProgress volumeCircle; private CircularProgress volumeCircle;
private CircularProgress volumeCircleGlow; private CircularProgress volumeCircleGlow;
@ -43,7 +44,13 @@ namespace osu.Game.Overlays.Volume
private Sample sample; private Sample sample;
private double sampleLastPlaybackTime; private double sampleLastPlaybackTime;
public Action<VolumeMeter> RequestFocus; public BindableBool SelectedBindable = new BindableBool();
public bool Selected
{
get => SelectedBindable.Value;
set => SelectedBindable.Value = value;
}
public VolumeMeter(string name, float circleSize, Color4 meterColour) public VolumeMeter(string name, float circleSize, Color4 meterColour)
{ {
@ -212,6 +219,8 @@ namespace osu.Game.Overlays.Volume
Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true); Bindable.BindValueChanged(volume => { this.TransformTo(nameof(DisplayVolume), volume.NewValue, 400, Easing.OutQuint); }, true);
bgProgress.Current.Value = 0.75f; bgProgress.Current.Value = 0.75f;
SelectedBindable.ValueChanged += selectionChanged;
} }
private int? displayVolumeInt; private int? displayVolumeInt;
@ -330,21 +339,9 @@ namespace osu.Game.Overlays.Volume
private const float transition_length = 500; private const float transition_length = 500;
public void Focus()
{
this.ScaleTo(1.04f, transition_length, Easing.OutExpo);
focusGlowContainer.FadeIn(transition_length, Easing.OutExpo);
}
public void Unfocus()
{
this.ScaleTo(1f, transition_length, Easing.OutExpo);
focusGlowContainer.FadeOut(transition_length, Easing.OutExpo);
}
protected override bool OnHover(HoverEvent e) protected override bool OnHover(HoverEvent e)
{ {
RequestFocus?.Invoke(this); Selected = true;
return false; return false;
} }
@ -352,6 +349,20 @@ namespace osu.Game.Overlays.Volume
{ {
} }
private void selectionChanged(ValueChangedEvent<bool> selected)
{
if (selected.NewValue)
{
this.ScaleTo(1.04f, transition_length, Easing.OutExpo);
focusGlowContainer.FadeIn(transition_length, Easing.OutExpo);
}
else
{
this.ScaleTo(1f, transition_length, Easing.OutExpo);
focusGlowContainer.FadeOut(transition_length, Easing.OutExpo);
}
}
public bool OnPressed(GlobalAction action) public bool OnPressed(GlobalAction action)
{ {
if (!IsHovered) if (!IsHovered)
@ -360,12 +371,12 @@ namespace osu.Game.Overlays.Volume
switch (action) switch (action)
{ {
case GlobalAction.SelectPrevious: case GlobalAction.SelectPrevious:
RequestFocus?.Invoke(this); Selected = true;
adjust(1, false); adjust(1, false);
return true; return true;
case GlobalAction.SelectNext: case GlobalAction.SelectNext:
RequestFocus?.Invoke(this); Selected = true;
adjust(-1, false); adjust(-1, false);
return true; return true;
} }

View File

@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Overlays.Volume; using osu.Game.Overlays.Volume;
using osuTK; using osuTK;
@ -32,7 +33,7 @@ namespace osu.Game.Overlays
public Bindable<bool> IsMuted { get; } = new Bindable<bool>(); public Bindable<bool> IsMuted { get; } = new Bindable<bool>();
private FillFlowContainer<VolumeMeter> volumeMeters; private SelectionCycleFillFlowContainer<VolumeMeter> volumeMeters;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio, OsuColour colours) private void load(AudioManager audio, OsuColour colours)
@ -55,7 +56,7 @@ namespace osu.Game.Overlays
Margin = new MarginPadding(10), Margin = new MarginPadding(10),
Current = { BindTarget = IsMuted } Current = { BindTarget = IsMuted }
}, },
volumeMeters = new FillFlowContainer<VolumeMeter> volumeMeters = new SelectionCycleFillFlowContainer<VolumeMeter>
{ {
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
@ -65,18 +66,9 @@ namespace osu.Game.Overlays
Margin = new MarginPadding { Left = offset }, Margin = new MarginPadding { Left = offset },
Children = new[] Children = new[]
{ {
volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker) volumeMeterEffect = new VolumeMeter("EFFECTS", 125, colours.BlueDarker),
{ volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker),
RequestFocus = v => focusedMeter.Value = v volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker),
},
volumeMeterMaster = new VolumeMeter("MASTER", 150, colours.PinkDarker)
{
RequestFocus = v => focusedMeter.Value = v
},
volumeMeterMusic = new VolumeMeter("MUSIC", 125, colours.BlueDarker)
{
RequestFocus = v => focusedMeter.Value = v
},
} }
} }
}); });
@ -92,23 +84,18 @@ namespace osu.Game.Overlays
else else
audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment);
}); });
focusedMeter.BindValueChanged(meter =>
{
meter.OldValue?.Unfocus();
meter.NewValue?.Focus();
});
} }
private readonly Bindable<VolumeMeter> focusedMeter = new Bindable<VolumeMeter>();
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
volumeMeterMaster.Bindable.ValueChanged += _ => Show(); foreach (var volumeMeter in volumeMeters)
volumeMeterEffect.Bindable.ValueChanged += _ => Show(); {
volumeMeterMusic.Bindable.ValueChanged += _ => Show(); volumeMeter.Bindable.ValueChanged += _ => Show();
volumeMeter.SelectedBindable.ValueChanged += selected => volumeMeterSelectionChanged(volumeMeter, selected.NewValue);
}
muteButton.Current.ValueChanged += _ => Show(); muteButton.Current.ValueChanged += _ => Show();
} }
@ -122,28 +109,26 @@ namespace osu.Game.Overlays
if (State.Value == Visibility.Hidden) if (State.Value == Visibility.Hidden)
Show(); Show();
else else
focusedMeter.Value.Decrease(amount, isPrecise); volumeMeters.Selected?.Decrease(amount, isPrecise);
return true; return true;
case GlobalAction.IncreaseVolume: case GlobalAction.IncreaseVolume:
if (State.Value == Visibility.Hidden) if (State.Value == Visibility.Hidden)
Show(); Show();
else else
focusedMeter.Value.Increase(amount, isPrecise); volumeMeters.Selected?.Increase(amount, isPrecise);
return true; return true;
case GlobalAction.NextVolumeMeter: case GlobalAction.NextVolumeMeter:
if (State.Value == Visibility.Hidden) if (State.Value == Visibility.Visible)
volumeMeters.SelectNext();
Show(); Show();
else
focusShift(1);
return true; return true;
case GlobalAction.PreviousVolumeMeter: case GlobalAction.PreviousVolumeMeter:
if (State.Value == Visibility.Hidden) if (State.Value == Visibility.Visible)
volumeMeters.SelectPrevious();
Show(); Show();
else
focusShift(-1);
return true; return true;
case GlobalAction.ToggleMute: case GlobalAction.ToggleMute:
@ -155,15 +140,12 @@ namespace osu.Game.Overlays
return false; return false;
} }
private void focusShift(int direction = 1) private void volumeMeterSelectionChanged(VolumeMeter meter, bool isSelected)
{ {
Show(); if (!isSelected)
volumeMeters.Deselect();
var newIndex = volumeMeters.IndexOf(focusedMeter.Value) + direction; else
if (newIndex < 0) volumeMeters.Select(meter);
newIndex += volumeMeters.Count;
focusedMeter.Value = volumeMeters.Children[newIndex % volumeMeters.Count];
} }
private ScheduledDelegate popOutDelegate; private ScheduledDelegate popOutDelegate;
@ -172,7 +154,7 @@ namespace osu.Game.Overlays
{ {
// Focus on the master meter as a default if previously hidden // Focus on the master meter as a default if previously hidden
if (State.Value == Visibility.Hidden) if (State.Value == Visibility.Hidden)
focusedMeter.Value = volumeMeterMaster; volumeMeters.Select(volumeMeterMaster);
if (State.Value == Visibility.Visible) if (State.Value == Visibility.Visible)
schedulePopOut(); schedulePopOut();

View File

@ -2,23 +2,24 @@
// See the LICENCE file in the repository root for full licence text. // See the LICENCE file in the repository root for full licence text.
using System; using System;
using System.Collections.Generic;
using System.Linq;
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Framework.Graphics.Effects;
using osuTK;
using osuTK.Graphics;
using osu.Game.Graphics;
using osu.Framework.Allocation;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using Humanizer; using osuTK;
using osu.Framework.Graphics.Effects; using osuTK.Graphics;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
{ {
@ -41,18 +42,18 @@ namespace osu.Game.Screens.Play
/// <summary> /// <summary>
/// Action that is invoked when <see cref="GlobalAction.Back"/> is triggered. /// Action that is invoked when <see cref="GlobalAction.Back"/> is triggered.
/// </summary> /// </summary>
protected virtual Action BackAction => () => InternalButtons.Children.LastOrDefault()?.Click(); protected virtual Action BackAction => () => InternalButtons.Selected?.Click();
/// <summary> /// <summary>
/// Action that is invoked when <see cref="GlobalAction.Select"/> is triggered. /// Action that is invoked when <see cref="GlobalAction.Select"/> is triggered.
/// </summary> /// </summary>
protected virtual Action SelectAction => () => InternalButtons.Children.FirstOrDefault(f => f.Selected.Value)?.Click(); protected virtual Action SelectAction => () => InternalButtons.Selected?.Click();
public abstract string Header { get; } public abstract string Header { get; }
public abstract string Description { get; } public abstract string Description { get; }
protected ButtonContainer InternalButtons; protected SelectionCycleFillFlowContainer<DialogButton> InternalButtons;
public IReadOnlyList<DialogButton> Buttons => InternalButtons; public IReadOnlyList<DialogButton> Buttons => InternalButtons;
private FillFlowContainer retryCounterContainer; private FillFlowContainer retryCounterContainer;
@ -116,7 +117,7 @@ namespace osu.Game.Screens.Play
} }
} }
}, },
InternalButtons = new ButtonContainer InternalButtons = new SelectionCycleFillFlowContainer<DialogButton>
{ {
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre, Anchor = Anchor.TopCentre,
@ -183,7 +184,7 @@ namespace osu.Game.Screens.Play
} }
}; };
button.Selected.ValueChanged += selected => buttonSelectionChanged(button, selected.NewValue); button.SelectedBindable.ValueChanged += selected => buttonSelectionChanged(button, selected.NewValue);
InternalButtons.Add(button); InternalButtons.Add(button);
} }
@ -255,46 +256,6 @@ namespace osu.Game.Screens.Play
}; };
} }
protected class ButtonContainer : FillFlowContainer<DialogButton>
{
private int selectedIndex = -1;
private void setSelected(int value)
{
if (selectedIndex == value)
return;
// Deselect the previously-selected button
if (selectedIndex != -1)
this[selectedIndex].Selected.Value = false;
selectedIndex = value;
// Select the newly-selected button
if (selectedIndex != -1)
this[selectedIndex].Selected.Value = true;
}
public void SelectNext()
{
if (selectedIndex == -1 || selectedIndex == Count - 1)
setSelected(0);
else
setSelected(selectedIndex + 1);
}
public void SelectPrevious()
{
if (selectedIndex == -1 || selectedIndex == 0)
setSelected(Count - 1);
else
setSelected(selectedIndex - 1);
}
public void Deselect() => setSelected(-1);
public void Select(DialogButton button) => setSelected(IndexOf(button));
}
private class Button : DialogButton private class Button : DialogButton
{ {
// required to ensure keyboard navigation always starts from an extremity (unless the cursor is moved) // required to ensure keyboard navigation always starts from an extremity (unless the cursor is moved)
@ -302,7 +263,7 @@ namespace osu.Game.Screens.Play
protected override bool OnMouseMove(MouseMoveEvent e) protected override bool OnMouseMove(MouseMoveEvent e)
{ {
Selected.Value = true; Selected = true;
return base.OnMouseMove(e); return base.OnMouseMove(e);
} }
} }