mirror of
https://github.com/ppy/osu.git
synced 2024-12-14 09:32:55 +08:00
Merge branch 'master' into grid-momentary-shortcuts
This commit is contained in:
commit
48057412f5
@ -0,0 +1,22 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
public class TestSceneOsuModFreezeFrame : OsuModTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestFreezeFrame()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModFreezeFrame(),
|
||||
PassCondition = () => true,
|
||||
Autoplay = false,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override IconUsage? Icon { get; } = FontAwesome.Regular.Circle;
|
||||
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles) };
|
||||
public override Type[] IncompatibleMods => new[] { typeof(IHidesApproachCircles), typeof(OsuModFreezeFrame) };
|
||||
|
||||
[SettingSource("Initial size", "Change the initial size of the approach circle, relative to hit circles.", 0)]
|
||||
public BindableFloat Scale { get; } = new BindableFloat(4)
|
||||
|
89
osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs
Normal file
89
osu.Game.Rulesets.Osu/Mods/OsuModFreezeFrame.cs
Normal file
@ -0,0 +1,89 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Objects.Drawables;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Mods
|
||||
{
|
||||
public class OsuModFreezeFrame : Mod, IApplicableToDrawableHitObject, IApplicableToBeatmap
|
||||
{
|
||||
public override string Name => "Freeze Frame";
|
||||
|
||||
public override string Acronym => "FR";
|
||||
|
||||
public override double ScoreMultiplier => 1;
|
||||
|
||||
public override LocalisableString Description => "Burn the notes into your memory.";
|
||||
|
||||
//Alters the transforms of the approach circles, breaking the effects of these mods.
|
||||
public override Type[] IncompatibleMods => new[] { typeof(OsuModApproachDifferent) };
|
||||
|
||||
public override ModType Type => ModType.Fun;
|
||||
|
||||
//mod breaks normal approach circle preempt
|
||||
private double originalPreempt;
|
||||
|
||||
public void ApplyToBeatmap(IBeatmap beatmap)
|
||||
{
|
||||
var firstHitObject = beatmap.HitObjects.OfType<OsuHitObject>().FirstOrDefault();
|
||||
if (firstHitObject == null)
|
||||
return;
|
||||
|
||||
double lastNewComboTime = 0;
|
||||
|
||||
originalPreempt = firstHitObject.TimePreempt;
|
||||
|
||||
foreach (var obj in beatmap.HitObjects.OfType<OsuHitObject>())
|
||||
{
|
||||
if (obj.NewCombo) { lastNewComboTime = obj.StartTime; }
|
||||
|
||||
applyFadeInAdjustment(obj);
|
||||
}
|
||||
|
||||
void applyFadeInAdjustment(OsuHitObject osuObject)
|
||||
{
|
||||
osuObject.TimePreempt += osuObject.StartTime - lastNewComboTime;
|
||||
|
||||
foreach (var nested in osuObject.NestedHitObjects.OfType<OsuHitObject>())
|
||||
{
|
||||
switch (nested)
|
||||
{
|
||||
//SliderRepeat wont layer correctly if preempt is changed.
|
||||
case SliderRepeat:
|
||||
break;
|
||||
|
||||
default:
|
||||
applyFadeInAdjustment(nested);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyToDrawableHitObject(DrawableHitObject drawableObject)
|
||||
{
|
||||
drawableObject.ApplyCustomUpdateState += (drawableHitObject, _) =>
|
||||
{
|
||||
if (drawableHitObject is not DrawableHitCircle drawableHitCircle) return;
|
||||
|
||||
var hitCircle = drawableHitCircle.HitObject;
|
||||
var approachCircle = drawableHitCircle.ApproachCircle;
|
||||
|
||||
// Reapply scale, ensuring the AR isn't changed due to the new preempt.
|
||||
approachCircle.ClearTransforms(targetMember: nameof(approachCircle.Scale));
|
||||
approachCircle.ScaleTo(4 * (float)(hitCircle.TimePreempt / originalPreempt));
|
||||
|
||||
using (drawableHitCircle.ApproachCircle.BeginAbsoluteSequence(hitCircle.StartTime - hitCircle.TimePreempt))
|
||||
approachCircle.ScaleTo(1, hitCircle.TimePreempt).Then().Expire();
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -201,7 +201,8 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModMuted(),
|
||||
new OsuModNoScope(),
|
||||
new MultiMod(new OsuModMagnetised(), new OsuModRepel()),
|
||||
new ModAdaptiveSpeed()
|
||||
new ModAdaptiveSpeed(),
|
||||
new OsuModFreezeFrame()
|
||||
};
|
||||
|
||||
case ModType.System:
|
||||
|
@ -106,6 +106,49 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
assertBeatSnap(16);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestKeyboardNavigation()
|
||||
{
|
||||
pressKey(1);
|
||||
assertBeatSnap(1);
|
||||
assertPreset(BeatDivisorType.Common);
|
||||
|
||||
pressKey(2);
|
||||
assertBeatSnap(2);
|
||||
assertPreset(BeatDivisorType.Common);
|
||||
|
||||
pressKey(3);
|
||||
assertBeatSnap(3);
|
||||
assertPreset(BeatDivisorType.Triplets);
|
||||
|
||||
pressKey(4);
|
||||
assertBeatSnap(4);
|
||||
assertPreset(BeatDivisorType.Common);
|
||||
|
||||
pressKey(5);
|
||||
assertBeatSnap(5);
|
||||
assertPreset(BeatDivisorType.Custom, 5);
|
||||
|
||||
pressKey(6);
|
||||
assertBeatSnap(6);
|
||||
assertPreset(BeatDivisorType.Triplets);
|
||||
|
||||
pressKey(7);
|
||||
assertBeatSnap(7);
|
||||
assertPreset(BeatDivisorType.Custom, 7);
|
||||
|
||||
pressKey(8);
|
||||
assertBeatSnap(8);
|
||||
assertPreset(BeatDivisorType.Common);
|
||||
|
||||
void pressKey(int key) => AddStep($"press shift+{key}", () =>
|
||||
{
|
||||
InputManager.PressKey(Key.ShiftLeft);
|
||||
InputManager.Key(Key.Number0 + key);
|
||||
InputManager.ReleaseKey(Key.ShiftLeft);
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBeatPresetNavigation()
|
||||
{
|
||||
|
32
osu.Game.Tests/Visual/Editing/TestSceneEditorBindings.cs
Normal file
32
osu.Game.Tests/Visual/Editing/TestSceneEditorBindings.cs
Normal file
@ -0,0 +1,32 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Editing
|
||||
{
|
||||
/// <summary>
|
||||
/// Test editor hotkeys at a high level to ensure they all work well together.
|
||||
/// </summary>
|
||||
public class TestSceneEditorBindings : EditorTestScene
|
||||
{
|
||||
protected override Ruleset CreateEditorRuleset() => new OsuRuleset();
|
||||
|
||||
[Test]
|
||||
public void TestBeatDivisorChangeHotkeys()
|
||||
{
|
||||
AddStep("hold shift", () => InputManager.PressKey(Key.LShift));
|
||||
|
||||
AddStep("press 4", () => InputManager.Key(Key.Number4));
|
||||
AddAssert("snap updated to 4", () => EditorBeatmap.BeatmapInfo.BeatDivisor, () => Is.EqualTo(4));
|
||||
|
||||
AddStep("press 6", () => InputManager.Key(Key.Number6));
|
||||
AddAssert("snap updated to 6", () => EditorBeatmap.BeatmapInfo.BeatDivisor, () => Is.EqualTo(6));
|
||||
|
||||
AddStep("release shift", () => InputManager.ReleaseKey(Key.LShift));
|
||||
}
|
||||
}
|
||||
}
|
@ -155,6 +155,20 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddUntilStep("composer selection box is visible", () => Editor.ChildrenOfType<HitObjectComposer>().First().ChildrenOfType<EditorSelectionHandler>().First().Alpha > 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestClone()
|
||||
{
|
||||
var addedObject = new HitCircle { StartTime = 1000 };
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
||||
|
||||
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
|
||||
AddStep("clone", () => Editor.Clone());
|
||||
AddAssert("is two objects", () => EditorBeatmap.HitObjects.Count == 2);
|
||||
AddStep("clone", () => Editor.Clone());
|
||||
AddAssert("is three objects", () => EditorBeatmap.HitObjects.Count == 3);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCutNothing()
|
||||
{
|
||||
@ -175,5 +189,22 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddStep("paste hitobject", () => Editor.Paste());
|
||||
AddAssert("are no objects", () => EditorBeatmap.HitObjects.Count == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCloneNothing()
|
||||
{
|
||||
// Add arbitrary object and copy to clipboard.
|
||||
// This is tested to ensure that clone doesn't incorrectly read from the clipboard when no selection is made.
|
||||
var addedObject = new HitCircle { StartTime = 1000 };
|
||||
AddStep("add hitobject", () => EditorBeatmap.Add(addedObject));
|
||||
AddStep("select added object", () => EditorBeatmap.SelectedHitObjects.Add(addedObject));
|
||||
AddStep("copy hitobject", () => Editor.Copy());
|
||||
|
||||
AddStep("deselect all objects", () => EditorBeatmap.SelectedHitObjects.Clear());
|
||||
|
||||
AddAssert("is one object", () => EditorBeatmap.HitObjects.Count == 1);
|
||||
AddStep("clone", () => Editor.Clone());
|
||||
AddAssert("still one object", () => EditorBeatmap.HitObjects.Count == 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -31,14 +31,17 @@ namespace osu.Game.Input.Bindings
|
||||
parentInputManager = GetContainingInputManager();
|
||||
}
|
||||
|
||||
// IMPORTANT: Do not change the order of key bindings in this list.
|
||||
// It is used to decide the order of precedence (see note in DatabasedKeyBindingContainer).
|
||||
// IMPORTANT: Take care when changing order of the items in the enumerable.
|
||||
// It is used to decide the order of precedence, with the earlier items having higher precedence.
|
||||
public override IEnumerable<IKeyBinding> DefaultKeyBindings => GlobalKeyBindings
|
||||
.Concat(OverlayKeyBindings)
|
||||
.Concat(EditorKeyBindings)
|
||||
.Concat(InGameKeyBindings)
|
||||
.Concat(SongSelectKeyBindings)
|
||||
.Concat(AudioControlKeyBindings);
|
||||
.Concat(AudioControlKeyBindings)
|
||||
// Overlay bindings may conflict with more local cases like the editor so they are checked last.
|
||||
// It has generally been agreed on that local screens like the editor should have priority,
|
||||
// based on such usages potentially requiring a lot more key bindings that may be "shared" with global ones.
|
||||
.Concat(OverlayKeyBindings);
|
||||
|
||||
public IEnumerable<KeyBinding> GlobalKeyBindings => new[]
|
||||
{
|
||||
@ -87,6 +90,7 @@ namespace osu.Game.Input.Bindings
|
||||
new KeyBinding(new[] { InputKey.F3 }, GlobalAction.EditorTimingMode),
|
||||
new KeyBinding(new[] { InputKey.F4 }, GlobalAction.EditorSetupMode),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.Shift, InputKey.A }, GlobalAction.EditorVerifyMode),
|
||||
new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.EditorCloneSelection),
|
||||
new KeyBinding(new[] { InputKey.J }, GlobalAction.EditorNudgeLeft),
|
||||
new KeyBinding(new[] { InputKey.K }, GlobalAction.EditorNudgeRight),
|
||||
new KeyBinding(new[] { InputKey.G }, GlobalAction.EditorCycleGridDisplayMode),
|
||||
@ -343,5 +347,8 @@ namespace osu.Game.Input.Bindings
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.ToggleProfile))]
|
||||
ToggleProfile,
|
||||
|
||||
[LocalisableDescription(typeof(GlobalActionKeyBindingStrings), nameof(GlobalActionKeyBindingStrings.EditorCloneSelection))]
|
||||
EditorCloneSelection
|
||||
}
|
||||
}
|
||||
|
@ -184,6 +184,11 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString EditorTapForBPM => new TranslatableString(getKey(@"editor_tap_for_bpm"), @"Tap for BPM");
|
||||
|
||||
/// <summary>
|
||||
/// "Clone selection"
|
||||
/// </summary>
|
||||
public static LocalisableString EditorCloneSelection => new TranslatableString(getKey(@"editor_clone_selection"), @"Clone selection");
|
||||
|
||||
/// <summary>
|
||||
/// "Cycle grid display mode"
|
||||
/// </summary>
|
||||
|
@ -238,7 +238,7 @@ namespace osu.Game.Rulesets.Edit
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.ControlPressed || e.AltPressed || e.SuperPressed)
|
||||
if (e.ControlPressed || e.AltPressed || e.SuperPressed || e.ShiftPressed)
|
||||
return false;
|
||||
|
||||
if (checkLeftToggleFromKey(e.Key, out int leftIndex))
|
||||
|
@ -199,8 +199,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
if (stringAddBank == @"none")
|
||||
stringAddBank = null;
|
||||
|
||||
bankInfo.Normal = stringBank;
|
||||
bankInfo.Add = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;
|
||||
bankInfo.BankForNormal = stringBank;
|
||||
bankInfo.BankForAdditions = string.IsNullOrEmpty(stringAddBank) ? stringBank : stringAddBank;
|
||||
|
||||
if (split.Length > 2)
|
||||
bankInfo.CustomSampleBank = Parsing.ParseInt(split[2]);
|
||||
@ -447,32 +447,54 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
|
||||
var soundTypes = new List<HitSampleInfo>
|
||||
{
|
||||
new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.Normal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
||||
new LegacyHitSampleInfo(HitSampleInfo.HIT_NORMAL, bankInfo.BankForNormal, bankInfo.Volume, bankInfo.CustomSampleBank,
|
||||
// if the sound type doesn't have the Normal flag set, attach it anyway as a layered sample.
|
||||
// None also counts as a normal non-layered sample: https://osu.ppy.sh/help/wiki/osu!_File_Formats/Osu_(file_format)#hitsounds
|
||||
type != LegacyHitSoundType.None && !type.HasFlagFast(LegacyHitSoundType.Normal))
|
||||
};
|
||||
|
||||
if (type.HasFlagFast(LegacyHitSoundType.Finish))
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_FINISH, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
|
||||
if (type.HasFlagFast(LegacyHitSoundType.Whistle))
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_WHISTLE, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
|
||||
if (type.HasFlagFast(LegacyHitSoundType.Clap))
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.Add, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
soundTypes.Add(new LegacyHitSampleInfo(HitSampleInfo.HIT_CLAP, bankInfo.BankForAdditions, bankInfo.Volume, bankInfo.CustomSampleBank));
|
||||
|
||||
return soundTypes;
|
||||
}
|
||||
|
||||
private class SampleBankInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// An optional overriding filename which causes all bank/sample specifications to be ignored.
|
||||
/// </summary>
|
||||
public string Filename;
|
||||
|
||||
public string Normal;
|
||||
public string Add;
|
||||
/// <summary>
|
||||
/// The bank identifier to use for the base ("hitnormal") sample.
|
||||
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
|
||||
/// </summary>
|
||||
public string BankForNormal;
|
||||
|
||||
/// <summary>
|
||||
/// The bank identifier to use for additions ("hitwhistle", "hitfinish", "hitclap").
|
||||
/// Transferred to <see cref="HitSampleInfo.Bank"/> when appropriate.
|
||||
/// </summary>
|
||||
public string BankForAdditions;
|
||||
|
||||
/// <summary>
|
||||
/// Hit sample volume (0-100).
|
||||
/// See <see cref="HitSampleInfo.Volume"/>.
|
||||
/// </summary>
|
||||
public int Volume;
|
||||
|
||||
/// <summary>
|
||||
/// The index of the custom sample bank. Is only used if 2 or above for "reasons".
|
||||
/// This will add a suffix to lookups, allowing extended bank lookups (ie. "normal-hitnormal-2").
|
||||
/// See <see cref="HitSampleInfo.Suffix"/>.
|
||||
/// </summary>
|
||||
public int CustomSampleBank;
|
||||
|
||||
public SampleBankInfo Clone() => (SampleBankInfo)MemberwiseClone();
|
||||
@ -503,7 +525,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
public sealed override HitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<string?> newSuffix = default, Optional<int> newVolume = default)
|
||||
=> With(newName, newBank, newVolume);
|
||||
|
||||
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default, Optional<int> newCustomSampleBank = default,
|
||||
public virtual LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default,
|
||||
Optional<int> newCustomSampleBank = default,
|
||||
Optional<bool> newIsLayered = default)
|
||||
=> new LegacyHitSampleInfo(newName.GetOr(Name), newBank.GetOr(Bank), newVolume.GetOr(Volume), newCustomSampleBank.GetOr(CustomSampleBank), newIsLayered.GetOr(IsLayered));
|
||||
|
||||
@ -537,7 +560,8 @@ namespace osu.Game.Rulesets.Objects.Legacy
|
||||
Path.ChangeExtension(Filename, null)
|
||||
};
|
||||
|
||||
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default, Optional<int> newCustomSampleBank = default,
|
||||
public sealed override LegacyHitSampleInfo With(Optional<string> newName = default, Optional<string?> newBank = default, Optional<int> newVolume = default,
|
||||
Optional<int> newCustomSampleBank = default,
|
||||
Optional<bool> newIsLayered = default)
|
||||
=> new FileHitSampleInfo(Filename, newVolume.GetOr(Volume));
|
||||
|
||||
|
@ -25,6 +25,26 @@ namespace osu.Game.Screens.Edit
|
||||
BindValueChanged(_ => ensureValidDivisor());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Set a divisor, updating the valid divisor range appropriately.
|
||||
/// </summary>
|
||||
/// <param name="divisor">The intended divisor.</param>
|
||||
public void SetArbitraryDivisor(int divisor)
|
||||
{
|
||||
// If the current valid divisor range doesn't contain the proposed value, attempt to find one which does.
|
||||
if (!ValidDivisors.Value.Presets.Contains(divisor))
|
||||
{
|
||||
if (BeatDivisorPresetCollection.COMMON.Presets.Contains(divisor))
|
||||
ValidDivisors.Value = BeatDivisorPresetCollection.COMMON;
|
||||
else if (BeatDivisorPresetCollection.TRIPLETS.Presets.Contains(divisor))
|
||||
ValidDivisors.Value = BeatDivisorPresetCollection.TRIPLETS;
|
||||
else
|
||||
ValidDivisors.Value = BeatDivisorPresetCollection.Custom(divisor);
|
||||
}
|
||||
|
||||
Value = divisor;
|
||||
}
|
||||
|
||||
private void updateBindableProperties()
|
||||
{
|
||||
ensureValidDivisor();
|
||||
|
@ -209,6 +209,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e)
|
||||
{
|
||||
if (e.ShiftPressed && e.Key >= Key.Number1 && e.Key <= Key.Number9)
|
||||
{
|
||||
beatDivisor.SetArbitraryDivisor(e.Key - Key.Number0);
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.OnKeyDown(e);
|
||||
}
|
||||
|
||||
internal class DivisorDisplay : OsuAnimatedButton, IHasPopover
|
||||
{
|
||||
public BindableBeatDivisor BeatDivisor { get; } = new BindableBeatDivisor();
|
||||
@ -306,17 +317,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
|
||||
return;
|
||||
}
|
||||
|
||||
if (!BeatDivisor.ValidDivisors.Value.Presets.Contains(divisor))
|
||||
{
|
||||
if (BeatDivisorPresetCollection.COMMON.Presets.Contains(divisor))
|
||||
BeatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.COMMON;
|
||||
else if (BeatDivisorPresetCollection.TRIPLETS.Presets.Contains(divisor))
|
||||
BeatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.TRIPLETS;
|
||||
else
|
||||
BeatDivisor.ValidDivisors.Value = BeatDivisorPresetCollection.Custom(divisor);
|
||||
}
|
||||
|
||||
BeatDivisor.Value = divisor;
|
||||
BeatDivisor.SetArbitraryDivisor(divisor);
|
||||
|
||||
this.HidePopover();
|
||||
}
|
||||
|
@ -304,6 +304,7 @@ namespace osu.Game.Screens.Edit
|
||||
cutMenuItem = new EditorMenuItem("Cut", MenuItemType.Standard, Cut),
|
||||
copyMenuItem = new EditorMenuItem("Copy", MenuItemType.Standard, Copy),
|
||||
pasteMenuItem = new EditorMenuItem("Paste", MenuItemType.Standard, Paste),
|
||||
cloneMenuItem = new EditorMenuItem("Clone", MenuItemType.Standard, Clone),
|
||||
}
|
||||
},
|
||||
new MenuItem("View")
|
||||
@ -575,6 +576,10 @@ namespace osu.Game.Screens.Edit
|
||||
this.Exit();
|
||||
return true;
|
||||
|
||||
case GlobalAction.EditorCloneSelection:
|
||||
Clone();
|
||||
return true;
|
||||
|
||||
case GlobalAction.EditorComposeMode:
|
||||
Mode.Value = EditorScreenMode.Compose;
|
||||
return true;
|
||||
@ -741,6 +746,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
private EditorMenuItem cutMenuItem;
|
||||
private EditorMenuItem copyMenuItem;
|
||||
private EditorMenuItem cloneMenuItem;
|
||||
private EditorMenuItem pasteMenuItem;
|
||||
|
||||
private readonly BindableWithCurrent<bool> canCut = new BindableWithCurrent<bool>();
|
||||
@ -750,7 +756,11 @@ namespace osu.Game.Screens.Edit
|
||||
private void setUpClipboardActionAvailability()
|
||||
{
|
||||
canCut.Current.BindValueChanged(cut => cutMenuItem.Action.Disabled = !cut.NewValue, true);
|
||||
canCopy.Current.BindValueChanged(copy => copyMenuItem.Action.Disabled = !copy.NewValue, true);
|
||||
canCopy.Current.BindValueChanged(copy =>
|
||||
{
|
||||
copyMenuItem.Action.Disabled = !copy.NewValue;
|
||||
cloneMenuItem.Action.Disabled = !copy.NewValue;
|
||||
}, true);
|
||||
canPaste.Current.BindValueChanged(paste => pasteMenuItem.Action.Disabled = !paste.NewValue, true);
|
||||
}
|
||||
|
||||
@ -765,6 +775,21 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
protected void Copy() => currentScreen?.Copy();
|
||||
|
||||
protected void Clone()
|
||||
{
|
||||
// Avoid attempting to clone if copying is not available (as it may result in pasting something unexpected).
|
||||
if (!canCopy.Value)
|
||||
return;
|
||||
|
||||
// This is an initial implementation just to get an idea of how people used this function.
|
||||
// There are a couple of differences from osu!stable's implementation which will require more work to match:
|
||||
// - The "clipboard" is not populated during the duplication process.
|
||||
// - The duplicated hitobjects are inserted after the original pattern (add one beat_length and then quantize using beat snap).
|
||||
// - The duplicated hitobjects are selected (but this is also applied for all paste operations so should be changed there).
|
||||
Copy();
|
||||
Paste();
|
||||
}
|
||||
|
||||
protected void Paste() => currentScreen?.Paste();
|
||||
|
||||
#endregion
|
||||
|
@ -110,6 +110,8 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
public new void Paste() => base.Paste();
|
||||
|
||||
public new void Clone() => base.Clone();
|
||||
|
||||
public new void SwitchToDifficulty(BeatmapInfo beatmapInfo) => base.SwitchToDifficulty(beatmapInfo);
|
||||
|
||||
public new void CreateNewDifficulty(RulesetInfo rulesetInfo) => base.CreateNewDifficulty(rulesetInfo);
|
||||
|
Loading…
Reference in New Issue
Block a user