mirror of
https://github.com/osukey/osukey.git
synced 2025-05-01 03:37:17 +09:00
Merge branch 'master' into chat-report
This commit is contained in:
commit
80ac8abaa6
@ -191,6 +191,8 @@ csharp_style_prefer_index_operator = false:silent
|
|||||||
csharp_style_prefer_range_operator = false:silent
|
csharp_style_prefer_range_operator = false:silent
|
||||||
csharp_style_prefer_switch_expression = false:none
|
csharp_style_prefer_switch_expression = false:none
|
||||||
|
|
||||||
|
csharp_style_namespace_declarations = block_scoped:warning
|
||||||
|
|
||||||
[*.{yaml,yml}]
|
[*.{yaml,yml}]
|
||||||
insert_final_newline = true
|
insert_final_newline = true
|
||||||
indent_style = space
|
indent_style = space
|
||||||
|
@ -15,6 +15,10 @@ using osuTK.Graphics;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Beatmaps.Legacy;
|
using osu.Game.Beatmaps.Legacy;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
@ -22,6 +26,7 @@ using osu.Game.Rulesets.Judgements;
|
|||||||
using osu.Game.Rulesets.Objects;
|
using osu.Game.Rulesets.Objects;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
using osu.Game.Rulesets.Osu.Configuration;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Osu.Tests
|
namespace osu.Game.Rulesets.Osu.Tests
|
||||||
{
|
{
|
||||||
@ -30,6 +35,27 @@ namespace osu.Game.Rulesets.Osu.Tests
|
|||||||
{
|
{
|
||||||
private int depthIndex;
|
private int depthIndex;
|
||||||
|
|
||||||
|
private readonly BindableBool snakingIn = new BindableBool();
|
||||||
|
private readonly BindableBool snakingOut = new BindableBool();
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddToggleStep("toggle snaking", v =>
|
||||||
|
{
|
||||||
|
snakingIn.Value = v;
|
||||||
|
snakingOut.Value = v;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
var config = (OsuRulesetConfigManager)RulesetConfigs.GetConfigFor(Ruleset.Value.CreateInstance()).AsNonNull();
|
||||||
|
config.BindWith(OsuRulesetSetting.SnakingInSliders, snakingIn);
|
||||||
|
config.BindWith(OsuRulesetSetting.SnakingOutSliders, snakingOut);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestVariousSliders()
|
public void TestVariousSliders()
|
||||||
{
|
{
|
||||||
|
@ -87,12 +87,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
|
|
||||||
protected override void UpdateInitialTransforms()
|
protected override void UpdateInitialTransforms()
|
||||||
{
|
{
|
||||||
|
// When snaking in is enabled, the first end circle needs to be delayed until the snaking completes.
|
||||||
|
bool delayFadeIn = DrawableSlider.SliderBody?.SnakingIn.Value == true && HitObject.RepeatIndex == 0;
|
||||||
|
|
||||||
animDuration = Math.Min(300, HitObject.SpanDuration);
|
animDuration = Math.Min(300, HitObject.SpanDuration);
|
||||||
|
|
||||||
this.Animate(
|
this
|
||||||
d => d.FadeIn(animDuration),
|
.FadeOut()
|
||||||
d => d.ScaleTo(0.5f).ScaleTo(1f, animDuration * 2, Easing.OutElasticHalf)
|
.Delay(delayFadeIn ? (Slider?.TimePreempt ?? 0) / 3 : 0)
|
||||||
);
|
.FadeIn(HitObject.RepeatIndex == 0 ? HitObject.TimeFadeIn : animDuration);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||||
|
@ -91,7 +91,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
|
|||||||
{
|
{
|
||||||
base.UpdateInitialTransforms();
|
base.UpdateInitialTransforms();
|
||||||
|
|
||||||
CirclePiece.FadeInFromZero(HitObject.TimeFadeIn);
|
// When snaking in is enabled, the first end circle needs to be delayed until the snaking completes.
|
||||||
|
bool delayFadeIn = DrawableSlider.SliderBody?.SnakingIn.Value == true && HitObject.RepeatIndex == 0;
|
||||||
|
|
||||||
|
CirclePiece
|
||||||
|
.FadeOut()
|
||||||
|
.Delay(delayFadeIn ? (Slider?.TimePreempt ?? 0) / 3 : 0)
|
||||||
|
.FadeIn(HitObject.TimeFadeIn);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void UpdateHitStateTransforms(ArmedState state)
|
protected override void UpdateHitStateTransforms(ArmedState state)
|
||||||
|
@ -39,11 +39,8 @@ namespace osu.Game.Rulesets.Osu.Objects
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// taken from osu-stable
|
|
||||||
const float first_end_circle_preempt_adjust = 2 / 3f;
|
|
||||||
|
|
||||||
// The first end circle should fade in with the slider.
|
// The first end circle should fade in with the slider.
|
||||||
TimePreempt = (StartTime - slider.StartTime) + slider.TimePreempt * first_end_circle_preempt_adjust;
|
TimePreempt += StartTime - slider.StartTime;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,5 +49,31 @@ namespace osu.Game.Tests.Mods
|
|||||||
Assert.That(mod3, Is.EqualTo(mod2));
|
Assert.That(mod3, Is.EqualTo(mod2));
|
||||||
Assert.That(doubleConvertedMod3, Is.EqualTo(doubleConvertedMod2));
|
Assert.That(doubleConvertedMod3, Is.EqualTo(doubleConvertedMod2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestModWithMultipleSettings()
|
||||||
|
{
|
||||||
|
var ruleset = new OsuRuleset();
|
||||||
|
|
||||||
|
var mod1 = new OsuModDifficultyAdjust { OverallDifficulty = { Value = 10 }, CircleSize = { Value = 0 } };
|
||||||
|
var mod2 = new OsuModDifficultyAdjust { OverallDifficulty = { Value = 10 }, CircleSize = { Value = 6 } };
|
||||||
|
var mod3 = new OsuModDifficultyAdjust { OverallDifficulty = { Value = 10 }, CircleSize = { Value = 6 } };
|
||||||
|
|
||||||
|
var doubleConvertedMod1 = new APIMod(mod1).ToMod(ruleset);
|
||||||
|
var doubleConvertedMod2 = new APIMod(mod2).ToMod(ruleset);
|
||||||
|
var doubleConvertedMod3 = new APIMod(mod3).ToMod(ruleset);
|
||||||
|
|
||||||
|
Assert.That(mod1, Is.Not.EqualTo(mod2));
|
||||||
|
Assert.That(doubleConvertedMod1, Is.Not.EqualTo(doubleConvertedMod2));
|
||||||
|
|
||||||
|
Assert.That(mod2, Is.EqualTo(mod2));
|
||||||
|
Assert.That(doubleConvertedMod2, Is.EqualTo(doubleConvertedMod2));
|
||||||
|
|
||||||
|
Assert.That(mod2, Is.EqualTo(mod3));
|
||||||
|
Assert.That(doubleConvertedMod2, Is.EqualTo(doubleConvertedMod3));
|
||||||
|
|
||||||
|
Assert.That(mod3, Is.EqualTo(mod2));
|
||||||
|
Assert.That(doubleConvertedMod3, Is.EqualTo(doubleConvertedMod2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,9 @@
|
|||||||
// 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 NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
|
||||||
@ -10,7 +13,7 @@ namespace osu.Game.Tests.Mods
|
|||||||
public class ModSettingsTest
|
public class ModSettingsTest
|
||||||
{
|
{
|
||||||
[Test]
|
[Test]
|
||||||
public void TestModSettingsUnboundWhenCopied()
|
public void TestModSettingsUnboundWhenCloned()
|
||||||
{
|
{
|
||||||
var original = new OsuModDoubleTime();
|
var original = new OsuModDoubleTime();
|
||||||
var copy = (OsuModDoubleTime)original.DeepClone();
|
var copy = (OsuModDoubleTime)original.DeepClone();
|
||||||
@ -22,7 +25,7 @@ namespace osu.Game.Tests.Mods
|
|||||||
}
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestMultiModSettingsUnboundWhenCopied()
|
public void TestMultiModSettingsUnboundWhenCloned()
|
||||||
{
|
{
|
||||||
var original = new MultiMod(new OsuModDoubleTime());
|
var original = new MultiMod(new OsuModDoubleTime());
|
||||||
var copy = (MultiMod)original.DeepClone();
|
var copy = (MultiMod)original.DeepClone();
|
||||||
@ -32,5 +35,67 @@ namespace osu.Game.Tests.Mods
|
|||||||
Assert.That(((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value, Is.EqualTo(2.0));
|
Assert.That(((OsuModDoubleTime)original.Mods[0]).SpeedChange.Value, Is.EqualTo(2.0));
|
||||||
Assert.That(((OsuModDoubleTime)copy.Mods[0]).SpeedChange.Value, Is.EqualTo(1.5));
|
Assert.That(((OsuModDoubleTime)copy.Mods[0]).SpeedChange.Value, Is.EqualTo(1.5));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDifferentTypeSettingsKeptWhenCopied()
|
||||||
|
{
|
||||||
|
const double setting_change = 50.4;
|
||||||
|
|
||||||
|
var modDouble = new TestNonMatchingSettingTypeModDouble { TestSetting = { Value = setting_change } };
|
||||||
|
var modBool = new TestNonMatchingSettingTypeModBool { TestSetting = { Default = false, Value = true } };
|
||||||
|
var modInt = new TestNonMatchingSettingTypeModInt { TestSetting = { Value = (int)setting_change / 2 } };
|
||||||
|
|
||||||
|
modDouble.CopyCommonSettingsFrom(modBool);
|
||||||
|
modDouble.CopyCommonSettingsFrom(modInt);
|
||||||
|
modBool.CopyCommonSettingsFrom(modDouble);
|
||||||
|
modBool.CopyCommonSettingsFrom(modInt);
|
||||||
|
modInt.CopyCommonSettingsFrom(modDouble);
|
||||||
|
modInt.CopyCommonSettingsFrom(modBool);
|
||||||
|
|
||||||
|
Assert.That(modDouble.TestSetting.Value, Is.EqualTo(setting_change));
|
||||||
|
Assert.That(modBool.TestSetting.Value, Is.EqualTo(true));
|
||||||
|
Assert.That(modInt.TestSetting.Value, Is.EqualTo((int)setting_change / 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestDefaultValueKeptWhenCopied()
|
||||||
|
{
|
||||||
|
var modBoolTrue = new TestNonMatchingSettingTypeModBool { TestSetting = { Default = true, Value = false } };
|
||||||
|
var modBoolFalse = new TestNonMatchingSettingTypeModBool { TestSetting = { Default = false, Value = true } };
|
||||||
|
|
||||||
|
modBoolFalse.CopyCommonSettingsFrom(modBoolTrue);
|
||||||
|
|
||||||
|
Assert.That(modBoolFalse.TestSetting.Default, Is.EqualTo(false));
|
||||||
|
Assert.That(modBoolFalse.TestSetting.Value, Is.EqualTo(modBoolTrue.TestSetting.Value));
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestNonMatchingSettingTypeModDouble : TestNonMatchingSettingTypeMod
|
||||||
|
{
|
||||||
|
public override string Acronym => "NMD";
|
||||||
|
public override BindableNumber<double> TestSetting { get; } = new BindableDouble();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestNonMatchingSettingTypeModInt : TestNonMatchingSettingTypeMod
|
||||||
|
{
|
||||||
|
public override string Acronym => "NMI";
|
||||||
|
public override BindableNumber<int> TestSetting { get; } = new BindableInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestNonMatchingSettingTypeModBool : TestNonMatchingSettingTypeMod
|
||||||
|
{
|
||||||
|
public override string Acronym => "NMB";
|
||||||
|
public override Bindable<bool> TestSetting { get; } = new BindableBool();
|
||||||
|
}
|
||||||
|
|
||||||
|
private abstract class TestNonMatchingSettingTypeMod : Mod
|
||||||
|
{
|
||||||
|
public override string Name => "Non-matching setting type mod";
|
||||||
|
public override LocalisableString Description => "Description";
|
||||||
|
public override double ScoreMultiplier => 1;
|
||||||
|
public override ModType Type => ModType.Conversion;
|
||||||
|
|
||||||
|
[SettingSource("Test setting")]
|
||||||
|
public abstract IBindable TestSetting { get; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,12 +2,15 @@
|
|||||||
// 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.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||||
using osu.Framework.Extensions.ObjectExtensions;
|
using osu.Framework.Extensions.ObjectExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
@ -182,6 +185,64 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
AddAssert("all boxes still selected", () => skinEditor.SelectedComponents, () => Has.Count.EqualTo(2));
|
AddAssert("all boxes still selected", () => skinEditor.SelectedComponents, () => Has.Count.EqualTo(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestUndoEditHistory()
|
||||||
|
{
|
||||||
|
SkinComponentsContainer firstTarget = null!;
|
||||||
|
TestSkinEditorChangeHandler changeHandler = null!;
|
||||||
|
byte[] defaultState = null!;
|
||||||
|
IEnumerable<ISerialisableDrawable> testComponents = null!;
|
||||||
|
|
||||||
|
AddStep("Load necessary things", () =>
|
||||||
|
{
|
||||||
|
firstTarget = Player.ChildrenOfType<SkinComponentsContainer>().First();
|
||||||
|
changeHandler = new TestSkinEditorChangeHandler(firstTarget);
|
||||||
|
|
||||||
|
changeHandler.SaveState();
|
||||||
|
defaultState = changeHandler.GetCurrentState();
|
||||||
|
|
||||||
|
testComponents = new[]
|
||||||
|
{
|
||||||
|
targetContainer.Components.First(),
|
||||||
|
targetContainer.Components[targetContainer.Components.Count / 2],
|
||||||
|
targetContainer.Components.Last()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Press undo", () => InputManager.Keys(PlatformAction.Undo));
|
||||||
|
AddAssert("Nothing changed", () => defaultState.SequenceEqual(changeHandler.GetCurrentState()));
|
||||||
|
|
||||||
|
AddStep("Add components", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(skinEditor.ChildrenOfType<BigBlackBox>().First());
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
revertAndCheckUnchanged();
|
||||||
|
|
||||||
|
AddStep("Move components", () =>
|
||||||
|
{
|
||||||
|
changeHandler.BeginChange();
|
||||||
|
testComponents.ForEach(c => ((Drawable)c).Position += Vector2.One);
|
||||||
|
changeHandler.EndChange();
|
||||||
|
});
|
||||||
|
revertAndCheckUnchanged();
|
||||||
|
|
||||||
|
AddStep("Select components", () => skinEditor.SelectedComponents.AddRange(testComponents));
|
||||||
|
AddStep("Bring to front", () => skinEditor.BringSelectionToFront());
|
||||||
|
revertAndCheckUnchanged();
|
||||||
|
|
||||||
|
AddStep("Remove components", () => testComponents.ForEach(c => firstTarget.Remove(c, false)));
|
||||||
|
revertAndCheckUnchanged();
|
||||||
|
|
||||||
|
void revertAndCheckUnchanged()
|
||||||
|
{
|
||||||
|
AddStep("Revert changes", () => changeHandler.RestoreState(int.MinValue));
|
||||||
|
AddAssert("Current state is same as default", () => defaultState.SequenceEqual(changeHandler.GetCurrentState()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[TestCase(false)]
|
[TestCase(false)]
|
||||||
[TestCase(true)]
|
[TestCase(true)]
|
||||||
public void TestBringToFront(bool alterSelectionOrder)
|
public void TestBringToFront(bool alterSelectionOrder)
|
||||||
@ -269,5 +330,23 @@ namespace osu.Game.Tests.Visual.Gameplay
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
protected override Ruleset CreatePlayerRuleset() => new OsuRuleset();
|
||||||
|
|
||||||
|
private partial class TestSkinEditorChangeHandler : SkinEditorChangeHandler
|
||||||
|
{
|
||||||
|
public TestSkinEditorChangeHandler(Drawable targetScreen)
|
||||||
|
: base(targetScreen)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] GetCurrentState()
|
||||||
|
{
|
||||||
|
using var stream = new MemoryStream();
|
||||||
|
|
||||||
|
WriteCurrentStateToStream(stream);
|
||||||
|
byte[] newState = stream.ToArray();
|
||||||
|
|
||||||
|
return newState;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -243,7 +243,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddUntilStep("wait for context menu", () => this.ChildrenOfType<OsuContextMenu>().Any());
|
AddUntilStep("wait for context menu", () => this.ChildrenOfType<OsuContextMenu>().Any());
|
||||||
AddStep("click delete", () =>
|
AddStep("click delete", () =>
|
||||||
{
|
{
|
||||||
var deleteItem = this.ChildrenOfType<DrawableOsuMenuItem>().Single();
|
var deleteItem = this.ChildrenOfType<DrawableOsuMenuItem>().ElementAt(1);
|
||||||
InputManager.MoveMouseTo(deleteItem);
|
InputManager.MoveMouseTo(deleteItem);
|
||||||
InputManager.Click(MouseButton.Left);
|
InputManager.Click(MouseButton.Left);
|
||||||
});
|
});
|
||||||
@ -261,6 +261,137 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("preset soft-deleted", () => Realm.Run(r => r.All<ModPreset>().Count(preset => preset.DeletePending) == 1));
|
AddAssert("preset soft-deleted", () => Realm.Run(r => r.All<ModPreset>().Count(preset => preset.DeletePending) == 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEditPresetName()
|
||||||
|
{
|
||||||
|
ModPresetColumn modPresetColumn = null!;
|
||||||
|
string presetName = null!;
|
||||||
|
ModPresetPanel panel = null!;
|
||||||
|
|
||||||
|
AddStep("clear mods", () => SelectedMods.Value = Array.Empty<Mod>());
|
||||||
|
AddStep("create content", () => Child = modPresetColumn = new ModPresetColumn
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("items loaded", () => modPresetColumn.IsLoaded && modPresetColumn.ItemsLoaded);
|
||||||
|
AddStep("right click first panel", () =>
|
||||||
|
{
|
||||||
|
panel = this.ChildrenOfType<ModPresetPanel>().First();
|
||||||
|
presetName = panel.Preset.Value.Name;
|
||||||
|
InputManager.MoveMouseTo(panel);
|
||||||
|
InputManager.Click(MouseButton.Right);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for context menu", () => this.ChildrenOfType<OsuContextMenu>().Any());
|
||||||
|
AddStep("click edit", () =>
|
||||||
|
{
|
||||||
|
var editItem = this.ChildrenOfType<DrawableOsuMenuItem>().ElementAt(0);
|
||||||
|
InputManager.MoveMouseTo(editItem);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
OsuPopover? popover = null;
|
||||||
|
AddUntilStep("wait for popover", () => (popover = this.ChildrenOfType<OsuPopover>().FirstOrDefault()) != null);
|
||||||
|
AddStep("clear preset name", () => popover.ChildrenOfType<LabelledTextBox>().First().Current.Value = "");
|
||||||
|
AddStep("attempt preset edit", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(popover.ChildrenOfType<ShearedButton>().ElementAt(1));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("preset is not changed", () => panel.Preset.Value.Name == presetName);
|
||||||
|
AddUntilStep("popover is unchanged", () => this.ChildrenOfType<OsuPopover>().FirstOrDefault() == popover);
|
||||||
|
AddStep("edit preset name", () => popover.ChildrenOfType<LabelledTextBox>().First().Current.Value = "something new");
|
||||||
|
AddStep("attempt preset edit", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(popover.ChildrenOfType<ShearedButton>().ElementAt(1));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddUntilStep("popover closed", () => !this.ChildrenOfType<OsuPopover>().Any());
|
||||||
|
AddAssert("preset is changed", () => panel.Preset.Value.Name != presetName);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEditPresetMod()
|
||||||
|
{
|
||||||
|
ModPresetColumn modPresetColumn = null!;
|
||||||
|
var mods = new Mod[] { new OsuModHidden(), new OsuModHardRock() };
|
||||||
|
List<Mod> previousMod = null!;
|
||||||
|
|
||||||
|
AddStep("clear mods", () => SelectedMods.Value = Array.Empty<Mod>());
|
||||||
|
AddStep("create content", () => Child = modPresetColumn = new ModPresetColumn
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("items loaded", () => modPresetColumn.IsLoaded && modPresetColumn.ItemsLoaded);
|
||||||
|
|
||||||
|
AddStep("right click first panel", () =>
|
||||||
|
{
|
||||||
|
var panel = this.ChildrenOfType<ModPresetPanel>().First();
|
||||||
|
previousMod = panel.Preset.Value.Mods.ToList();
|
||||||
|
InputManager.MoveMouseTo(panel);
|
||||||
|
InputManager.Click(MouseButton.Right);
|
||||||
|
});
|
||||||
|
AddUntilStep("wait for context menu", () => this.ChildrenOfType<OsuContextMenu>().Any());
|
||||||
|
AddStep("click edit", () =>
|
||||||
|
{
|
||||||
|
var editItem = this.ChildrenOfType<DrawableOsuMenuItem>().ElementAt(0);
|
||||||
|
InputManager.MoveMouseTo(editItem);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
OsuPopover? popover = null;
|
||||||
|
AddUntilStep("wait for popover", () => (popover = this.ChildrenOfType<OsuPopover>().FirstOrDefault()) != null);
|
||||||
|
AddStep("click use current mods", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(popover.ChildrenOfType<ShearedButton>().ElementAt(0));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddStep("attempt preset edit", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(popover.ChildrenOfType<ShearedButton>().ElementAt(1));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("preset mod not changed", () =>
|
||||||
|
new HashSet<Mod>(this.ChildrenOfType<ModPresetPanel>().First().Preset.Value.Mods).SetEquals(previousMod));
|
||||||
|
|
||||||
|
AddStep("select mods", () => SelectedMods.Value = mods);
|
||||||
|
AddStep("right click first panel", () =>
|
||||||
|
{
|
||||||
|
var panel = this.ChildrenOfType<ModPresetPanel>().First();
|
||||||
|
InputManager.MoveMouseTo(panel);
|
||||||
|
InputManager.Click(MouseButton.Right);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for context menu", () => this.ChildrenOfType<OsuContextMenu>().Any());
|
||||||
|
AddStep("click edit", () =>
|
||||||
|
{
|
||||||
|
var editItem = this.ChildrenOfType<DrawableOsuMenuItem>().ElementAt(0);
|
||||||
|
InputManager.MoveMouseTo(editItem);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("wait for popover", () => (popover = this.ChildrenOfType<OsuPopover>().FirstOrDefault()) != null);
|
||||||
|
AddStep("click use current mods", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(popover.ChildrenOfType<ShearedButton>().ElementAt(0));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddStep("attempt preset edit", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(popover.ChildrenOfType<ShearedButton>().ElementAt(1));
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("preset mod is changed", () =>
|
||||||
|
new HashSet<Mod>(this.ChildrenOfType<ModPresetPanel>().First().Preset.Value.Mods).SetEquals(mods));
|
||||||
|
}
|
||||||
|
|
||||||
private ICollection<ModPreset> createTestPresets() => new[]
|
private ICollection<ModPreset> createTestPresets() => new[]
|
||||||
{
|
{
|
||||||
new ModPreset
|
new ModPreset
|
||||||
|
@ -22,6 +22,7 @@ using osu.Game.Rulesets.Catch.Mods;
|
|||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Osu;
|
using osu.Game.Rulesets.Osu;
|
||||||
using osu.Game.Rulesets.Osu.Mods;
|
using osu.Game.Rulesets.Osu.Mods;
|
||||||
|
using osu.Game.Rulesets.Taiko.Mods;
|
||||||
using osu.Game.Tests.Mods;
|
using osu.Game.Tests.Mods;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
@ -385,6 +386,50 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("no mod selected", () => SelectedMods.Value.Count == 0);
|
AddAssert("no mod selected", () => SelectedMods.Value.Count == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestKeepSharedSettingsFromSimilarMods()
|
||||||
|
{
|
||||||
|
const float setting_change = 1.2f;
|
||||||
|
|
||||||
|
createScreen();
|
||||||
|
changeRuleset(0);
|
||||||
|
|
||||||
|
AddStep("select difficulty adjust mod", () => SelectedMods.Value = new[] { Ruleset.Value.CreateInstance().CreateMod<ModDifficultyAdjust>()! });
|
||||||
|
|
||||||
|
changeRuleset(0);
|
||||||
|
AddAssert("ensure mod still selected", () => SelectedMods.Value.SingleOrDefault() is OsuModDifficultyAdjust);
|
||||||
|
|
||||||
|
AddStep("change mod settings", () =>
|
||||||
|
{
|
||||||
|
var osuMod = getSelectedMod<OsuModDifficultyAdjust>();
|
||||||
|
|
||||||
|
osuMod.ExtendedLimits.Value = true;
|
||||||
|
osuMod.CircleSize.Value = setting_change;
|
||||||
|
osuMod.DrainRate.Value = setting_change;
|
||||||
|
osuMod.OverallDifficulty.Value = setting_change;
|
||||||
|
osuMod.ApproachRate.Value = setting_change;
|
||||||
|
});
|
||||||
|
|
||||||
|
changeRuleset(1);
|
||||||
|
AddAssert("taiko variant selected", () => SelectedMods.Value.SingleOrDefault() is TaikoModDifficultyAdjust);
|
||||||
|
|
||||||
|
AddAssert("shared settings preserved", () =>
|
||||||
|
{
|
||||||
|
var taikoMod = getSelectedMod<TaikoModDifficultyAdjust>();
|
||||||
|
|
||||||
|
return taikoMod.ExtendedLimits.Value &&
|
||||||
|
taikoMod.DrainRate.Value == setting_change &&
|
||||||
|
taikoMod.OverallDifficulty.Value == setting_change;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("non-shared settings remain default", () =>
|
||||||
|
{
|
||||||
|
var taikoMod = getSelectedMod<TaikoModDifficultyAdjust>();
|
||||||
|
|
||||||
|
return taikoMod.ScrollSpeed.IsDefault;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestExternallySetCustomizedMod()
|
public void TestExternallySetCustomizedMod()
|
||||||
{
|
{
|
||||||
@ -617,6 +662,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert($"customisation toggle is {(active ? "" : "not ")}active", () => modSelectOverlay.CustomisationButton.AsNonNull().Active.Value == active);
|
AddAssert($"customisation toggle is {(active ? "" : "not ")}active", () => modSelectOverlay.CustomisationButton.AsNonNull().Active.Value == active);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private T getSelectedMod<T>() where T : Mod => SelectedMods.Value.OfType<T>().Single();
|
||||||
|
|
||||||
private ModPanel getPanelForMod(Type modType)
|
private ModPanel getPanelForMod(Type modType)
|
||||||
=> modSelectOverlay.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.GetType() == modType);
|
=> modSelectOverlay.ChildrenOfType<ModPanel>().Single(panel => panel.Mod.GetType() == modType);
|
||||||
|
|
||||||
|
@ -34,6 +34,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString AddPreset => new TranslatableString(getKey(@"add_preset"), @"Add preset");
|
public static LocalisableString AddPreset => new TranslatableString(getKey(@"add_preset"), @"Add preset");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Use current mods"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString UseCurrentMods => new TranslatableString(getKey(@"use_current_mods"), @"Use current mods");
|
||||||
|
|
||||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -58,7 +58,6 @@ using osu.Game.Rulesets.Mods;
|
|||||||
using osu.Game.Scoring;
|
using osu.Game.Scoring;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
using File = System.IO.File;
|
|
||||||
using RuntimeInfo = osu.Framework.RuntimeInfo;
|
using RuntimeInfo = osu.Framework.RuntimeInfo;
|
||||||
|
|
||||||
namespace osu.Game
|
namespace osu.Game
|
||||||
@ -626,15 +625,22 @@ namespace osu.Game
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var previouslySelectedMods = SelectedMods.Value.ToArray();
|
|
||||||
|
|
||||||
if (!SelectedMods.Disabled)
|
|
||||||
SelectedMods.Value = Array.Empty<Mod>();
|
|
||||||
|
|
||||||
AvailableMods.Value = dict;
|
AvailableMods.Value = dict;
|
||||||
|
|
||||||
if (!SelectedMods.Disabled)
|
if (SelectedMods.Disabled)
|
||||||
SelectedMods.Value = previouslySelectedMods.Select(m => instance.CreateModFromAcronym(m.Acronym)).Where(m => m != null).ToArray();
|
return;
|
||||||
|
|
||||||
|
var convertedMods = SelectedMods.Value.Select(mod =>
|
||||||
|
{
|
||||||
|
var newMod = instance.CreateModFromAcronym(mod.Acronym);
|
||||||
|
newMod?.CopyCommonSettingsFrom(mod);
|
||||||
|
return newMod;
|
||||||
|
}).Where(newMod => newMod != null).ToList();
|
||||||
|
|
||||||
|
if (!ModUtils.CheckValidForGameplay(convertedMods, out var invalid))
|
||||||
|
invalid.ForEach(newMod => convertedMods.Remove(newMod));
|
||||||
|
|
||||||
|
SelectedMods.Value = convertedMods;
|
||||||
|
|
||||||
void revertRulesetChange() => Ruleset.Value = r.OldValue?.Available == true ? r.OldValue : RulesetStore.AvailableRulesets.First();
|
void revertRulesetChange() => Ruleset.Value = r.OldValue?.Available == true ? r.OldValue : RulesetStore.AvailableRulesets.First();
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ using osu.Framework.Extensions;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Database;
|
using osu.Game.Database;
|
||||||
using osu.Game.Extensions;
|
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
@ -67,7 +66,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Anchor = Anchor.TopCentre,
|
Anchor = Anchor.TopCentre,
|
||||||
Origin = Anchor.TopCentre,
|
Origin = Anchor.TopCentre,
|
||||||
Text = ModSelectOverlayStrings.AddPreset,
|
Text = ModSelectOverlayStrings.AddPreset,
|
||||||
Action = tryCreatePreset
|
Action = createPreset
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -89,16 +88,15 @@ namespace osu.Game.Overlays.Mods
|
|||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox));
|
ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox));
|
||||||
|
|
||||||
|
nameTextBox.Current.BindValueChanged(s =>
|
||||||
|
{
|
||||||
|
createButton.Enabled.Value = !string.IsNullOrWhiteSpace(s.NewValue);
|
||||||
|
}, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void tryCreatePreset()
|
private void createPreset()
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(nameTextBox.Current.Value))
|
|
||||||
{
|
|
||||||
Body.Shake();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
realm.Write(r => r.Add(new ModPreset
|
realm.Write(r => r.Add(new ModPreset
|
||||||
{
|
{
|
||||||
Name = nameTextBox.Current.Value,
|
Name = nameTextBox.Current.Value,
|
||||||
|
172
osu.Game/Overlays/Mods/EditPresetPopover.cs
Normal file
172
osu.Game/Overlays/Mods/EditPresetPopover.cs
Normal file
@ -0,0 +1,172 @@
|
|||||||
|
// 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 System.Linq;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Database;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Mods
|
||||||
|
{
|
||||||
|
internal partial class EditPresetPopover : OsuPopover
|
||||||
|
{
|
||||||
|
private LabelledTextBox nameTextBox = null!;
|
||||||
|
private LabelledTextBox descriptionTextBox = null!;
|
||||||
|
private ShearedButton useCurrentModsButton = null!;
|
||||||
|
private ShearedButton saveButton = null!;
|
||||||
|
private FillFlowContainer scrollContent = null!;
|
||||||
|
|
||||||
|
private readonly Live<ModPreset> preset;
|
||||||
|
|
||||||
|
private HashSet<Mod> saveableMods;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private Bindable<IReadOnlyList<Mod>> selectedMods { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuColour colours { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
public EditPresetPopover(Live<ModPreset> preset)
|
||||||
|
{
|
||||||
|
this.preset = preset;
|
||||||
|
saveableMods = preset.PerformRead(p => p.Mods).ToHashSet();
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
Child = new FillFlowContainer
|
||||||
|
{
|
||||||
|
Width = 300,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(7),
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
nameTextBox = new LabelledTextBox
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Label = CommonStrings.Name,
|
||||||
|
TabbableContentContainer = this,
|
||||||
|
Current = { Value = preset.PerformRead(p => p.Name) },
|
||||||
|
},
|
||||||
|
descriptionTextBox = new LabelledTextBox
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Label = CommonStrings.Description,
|
||||||
|
TabbableContentContainer = this,
|
||||||
|
Current = { Value = preset.PerformRead(p => p.Description) },
|
||||||
|
},
|
||||||
|
new OsuScrollContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 100,
|
||||||
|
Padding = new MarginPadding(7),
|
||||||
|
Child = scrollContent = new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding(7),
|
||||||
|
Spacing = new Vector2(7),
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(7),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
useCurrentModsButton = new ShearedButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = ModSelectOverlayStrings.UseCurrentMods,
|
||||||
|
DarkerColour = colours.Blue1,
|
||||||
|
LighterColour = colours.Blue0,
|
||||||
|
TextColour = colourProvider.Background6,
|
||||||
|
Action = useCurrentMods,
|
||||||
|
},
|
||||||
|
saveButton = new ShearedButton
|
||||||
|
{
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Text = Resources.Localisation.Web.CommonStrings.ButtonsSave,
|
||||||
|
DarkerColour = colours.Orange1,
|
||||||
|
LighterColour = colours.Orange0,
|
||||||
|
TextColour = colourProvider.Background6,
|
||||||
|
Action = save,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Body.BorderThickness = 3;
|
||||||
|
Body.BorderColour = colours.Orange1;
|
||||||
|
|
||||||
|
selectedMods.BindValueChanged(_ => updateState(), true);
|
||||||
|
nameTextBox.Current.BindValueChanged(s =>
|
||||||
|
{
|
||||||
|
saveButton.Enabled.Value = !string.IsNullOrWhiteSpace(s.NewValue);
|
||||||
|
}, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void useCurrentMods()
|
||||||
|
{
|
||||||
|
saveableMods = selectedMods.Value.ToHashSet();
|
||||||
|
updateState();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateState()
|
||||||
|
{
|
||||||
|
scrollContent.ChildrenEnumerable = saveableMods.Select(mod => new ModPresetRow(mod));
|
||||||
|
useCurrentModsButton.Enabled.Value = checkSelectedModsDiffersFromSaved();
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool checkSelectedModsDiffersFromSaved()
|
||||||
|
{
|
||||||
|
if (!selectedMods.Value.Any())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
return !saveableMods.SetEquals(selectedMods.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
ScheduleAfterChildren(() => GetContainingInputManager().ChangeFocus(nameTextBox));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void save()
|
||||||
|
{
|
||||||
|
preset.PerformWrite(s =>
|
||||||
|
{
|
||||||
|
s.Name = nameTextBox.Current.Value;
|
||||||
|
s.Description = descriptionTextBox.Current.Value;
|
||||||
|
s.Mods = saveableMods;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.HidePopover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
@ -17,7 +18,7 @@ using osu.Game.Rulesets.Mods;
|
|||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
{
|
{
|
||||||
public partial class ModPresetPanel : ModSelectPanel, IHasCustomTooltip<ModPreset>, IHasContextMenu
|
public partial class ModPresetPanel : ModSelectPanel, IHasCustomTooltip<ModPreset>, IHasContextMenu, IHasPopover
|
||||||
{
|
{
|
||||||
public readonly Live<ModPreset> Preset;
|
public readonly Live<ModPreset> Preset;
|
||||||
|
|
||||||
@ -91,7 +92,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
public MenuItem[] ContextMenuItems => new MenuItem[]
|
public MenuItem[] ContextMenuItems => new MenuItem[]
|
||||||
{
|
{
|
||||||
new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new DeleteModPresetDialog(Preset)))
|
new OsuMenuItem(CommonStrings.ButtonsEdit, MenuItemType.Highlighted, this.ShowPopover),
|
||||||
|
new OsuMenuItem(CommonStrings.ButtonsDelete, MenuItemType.Destructive, () => dialogOverlay?.Push(new DeleteModPresetDialog(Preset))),
|
||||||
};
|
};
|
||||||
|
|
||||||
#endregion
|
#endregion
|
||||||
@ -102,5 +104,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
settingChangeTracker?.Dispose();
|
settingChangeTracker?.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Popover GetPopover() => new EditPresetPopover(Preset);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
64
osu.Game/Overlays/Mods/ModPresetRow.cs
Normal file
64
osu.Game/Overlays/Mods/ModPresetRow.cs
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// 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;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
using osu.Game.Rulesets.UI;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Mods
|
||||||
|
{
|
||||||
|
public partial class ModPresetRow : FillFlowContainer
|
||||||
|
{
|
||||||
|
public ModPresetRow(Mod mod)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
Direction = FillDirection.Vertical;
|
||||||
|
Spacing = new Vector2(4);
|
||||||
|
InternalChildren = new Drawable[]
|
||||||
|
{
|
||||||
|
new FillFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Direction = FillDirection.Horizontal,
|
||||||
|
Spacing = new Vector2(7),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new ModSwitchTiny(mod)
|
||||||
|
{
|
||||||
|
Active = { Value = true },
|
||||||
|
Scale = new Vector2(0.6f),
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Text = mod.Name,
|
||||||
|
Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold),
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Margin = new MarginPadding { Bottom = 2 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(mod.SettingDescription))
|
||||||
|
{
|
||||||
|
AddInternal(new OsuTextFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Padding = new MarginPadding { Left = 14 },
|
||||||
|
Text = mod.SettingDescription
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,11 +6,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.UI;
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Mods
|
namespace osu.Game.Overlays.Mods
|
||||||
@ -61,55 +57,5 @@ namespace osu.Game.Overlays.Mods
|
|||||||
protected override void PopOut() => this.FadeOut(transition_duration, Easing.OutQuint);
|
protected override void PopOut() => this.FadeOut(transition_duration, Easing.OutQuint);
|
||||||
|
|
||||||
public void Move(Vector2 pos) => Position = pos;
|
public void Move(Vector2 pos) => Position = pos;
|
||||||
|
|
||||||
private partial class ModPresetRow : FillFlowContainer
|
|
||||||
{
|
|
||||||
public ModPresetRow(Mod mod)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
Direction = FillDirection.Vertical;
|
|
||||||
Spacing = new Vector2(4);
|
|
||||||
InternalChildren = new Drawable[]
|
|
||||||
{
|
|
||||||
new FillFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Direction = FillDirection.Horizontal,
|
|
||||||
Spacing = new Vector2(7),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new ModSwitchTiny(mod)
|
|
||||||
{
|
|
||||||
Active = { Value = true },
|
|
||||||
Scale = new Vector2(0.6f),
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Origin = Anchor.CentreLeft
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Text = mod.Name,
|
|
||||||
Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold),
|
|
||||||
Origin = Anchor.CentreLeft,
|
|
||||||
Anchor = Anchor.CentreLeft,
|
|
||||||
Margin = new MarginPadding { Bottom = 2 }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(mod.SettingDescription))
|
|
||||||
{
|
|
||||||
AddInternal(new OsuTextFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Padding = new MarginPadding { Left = 14 },
|
|
||||||
Text = mod.SettingDescription
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
@ -56,20 +57,53 @@ namespace osu.Game.Overlays.SkinEditor
|
|||||||
if (deserializedContent == null)
|
if (deserializedContent == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
SerialisedDrawableInfo[] skinnableInfo = deserializedContent.ToArray();
|
SerialisedDrawableInfo[] skinnableInfos = deserializedContent.ToArray();
|
||||||
Drawable[] targetComponents = firstTarget.Components.OfType<Drawable>().ToArray();
|
ISerialisableDrawable[] targetComponents = firstTarget.Components.ToArray();
|
||||||
|
|
||||||
if (!skinnableInfo.Select(s => s.Type).SequenceEqual(targetComponents.Select(d => d.GetType())))
|
// Store components based on type for later reuse
|
||||||
|
var componentsPerTypeLookup = new Dictionary<Type, Queue<Drawable>>();
|
||||||
|
|
||||||
|
foreach (ISerialisableDrawable component in targetComponents)
|
||||||
{
|
{
|
||||||
// Perform a naive full reload for now.
|
Type lookup = component.GetType();
|
||||||
firstTarget.Reload(skinnableInfo);
|
|
||||||
|
if (!componentsPerTypeLookup.TryGetValue(lookup, out Queue<Drawable>? componentsOfSameType))
|
||||||
|
componentsPerTypeLookup.Add(lookup, componentsOfSameType = new Queue<Drawable>());
|
||||||
|
|
||||||
|
componentsOfSameType.Enqueue((Drawable)component);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = targetComponents.Length - 1; i >= 0; i--)
|
||||||
|
firstTarget.Remove(targetComponents[i], false);
|
||||||
|
|
||||||
|
foreach (var skinnableInfo in skinnableInfos)
|
||||||
|
{
|
||||||
|
Type lookup = skinnableInfo.Type;
|
||||||
|
|
||||||
|
if (!componentsPerTypeLookup.TryGetValue(lookup, out Queue<Drawable>? componentsOfSameType))
|
||||||
|
{
|
||||||
|
firstTarget.Add((ISerialisableDrawable)skinnableInfo.CreateInstance());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wherever possible, attempt to reuse existing component instances.
|
||||||
|
if (componentsOfSameType.TryDequeue(out Drawable? component))
|
||||||
|
{
|
||||||
|
component.ApplySerialisedInfo(skinnableInfo);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
int i = 0;
|
component = skinnableInfo.CreateInstance();
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var drawable in targetComponents)
|
firstTarget.Add((ISerialisableDrawable)component);
|
||||||
drawable.ApplySerialisedInfo(skinnableInfo[i++]);
|
}
|
||||||
|
|
||||||
|
// Dispose components which were not reused.
|
||||||
|
foreach ((Type _, Queue<Drawable> typeComponents) in componentsPerTypeLookup)
|
||||||
|
{
|
||||||
|
foreach (var component in typeComponents)
|
||||||
|
component.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,146 +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.
|
|
||||||
|
|
||||||
using System.Linq;
|
|
||||||
using osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Extensions.TypeExtensions;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Threading;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Overlays;
|
|
||||||
using osu.Game.Rulesets.Objects;
|
|
||||||
using osu.Game.Rulesets.Objects.Types;
|
|
||||||
using osu.Game.Screens.Edit;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Edit
|
|
||||||
{
|
|
||||||
internal partial class HitObjectInspector : CompositeDrawable
|
|
||||||
{
|
|
||||||
private OsuTextFlowContainer inspectorText = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
protected EditorBeatmap EditorBeatmap { get; private set; } = null!;
|
|
||||||
|
|
||||||
[Resolved]
|
|
||||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load()
|
|
||||||
{
|
|
||||||
AutoSizeAxes = Axes.Y;
|
|
||||||
RelativeSizeAxes = Axes.X;
|
|
||||||
|
|
||||||
InternalChild = inspectorText = new OsuTextFlowContainer
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override void LoadComplete()
|
|
||||||
{
|
|
||||||
base.LoadComplete();
|
|
||||||
|
|
||||||
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, _) => updateInspectorText();
|
|
||||||
EditorBeatmap.TransactionBegan += updateInspectorText;
|
|
||||||
EditorBeatmap.TransactionEnded += updateInspectorText;
|
|
||||||
updateInspectorText();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ScheduledDelegate? rollingTextUpdate;
|
|
||||||
|
|
||||||
private void updateInspectorText()
|
|
||||||
{
|
|
||||||
inspectorText.Clear();
|
|
||||||
rollingTextUpdate?.Cancel();
|
|
||||||
rollingTextUpdate = null;
|
|
||||||
|
|
||||||
switch (EditorBeatmap.SelectedHitObjects.Count)
|
|
||||||
{
|
|
||||||
case 0:
|
|
||||||
addValue("No selection");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 1:
|
|
||||||
var selected = EditorBeatmap.SelectedHitObjects.Single();
|
|
||||||
|
|
||||||
addHeader("Type");
|
|
||||||
addValue($"{selected.GetType().ReadableName()}");
|
|
||||||
|
|
||||||
addHeader("Time");
|
|
||||||
addValue($"{selected.StartTime:#,0.##}ms");
|
|
||||||
|
|
||||||
switch (selected)
|
|
||||||
{
|
|
||||||
case IHasPosition pos:
|
|
||||||
addHeader("Position");
|
|
||||||
addValue($"x:{pos.X:#,0.##} y:{pos.Y:#,0.##}");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IHasXPosition x:
|
|
||||||
addHeader("Position");
|
|
||||||
|
|
||||||
addValue($"x:{x.X:#,0.##} ");
|
|
||||||
break;
|
|
||||||
|
|
||||||
case IHasYPosition y:
|
|
||||||
addHeader("Position");
|
|
||||||
|
|
||||||
addValue($"y:{y.Y:#,0.##}");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected is IHasDistance distance)
|
|
||||||
{
|
|
||||||
addHeader("Distance");
|
|
||||||
addValue($"{distance.Distance:#,0.##}px");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected is IHasRepeats repeats)
|
|
||||||
{
|
|
||||||
addHeader("Repeats");
|
|
||||||
addValue($"{repeats.RepeatCount:#,0.##}");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selected is IHasDuration duration)
|
|
||||||
{
|
|
||||||
addHeader("End Time");
|
|
||||||
addValue($"{duration.EndTime:#,0.##}ms");
|
|
||||||
addHeader("Duration");
|
|
||||||
addValue($"{duration.Duration:#,0.##}ms");
|
|
||||||
}
|
|
||||||
|
|
||||||
// I'd hope there's a better way to do this, but I don't want to bind to each and every property above to watch for changes.
|
|
||||||
// This is a good middle-ground for the time being.
|
|
||||||
rollingTextUpdate ??= Scheduler.AddDelayed(updateInspectorText, 250);
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
addHeader("Selected Objects");
|
|
||||||
addValue($"{EditorBeatmap.SelectedHitObjects.Count:#,0.##}");
|
|
||||||
|
|
||||||
addHeader("Start Time");
|
|
||||||
addValue($"{EditorBeatmap.SelectedHitObjects.Min(o => o.StartTime):#,0.##}ms");
|
|
||||||
|
|
||||||
addHeader("End Time");
|
|
||||||
addValue($"{EditorBeatmap.SelectedHitObjects.Max(o => o.GetEndTime()):#,0.##}ms");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
void addHeader(string header) => inspectorText.AddParagraph($"{header}: ", s =>
|
|
||||||
{
|
|
||||||
s.Padding = new MarginPadding { Top = 2 };
|
|
||||||
s.Font = s.Font.With(size: 12);
|
|
||||||
s.Colour = colourProvider.Content2;
|
|
||||||
});
|
|
||||||
|
|
||||||
void addValue(string value) => inspectorText.AddParagraph(value, s =>
|
|
||||||
{
|
|
||||||
s.Font = s.Font.With(weight: FontWeight.SemiBold);
|
|
||||||
s.Colour = colourProvider.Content1;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,6 +12,7 @@ using osu.Framework.Graphics.Sprites;
|
|||||||
using osu.Framework.Localisation;
|
using osu.Framework.Localisation;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Extensions;
|
||||||
using osu.Game.Rulesets.UI;
|
using osu.Game.Rulesets.UI;
|
||||||
using osu.Game.Utils;
|
using osu.Game.Utils;
|
||||||
|
|
||||||
@ -113,21 +114,29 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
[JsonIgnore]
|
[JsonIgnore]
|
||||||
public virtual Type[] IncompatibleMods => Array.Empty<Type>();
|
public virtual Type[] IncompatibleMods => Array.Empty<Type>();
|
||||||
|
|
||||||
private IReadOnlyList<IBindable>? settingsBacking;
|
private IReadOnlyDictionary<string, IBindable>? settingsBacking;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A list of the all <see cref="IBindable"/> settings within this mod.
|
/// All <see cref="IBindable"/> settings within this mod.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal IReadOnlyList<IBindable> Settings =>
|
/// <remarks>
|
||||||
|
/// The settings are returned in ascending key order as per <see cref="SettingsMap"/>.
|
||||||
|
/// The ordering is intentionally enforced manually, as ordering of <see cref="Dictionary{TKey,TValue}.Values"/> is unspecified.
|
||||||
|
/// </remarks>
|
||||||
|
internal IEnumerable<IBindable> SettingsBindables => SettingsMap.OrderBy(pair => pair.Key).Select(pair => pair.Value);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Provides mapping of names to <see cref="IBindable"/>s of all settings within this mod.
|
||||||
|
/// </summary>
|
||||||
|
internal IReadOnlyDictionary<string, IBindable> SettingsMap =>
|
||||||
settingsBacking ??= this.GetSettingsSourceProperties()
|
settingsBacking ??= this.GetSettingsSourceProperties()
|
||||||
.Select(p => p.Item2.GetValue(this))
|
.Select(p => p.Item2)
|
||||||
.Cast<IBindable>()
|
.ToDictionary(property => property.Name.ToSnakeCase(), property => (IBindable)property.GetValue(this)!);
|
||||||
.ToList();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether all settings in this mod are set to their default state.
|
/// Whether all settings in this mod are set to their default state.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected virtual bool UsesDefaultConfiguration => Settings.All(s => s.IsDefault);
|
protected virtual bool UsesDefaultConfiguration => SettingsBindables.All(s => s.IsDefault);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a copy of this <see cref="Mod"/> initialised to a default state.
|
/// Creates a copy of this <see cref="Mod"/> initialised to a default state.
|
||||||
@ -148,15 +157,53 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
if (source.GetType() != GetType())
|
if (source.GetType() != GetType())
|
||||||
throw new ArgumentException($"Expected mod of type {GetType()}, got {source.GetType()}.", nameof(source));
|
throw new ArgumentException($"Expected mod of type {GetType()}, got {source.GetType()}.", nameof(source));
|
||||||
|
|
||||||
foreach (var (_, prop) in this.GetSettingsSourceProperties())
|
foreach (var (_, property) in this.GetSettingsSourceProperties())
|
||||||
{
|
{
|
||||||
var targetBindable = (IBindable)prop.GetValue(this)!;
|
var targetBindable = (IBindable)property.GetValue(this)!;
|
||||||
var sourceBindable = (IBindable)prop.GetValue(source)!;
|
var sourceBindable = (IBindable)property.GetValue(source)!;
|
||||||
|
|
||||||
CopyAdjustedSetting(targetBindable, sourceBindable);
|
CopyAdjustedSetting(targetBindable, sourceBindable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This method copies the values of all settings from <paramref name="source"/> that share the same names with this mod instance.
|
||||||
|
/// The most frequent use of this is when switching rulesets, in order to preserve values of common settings during the switch.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// The values are copied directly, without adjusting for possibly different allowed ranges of values.
|
||||||
|
/// If the value of a setting is not valid for this instance due to not falling inside of the allowed range, it will be clamped accordingly.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="source">The mod to extract settings from.</param>
|
||||||
|
public void CopyCommonSettingsFrom(Mod source)
|
||||||
|
{
|
||||||
|
if (source.UsesDefaultConfiguration)
|
||||||
|
return;
|
||||||
|
|
||||||
|
foreach (var (name, targetSetting) in SettingsMap)
|
||||||
|
{
|
||||||
|
if (!source.SettingsMap.TryGetValue(name, out IBindable? sourceSetting))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (sourceSetting.IsDefault)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var targetBindableType = targetSetting.GetType();
|
||||||
|
var sourceBindableType = sourceSetting.GetType();
|
||||||
|
|
||||||
|
// if either the target is assignable to the source or the source is assignable to the target,
|
||||||
|
// then we presume that the data types contained in both bindables are compatible and we can proceed with the copy.
|
||||||
|
// this handles cases like `Bindable<int>` and `BindableInt`.
|
||||||
|
if (!targetBindableType.IsAssignableFrom(sourceBindableType) && !sourceBindableType.IsAssignableFrom(targetBindableType))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// TODO: special case for handling number types
|
||||||
|
|
||||||
|
PropertyInfo property = targetSetting.GetType().GetProperty(nameof(Bindable<bool>.Value))!;
|
||||||
|
property.SetValue(targetSetting, property.GetValue(sourceSetting));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// When creating copies or clones of a Mod, this method will be called
|
/// When creating copies or clones of a Mod, this method will be called
|
||||||
/// to copy explicitly adjusted user settings from <paramref name="target"/>.
|
/// to copy explicitly adjusted user settings from <paramref name="target"/>.
|
||||||
@ -191,7 +238,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
if (ReferenceEquals(this, other)) return true;
|
if (ReferenceEquals(this, other)) return true;
|
||||||
|
|
||||||
return GetType() == other.GetType() &&
|
return GetType() == other.GetType() &&
|
||||||
Settings.SequenceEqual(other.Settings, ModSettingsEqualityComparer.Default);
|
SettingsBindables.SequenceEqual(other.SettingsBindables, ModSettingsEqualityComparer.Default);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
@ -200,7 +247,7 @@ namespace osu.Game.Rulesets.Mods
|
|||||||
|
|
||||||
hashCode.Add(GetType());
|
hashCode.Add(GetType());
|
||||||
|
|
||||||
foreach (var setting in Settings)
|
foreach (var setting in SettingsBindables)
|
||||||
hashCode.Add(setting.GetUnderlyingSettingValue());
|
hashCode.Add(setting.GetUnderlyingSettingValue());
|
||||||
|
|
||||||
return hashCode.ToHashCode();
|
return hashCode.ToHashCode();
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Types;
|
namespace osu.Game.Rulesets.Objects.Types
|
||||||
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A HitObject that has a slider velocity multiplier.
|
/// A HitObject that has a slider velocity multiplier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -17,3 +17,4 @@ public interface IHasSliderVelocity
|
|||||||
|
|
||||||
BindableNumber<double> SliderVelocityBindable { get; }
|
BindableNumber<double> SliderVelocityBindable { get; }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
49
osu.Game/Screens/Edit/Compose/Components/EditorInspector.cs
Normal file
49
osu.Game/Screens/Edit/Compose/Components/EditorInspector.cs
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
// 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.Allocation;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
|
{
|
||||||
|
internal partial class EditorInspector : CompositeDrawable
|
||||||
|
{
|
||||||
|
protected OsuTextFlowContainer InspectorText = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
protected EditorBeatmap EditorBeatmap { get; private set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y;
|
||||||
|
RelativeSizeAxes = Axes.X;
|
||||||
|
|
||||||
|
InternalChild = InspectorText = new OsuTextFlowContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void AddHeader(string header) => InspectorText.AddParagraph($"{header}: ", s =>
|
||||||
|
{
|
||||||
|
s.Padding = new MarginPadding { Top = 2 };
|
||||||
|
s.Font = s.Font.With(size: 12);
|
||||||
|
s.Colour = colourProvider.Content2;
|
||||||
|
});
|
||||||
|
|
||||||
|
protected void AddValue(string value) => InspectorText.AddParagraph(value, s =>
|
||||||
|
{
|
||||||
|
s.Font = s.Font.With(weight: FontWeight.SemiBold);
|
||||||
|
s.Colour = colourProvider.Content1;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
111
osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs
Normal file
111
osu.Game/Screens/Edit/Compose/Components/HitObjectInspector.cs
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// 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.Linq;
|
||||||
|
using osu.Framework.Extensions.TypeExtensions;
|
||||||
|
using osu.Framework.Threading;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Compose.Components
|
||||||
|
{
|
||||||
|
internal partial class HitObjectInspector : EditorInspector
|
||||||
|
{
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
EditorBeatmap.SelectedHitObjects.CollectionChanged += (_, _) => updateInspectorText();
|
||||||
|
EditorBeatmap.TransactionBegan += updateInspectorText;
|
||||||
|
EditorBeatmap.TransactionEnded += updateInspectorText;
|
||||||
|
updateInspectorText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ScheduledDelegate? rollingTextUpdate;
|
||||||
|
|
||||||
|
private void updateInspectorText()
|
||||||
|
{
|
||||||
|
InspectorText.Clear();
|
||||||
|
rollingTextUpdate?.Cancel();
|
||||||
|
rollingTextUpdate = null;
|
||||||
|
|
||||||
|
switch (EditorBeatmap.SelectedHitObjects.Count)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
AddValue("No selection");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 1:
|
||||||
|
var selected = EditorBeatmap.SelectedHitObjects.Single();
|
||||||
|
|
||||||
|
AddHeader("Type");
|
||||||
|
AddValue($"{selected.GetType().ReadableName()}");
|
||||||
|
|
||||||
|
AddHeader("Time");
|
||||||
|
AddValue($"{selected.StartTime:#,0.##}ms");
|
||||||
|
|
||||||
|
switch (selected)
|
||||||
|
{
|
||||||
|
case IHasPosition pos:
|
||||||
|
AddHeader("Position");
|
||||||
|
AddValue($"x:{pos.X:#,0.##} y:{pos.Y:#,0.##}");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IHasXPosition x:
|
||||||
|
AddHeader("Position");
|
||||||
|
|
||||||
|
AddValue($"x:{x.X:#,0.##} ");
|
||||||
|
break;
|
||||||
|
|
||||||
|
case IHasYPosition y:
|
||||||
|
AddHeader("Position");
|
||||||
|
|
||||||
|
AddValue($"y:{y.Y:#,0.##}");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected is IHasDistance distance)
|
||||||
|
{
|
||||||
|
AddHeader("Distance");
|
||||||
|
AddValue($"{distance.Distance:#,0.##}px");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected is IHasSliderVelocity sliderVelocity)
|
||||||
|
{
|
||||||
|
AddHeader("Slider Velocity");
|
||||||
|
AddValue($"{sliderVelocity.SliderVelocity:#,0.00}x");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected is IHasRepeats repeats)
|
||||||
|
{
|
||||||
|
AddHeader("Repeats");
|
||||||
|
AddValue($"{repeats.RepeatCount:#,0.##}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected is IHasDuration duration)
|
||||||
|
{
|
||||||
|
AddHeader("End Time");
|
||||||
|
AddValue($"{duration.EndTime:#,0.##}ms");
|
||||||
|
AddHeader("Duration");
|
||||||
|
AddValue($"{duration.Duration:#,0.##}ms");
|
||||||
|
}
|
||||||
|
|
||||||
|
// I'd hope there's a better way to do this, but I don't want to bind to each and every property above to watch for changes.
|
||||||
|
// This is a good middle-ground for the time being.
|
||||||
|
rollingTextUpdate ??= Scheduler.AddDelayed(updateInspectorText, 250);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
AddHeader("Selected Objects");
|
||||||
|
AddValue($"{EditorBeatmap.SelectedHitObjects.Count:#,0.##}");
|
||||||
|
|
||||||
|
AddHeader("Start Time");
|
||||||
|
AddValue($"{EditorBeatmap.SelectedHitObjects.Min(o => o.StartTime):#,0.##}ms");
|
||||||
|
|
||||||
|
AddHeader("End Time");
|
||||||
|
AddValue($"{EditorBeatmap.SelectedHitObjects.Max(o => o.GetEndTime()):#,0.##}ms");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -95,7 +95,8 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Text = "Hold shift while dragging the end of an object to adjust velocity while snapping."
|
Text = "Hold shift while dragging the end of an object to adjust velocity while snapping."
|
||||||
}
|
},
|
||||||
|
new SliderVelocityInspector(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -105,7 +106,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).Where(o => o is IHasSliderVelocity).ToArray();
|
var relevantObjects = (beatmap.SelectedHitObjects.Contains(hitObject) ? beatmap.SelectedHitObjects : hitObject.Yield()).Where(o => o is IHasSliderVelocity).ToArray();
|
||||||
|
|
||||||
// even if there are multiple objects selected, we can still display a value if they all have the same value.
|
// even if there are multiple objects selected, we can still display a value if they all have the same value.
|
||||||
var selectedPointBindable = relevantObjects.Select(point => ((IHasSliderVelocity)point).SliderVelocity).Distinct().Count() == 1 ? ((IHasSliderVelocity)relevantObjects.First()).SliderVelocityBindable : null;
|
var selectedPointBindable = relevantObjects.Select(point => ((IHasSliderVelocity)point).SliderVelocity).Distinct().Count() == 1
|
||||||
|
? ((IHasSliderVelocity)relevantObjects.First()).SliderVelocityBindable
|
||||||
|
: null;
|
||||||
|
|
||||||
if (selectedPointBindable != null)
|
if (selectedPointBindable != null)
|
||||||
{
|
{
|
||||||
@ -139,4 +142,45 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal partial class SliderVelocityInspector : EditorInspector
|
||||||
|
{
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
EditorBeatmap.TransactionBegan += updateInspectorText;
|
||||||
|
EditorBeatmap.TransactionEnded += updateInspectorText;
|
||||||
|
updateInspectorText();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateInspectorText()
|
||||||
|
{
|
||||||
|
InspectorText.Clear();
|
||||||
|
|
||||||
|
double[] sliderVelocities = EditorBeatmap.HitObjects.OfType<IHasSliderVelocity>().Select(sv => sv.SliderVelocity).OrderBy(v => v).ToArray();
|
||||||
|
|
||||||
|
if (sliderVelocities.Length < 2)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double? modeSliderVelocity = sliderVelocities.GroupBy(v => v).MaxBy(v => v.Count())?.Key;
|
||||||
|
double? medianSliderVelocity = sliderVelocities[sliderVelocities.Length / 2];
|
||||||
|
|
||||||
|
AddHeader("Average velocity");
|
||||||
|
AddValue($"{medianSliderVelocity:#,0.00}x");
|
||||||
|
|
||||||
|
AddHeader("Most used velocity");
|
||||||
|
AddValue($"{modeSliderVelocity:#,0.00}x");
|
||||||
|
|
||||||
|
AddHeader("Velocity range");
|
||||||
|
AddValue($"{sliderVelocities.First():#,0.00}x - {sliderVelocities.Last():#,0.00}x");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
EditorBeatmap.TransactionBegan -= updateInspectorText;
|
||||||
|
EditorBeatmap.TransactionEnded -= updateInspectorText;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Colour = Color4.DarkBlue,
|
Colour = Color4.DarkBlue,
|
||||||
Size = new Vector2(0.96f)
|
Size = OsuLogo.SCALE_ADJUST,
|
||||||
},
|
},
|
||||||
new Circle
|
new Circle
|
||||||
{
|
{
|
||||||
|
@ -35,6 +35,12 @@ namespace osu.Game.Screens.Menu
|
|||||||
|
|
||||||
private const double transition_length = 300;
|
private const double transition_length = 300;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The osu! logo sprite has a shadow included in its texture.
|
||||||
|
/// This adjustment vector is used to match the precise edge of the border of the logo.
|
||||||
|
/// </summary>
|
||||||
|
public static readonly Vector2 SCALE_ADJUST = new Vector2(0.96f);
|
||||||
|
|
||||||
private readonly Sprite logo;
|
private readonly Sprite logo;
|
||||||
private readonly CircularContainer logoContainer;
|
private readonly CircularContainer logoContainer;
|
||||||
private readonly Container logoBounceContainer;
|
private readonly Container logoBounceContainer;
|
||||||
@ -150,7 +156,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Alpha = visualizer_default_alpha,
|
Alpha = visualizer_default_alpha,
|
||||||
Size = new Vector2(0.96f)
|
Size = SCALE_ADJUST
|
||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
@ -162,7 +168,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Scale = new Vector2(0.88f),
|
Scale = SCALE_ADJUST,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
@ -406,7 +412,7 @@ namespace osu.Game.Screens.Menu
|
|||||||
public void Impact()
|
public void Impact()
|
||||||
{
|
{
|
||||||
impactContainer.FadeOutFromOne(250, Easing.In);
|
impactContainer.FadeOutFromOne(250, Easing.In);
|
||||||
impactContainer.ScaleTo(0.96f);
|
impactContainer.ScaleTo(SCALE_ADJUST);
|
||||||
impactContainer.ScaleTo(1.12f, 250);
|
impactContainer.ScaleTo(1.12f, 250);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ namespace osu.Game.Screens.Play.Break
|
|||||||
set
|
set
|
||||||
{
|
{
|
||||||
icon.Size = value;
|
icon.Size = value;
|
||||||
base.Size = value + BlurSigma * 2.5f;
|
base.Size = value + BlurSigma * 5;
|
||||||
ForceRedraw();
|
ForceRedraw();
|
||||||
}
|
}
|
||||||
get => base.Size;
|
get => base.Size;
|
||||||
|
@ -58,7 +58,7 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
|
|
||||||
item = value;
|
item = value;
|
||||||
|
|
||||||
if (IsLoaded)
|
if (IsLoaded && !IsDisposed)
|
||||||
UpdateItem();
|
UpdateItem();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -165,5 +165,13 @@ namespace osu.Game.Screens.Select.Carousel
|
|||||||
Item.State.Value = CarouselItemState.Selected;
|
Item.State.Value = CarouselItemState.Selected;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void Dispose(bool isDisposing)
|
||||||
|
{
|
||||||
|
base.Dispose(isDisposing);
|
||||||
|
|
||||||
|
// This is important to clean up event subscriptions.
|
||||||
|
Item = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -277,6 +277,7 @@
|
|||||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_INTERNAL_MODIFIER/@EntryValue">Explicit</s:String>
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/DEFAULT_INTERNAL_MODIFIER/@EntryValue">Explicit</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/LOCAL_FUNCTION_BODY/@EntryValue">ExpressionBody</s:String>
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/LOCAL_FUNCTION_BODY/@EntryValue">ExpressionBody</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">BlockBody</s:String>
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/METHOD_OR_OPERATOR_BODY/@EntryValue">BlockBody</s:String>
|
||||||
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/NAMESPACE_BODY/@EntryValue">BlockScoped</s:String>
|
||||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/OBJECT_CREATION_WHEN_TYPE_EVIDENT/@EntryValue">ExplicitlyTyped</s:String>
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/OBJECT_CREATION_WHEN_TYPE_EVIDENT/@EntryValue">ExplicitlyTyped</s:String>
|
||||||
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/USE_HEURISTICS_FOR_BODY_STYLE/@EntryValue">True</s:Boolean>
|
<s:Boolean x:Key="/Default/CodeStyle/CodeFormatting/CSharpCodeStyle/USE_HEURISTICS_FOR_BODY_STYLE/@EntryValue">True</s:Boolean>
|
||||||
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
|
<s:String x:Key="/Default/CodeStyle/CodeFormatting/CSharpFormat/ACCESSOR_DECLARATION_BRACES/@EntryValue">NEXT_LINE</s:String>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user