1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-14 15:57:24 +08:00

Split KeyBindingRow out into partial pieces

This commit is contained in:
Dean Herbert 2023-10-13 18:29:06 +09:00
parent 8e609b6f1d
commit 9b0c4acdef
No known key found for this signature in database
3 changed files with 276 additions and 245 deletions

View File

@ -0,0 +1,75 @@
// 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 osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Bindings;
namespace osu.Game.Overlays.Settings.Sections.Input
{
public partial class KeyBindingRow : IHasPopover
{
private readonly Bindable<KeyBindingConflictInfo> keyBindingConflictInfo = new Bindable<KeyBindingConflictInfo>();
public Popover GetPopover() => new KeyBindingConflictPopover
{
ConflictInfo = { BindTarget = keyBindingConflictInfo },
BindingConflictResolved = () => BindingUpdated?.Invoke(this, new KeyBindingUpdatedEventArgs(bindingConflictResolved: true, canAdvanceToNextBinding: false))
};
private void showBindingConflictPopover(KeyBindingConflictInfo conflictInfo)
{
keyBindingConflictInfo.Value = conflictInfo;
this.ShowPopover();
}
/// <summary>
/// Contains information about the key binding conflict to be resolved.
/// </summary>
public class KeyBindingConflictInfo
{
public ConflictingKeyBinding Existing { get; }
public ConflictingKeyBinding New { get; }
/// <summary>
/// Contains information about the key binding conflict to be resolved.
/// </summary>
public KeyBindingConflictInfo(ConflictingKeyBinding existingBinding, ConflictingKeyBinding newBinding)
{
Existing = existingBinding;
New = newBinding;
}
}
public class ConflictingKeyBinding
{
public Guid ID { get; }
public object Action { get; }
public KeyCombination CombinationWhenChosen { get; }
public KeyCombination CombinationWhenNotChosen { get; }
public ConflictingKeyBinding(Guid id, object action, KeyCombination combinationWhenChosen, KeyCombination combinationWhenNotChosen)
{
ID = id;
Action = action;
CombinationWhenChosen = combinationWhenChosen;
CombinationWhenNotChosen = combinationWhenNotChosen;
}
}
public class KeyBindingUpdatedEventArgs
{
public bool BindingConflictResolved { get; }
public bool CanAdvanceToNextBinding { get; }
public KeyBindingUpdatedEventArgs(bool bindingConflictResolved, bool canAdvanceToNextBinding)
{
BindingConflictResolved = bindingConflictResolved;
CanAdvanceToNextBinding = canAdvanceToNextBinding;
}
}
}
}

View File

@ -0,0 +1,168 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input;
using osu.Game.Input.Bindings;
using osuTK.Graphics;
namespace osu.Game.Overlays.Settings.Sections.Input
{
public partial class KeyBindingRow
{
public partial class KeyButton : Container
{
public Bindable<RealmKeyBinding> KeyBinding { get; } = new Bindable<RealmKeyBinding>();
private readonly Box box;
public readonly OsuSpriteText Text;
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
[Resolved]
private ReadableKeyCombinationProvider keyCombinationProvider { get; set; } = null!;
private bool isBinding;
public bool IsBinding
{
get => isBinding;
set
{
if (value == isBinding) return;
isBinding = value;
updateHoverState();
}
}
public KeyButton()
{
Margin = new MarginPadding(padding);
Masking = true;
CornerRadius = padding;
Height = height;
AutoSizeAxes = Axes.X;
Children = new Drawable[]
{
new Container
{
AlwaysPresent = true,
Width = 80,
Height = height,
},
box = new Box
{
RelativeSizeAxes = Axes.Both,
},
Text = new OsuSpriteText
{
Font = OsuFont.Numeric.With(size: 10),
Margin = new MarginPadding(5),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new HoverSounds()
};
}
protected override void LoadComplete()
{
base.LoadComplete();
KeyBinding.BindValueChanged(_ =>
{
if (KeyBinding.Value.IsManaged)
throw new ArgumentException("Key binding should not be attached as we make temporary changes", nameof(KeyBinding));
updateKeyCombinationText();
});
keyCombinationProvider.KeymapChanged += updateKeyCombinationText;
updateKeyCombinationText();
}
[BackgroundDependencyLoader]
private void load()
{
updateHoverState();
FinishTransforms(true);
}
protected override bool OnHover(HoverEvent e)
{
updateHoverState();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
updateHoverState();
base.OnHoverLost(e);
}
private void updateHoverState()
{
if (isBinding)
{
box.FadeColour(colourProvider.Light2, transition_time, Easing.OutQuint);
Text.FadeColour(Color4.Black, transition_time, Easing.OutQuint);
}
else
{
box.FadeColour(IsHovered ? colourProvider.Light4 : colourProvider.Background6, transition_time, Easing.OutQuint);
Text.FadeColour(IsHovered ? Color4.Black : Color4.White, transition_time, Easing.OutQuint);
}
}
/// <summary>
/// Update from a key combination, only allowing a single non-modifier key to be specified.
/// </summary>
/// <param name="fullState">A <see cref="KeyCombination"/> generated from the full input state.</param>
/// <param name="triggerKey">The key which triggered this update, and should be used as the binding.</param>
public void UpdateKeyCombination(KeyCombination fullState, InputKey triggerKey) =>
UpdateKeyCombination(new KeyCombination(fullState.Keys.Where(KeyCombination.IsModifierKey).Append(triggerKey)));
public void UpdateKeyCombination(KeyCombination newCombination)
{
if (KeyBinding.Value.RulesetName != null && !RealmKeyBindingStore.CheckValidForGameplay(newCombination))
return;
KeyBinding.Value.KeyCombination = newCombination;
updateKeyCombinationText();
}
private void updateKeyCombinationText()
{
Scheduler.AddOnce(updateText);
void updateText() => Text.Text = keyCombinationProvider.GetReadableString(KeyBinding.Value.KeyCombination);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (keyCombinationProvider.IsNotNull())
keyCombinationProvider.KeymapChanged -= updateKeyCombinationText;
}
}
}
}

View File

@ -9,33 +9,27 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Input;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osuTK; using osuTK;
using osuTK.Graphics;
using osuTK.Input; using osuTK.Input;
namespace osu.Game.Overlays.Settings.Sections.Input namespace osu.Game.Overlays.Settings.Sections.Input
{ {
public partial class KeyBindingRow : Container, IFilterable, IHasPopover public partial class KeyBindingRow : Container, IFilterable
{ {
/// <summary> /// <summary>
/// Invoked when the binding of this row is updated with a change being written. /// Invoked when the binding of this row is updated with a change being written.
@ -210,18 +204,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input
}, true); }, true);
} }
private void updateButtons()
{
if (buttons.Count > KeyBindings.Count)
buttons.RemoveRange(buttons.Skip(KeyBindings.Count).ToArray(), true);
while (buttons.Count < KeyBindings.Count)
buttons.Add(new KeyButton());
foreach (var (button, binding) in buttons.Zip(KeyBindings))
button.KeyBinding.Value = binding;
}
public void RestoreDefaults() public void RestoreDefaults()
{ {
int i = 0; int i = 0;
@ -251,8 +233,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input
base.OnHoverLost(e); base.OnHoverLost(e);
} }
private bool isModifier(Key k) => k < Key.F1;
protected override bool OnClick(ClickEvent e) => true; protected override bool OnClick(ClickEvent e) => true;
protected override bool OnMouseDown(MouseDownEvent e) protected override bool OnMouseDown(MouseDownEvent e)
@ -325,6 +305,8 @@ namespace osu.Game.Overlays.Settings.Sections.Input
if (!isModifier(e.Key)) finalise(); if (!isModifier(e.Key)) finalise();
return true; return true;
bool isModifier(Key k) => k < Key.F1;
} }
protected override void OnKeyUp(KeyUpEvent e) protected override void OnKeyUp(KeyUpEvent e)
@ -434,6 +416,18 @@ namespace osu.Game.Overlays.Settings.Sections.Input
finalise(); finalise();
} }
private void updateButtons()
{
if (buttons.Count > KeyBindings.Count)
buttons.RemoveRange(buttons.Skip(KeyBindings.Count).ToArray(), true);
while (buttons.Count < KeyBindings.Count)
buttons.Add(new KeyButton());
foreach (var (button, binding) in buttons.Zip(KeyBindings))
button.KeyBinding.Value = binding;
}
private void clear() private void clear()
{ {
if (bindTarget == null) if (bindTarget == null)
@ -466,6 +460,24 @@ namespace osu.Game.Overlays.Settings.Sections.Input
cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y; cancelAndClearButtons.BypassAutoSizeAxes |= Axes.Y;
} }
protected override void OnFocus(FocusEvent e)
{
content.AutoSizeDuration = 500;
content.AutoSizeEasing = Easing.OutQuint;
cancelAndClearButtons.FadeIn(300, Easing.OutQuint);
cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y;
updateBindTarget();
base.OnFocus(e);
}
protected override void OnFocusLost(FocusLostEvent e)
{
finalise(false);
base.OnFocusLost(e);
}
private void tryPersistKeyBinding(RealmKeyBinding keyBinding, bool advanceToNextBinding) private void tryPersistKeyBinding(RealmKeyBinding keyBinding, bool advanceToNextBinding)
{ {
List<RealmKeyBinding> bindings = GetAllSectionBindings(); List<RealmKeyBinding> bindings = GetAllSectionBindings();
@ -488,24 +500,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input
new ConflictingKeyBinding(keyBindingBeforeUpdate.ID, Action, keyBinding.KeyCombination, keyBindingBeforeUpdate.KeyCombination))); new ConflictingKeyBinding(keyBindingBeforeUpdate.ID, Action, keyBinding.KeyCombination, keyBindingBeforeUpdate.KeyCombination)));
} }
protected override void OnFocus(FocusEvent e)
{
content.AutoSizeDuration = 500;
content.AutoSizeEasing = Easing.OutQuint;
cancelAndClearButtons.FadeIn(300, Easing.OutQuint);
cancelAndClearButtons.BypassAutoSizeAxes &= ~Axes.Y;
updateBindTarget();
base.OnFocus(e);
}
protected override void OnFocusLost(FocusLostEvent e)
{
finalise(false);
base.OnFocusLost(e);
}
/// <summary> /// <summary>
/// Updates the bind target to the currently hovered key button or the first if clicked anywhere else. /// Updates the bind target to the currently hovered key button or the first if clicked anywhere else.
/// </summary> /// </summary>
@ -521,70 +515,6 @@ namespace osu.Game.Overlays.Settings.Sections.Input
isDefault.Value = KeyBindings.Select(b => b.KeyCombination).SequenceEqual(Defaults); isDefault.Value = KeyBindings.Select(b => b.KeyCombination).SequenceEqual(Defaults);
} }
#region Handling conflicts
private readonly Bindable<KeyBindingConflictInfo> keyBindingConflictInfo = new Bindable<KeyBindingConflictInfo>();
public Popover GetPopover() => new KeyBindingConflictPopover
{
ConflictInfo = { BindTarget = keyBindingConflictInfo },
BindingConflictResolved = () => BindingUpdated?.Invoke(this, new KeyBindingUpdatedEventArgs(bindingConflictResolved: true, canAdvanceToNextBinding: false))
};
private void showBindingConflictPopover(KeyBindingConflictInfo conflictInfo)
{
keyBindingConflictInfo.Value = conflictInfo;
this.ShowPopover();
}
/// <summary>
/// Contains information about the key binding conflict to be resolved.
/// </summary>
public class KeyBindingConflictInfo
{
public ConflictingKeyBinding Existing { get; }
public ConflictingKeyBinding New { get; }
/// <summary>
/// Contains information about the key binding conflict to be resolved.
/// </summary>
public KeyBindingConflictInfo(ConflictingKeyBinding existingBinding, ConflictingKeyBinding newBinding)
{
Existing = existingBinding;
New = newBinding;
}
}
public class ConflictingKeyBinding
{
public Guid ID { get; }
public object Action { get; }
public KeyCombination CombinationWhenChosen { get; }
public KeyCombination CombinationWhenNotChosen { get; }
public ConflictingKeyBinding(Guid id, object action, KeyCombination combinationWhenChosen, KeyCombination combinationWhenNotChosen)
{
ID = id;
Action = action;
CombinationWhenChosen = combinationWhenChosen;
CombinationWhenNotChosen = combinationWhenNotChosen;
}
}
public class KeyBindingUpdatedEventArgs
{
public bool BindingConflictResolved { get; }
public bool CanAdvanceToNextBinding { get; }
public KeyBindingUpdatedEventArgs(bool bindingConflictResolved, bool canAdvanceToNextBinding)
{
BindingConflictResolved = bindingConflictResolved;
CanAdvanceToNextBinding = canAdvanceToNextBinding;
}
}
#endregion
private partial class CancelButton : RoundedButton private partial class CancelButton : RoundedButton
{ {
public CancelButton() public CancelButton()
@ -602,147 +532,5 @@ namespace osu.Game.Overlays.Settings.Sections.Input
Size = new Vector2(80, 20); Size = new Vector2(80, 20);
} }
} }
public partial class KeyButton : Container
{
public Bindable<RealmKeyBinding> KeyBinding { get; } = new Bindable<RealmKeyBinding>();
private readonly Box box;
public readonly OsuSpriteText Text;
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
[Resolved]
private ReadableKeyCombinationProvider keyCombinationProvider { get; set; } = null!;
private bool isBinding;
public bool IsBinding
{
get => isBinding;
set
{
if (value == isBinding) return;
isBinding = value;
updateHoverState();
}
}
public KeyButton()
{
Margin = new MarginPadding(padding);
Masking = true;
CornerRadius = padding;
Height = height;
AutoSizeAxes = Axes.X;
Children = new Drawable[]
{
new Container
{
AlwaysPresent = true,
Width = 80,
Height = height,
},
box = new Box
{
RelativeSizeAxes = Axes.Both,
},
Text = new OsuSpriteText
{
Font = OsuFont.Numeric.With(size: 10),
Margin = new MarginPadding(5),
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
},
new HoverSounds()
};
}
protected override void LoadComplete()
{
base.LoadComplete();
KeyBinding.BindValueChanged(_ =>
{
if (KeyBinding.Value.IsManaged)
throw new ArgumentException("Key binding should not be attached as we make temporary changes", nameof(KeyBinding));
updateKeyCombinationText();
});
keyCombinationProvider.KeymapChanged += updateKeyCombinationText;
updateKeyCombinationText();
}
[BackgroundDependencyLoader]
private void load()
{
updateHoverState();
FinishTransforms(true);
}
protected override bool OnHover(HoverEvent e)
{
updateHoverState();
return base.OnHover(e);
}
protected override void OnHoverLost(HoverLostEvent e)
{
updateHoverState();
base.OnHoverLost(e);
}
private void updateHoverState()
{
if (isBinding)
{
box.FadeColour(colourProvider.Light2, transition_time, Easing.OutQuint);
Text.FadeColour(Color4.Black, transition_time, Easing.OutQuint);
}
else
{
box.FadeColour(IsHovered ? colourProvider.Light4 : colourProvider.Background6, transition_time, Easing.OutQuint);
Text.FadeColour(IsHovered ? Color4.Black : Color4.White, transition_time, Easing.OutQuint);
}
}
/// <summary>
/// Update from a key combination, only allowing a single non-modifier key to be specified.
/// </summary>
/// <param name="fullState">A <see cref="KeyCombination"/> generated from the full input state.</param>
/// <param name="triggerKey">The key which triggered this update, and should be used as the binding.</param>
public void UpdateKeyCombination(KeyCombination fullState, InputKey triggerKey) =>
UpdateKeyCombination(new KeyCombination(fullState.Keys.Where(KeyCombination.IsModifierKey).Append(triggerKey)));
public void UpdateKeyCombination(KeyCombination newCombination)
{
if (KeyBinding.Value.RulesetName != null && !RealmKeyBindingStore.CheckValidForGameplay(newCombination))
return;
KeyBinding.Value.KeyCombination = newCombination;
updateKeyCombinationText();
}
private void updateKeyCombinationText()
{
Scheduler.AddOnce(updateText);
void updateText() => Text.Text = keyCombinationProvider.GetReadableString(KeyBinding.Value.KeyCombination);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (keyCombinationProvider.IsNotNull())
keyCombinationProvider.KeymapChanged -= updateKeyCombinationText;
}
}
} }
} }