mirror of
https://github.com/osukey/osukey.git
synced 2025-04-29 02:37:25 +09:00
Merge branch 'master' into update-framework
This commit is contained in:
commit
e2410c00e2
@ -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
|
||||||
|
@ -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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,10 +8,12 @@ using System.Linq;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using JetBrains.Annotations;
|
using JetBrains.Annotations;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
@ -30,6 +32,7 @@ using osu.Game.Overlays.Chat.Listing;
|
|||||||
using osu.Game.Overlays.Chat.ChannelList;
|
using osu.Game.Overlays.Chat.ChannelList;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Input;
|
using osuTK.Input;
|
||||||
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
|
||||||
namespace osu.Game.Tests.Visual.Online
|
namespace osu.Game.Tests.Visual.Online
|
||||||
{
|
{
|
||||||
@ -53,6 +56,9 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
|
|
||||||
private int currentMessageId;
|
private int currentMessageId;
|
||||||
|
|
||||||
|
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
|
||||||
|
private readonly ManualResetEventSlim requestLock = new ManualResetEventSlim();
|
||||||
|
|
||||||
[SetUp]
|
[SetUp]
|
||||||
public void SetUp() => Schedule(() =>
|
public void SetUp() => Schedule(() =>
|
||||||
{
|
{
|
||||||
@ -576,6 +582,75 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChatReport()
|
||||||
|
{
|
||||||
|
ChatReportRequest request = null;
|
||||||
|
|
||||||
|
AddStep("Show overlay with channel", () =>
|
||||||
|
{
|
||||||
|
chatOverlay.Show();
|
||||||
|
channelManager.CurrentChannel.Value = channelManager.JoinChannel(testChannel1);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
|
||||||
|
waitForChannel1Visible();
|
||||||
|
|
||||||
|
AddStep("Setup request handling", () =>
|
||||||
|
{
|
||||||
|
requestLock.Reset();
|
||||||
|
|
||||||
|
dummyAPI.HandleRequest = r =>
|
||||||
|
{
|
||||||
|
if (!(r is ChatReportRequest req))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
request = req;
|
||||||
|
requestLock.Wait(10000);
|
||||||
|
req.TriggerSuccess();
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Show report popover", () => this.ChildrenOfType<ChatLine>().First().ShowPopover());
|
||||||
|
|
||||||
|
AddStep("Set report reason to other", () =>
|
||||||
|
{
|
||||||
|
var reason = this.ChildrenOfType<OsuEnumDropdown<ChatReportReason>>().Single();
|
||||||
|
reason.Current.Value = ChatReportReason.Other;
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Try to report", () =>
|
||||||
|
{
|
||||||
|
var btn = this.ChildrenOfType<ReportChatPopover>().Single().ChildrenOfType<RoundedButton>().Single();
|
||||||
|
InputManager.MoveMouseTo(btn);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("Nothing happened", () => this.ChildrenOfType<ReportChatPopover>().Any());
|
||||||
|
AddStep("Set report data", () =>
|
||||||
|
{
|
||||||
|
var field = this.ChildrenOfType<ReportChatPopover>().Single().ChildrenOfType<OsuTextBox>().Single();
|
||||||
|
field.Current.Value = "test other";
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Try to report", () =>
|
||||||
|
{
|
||||||
|
var btn = this.ChildrenOfType<ReportChatPopover>().Single().ChildrenOfType<RoundedButton>().Single();
|
||||||
|
InputManager.MoveMouseTo(btn);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("Overlay closed", () => !this.ChildrenOfType<ReportChatPopover>().Any());
|
||||||
|
AddStep("Complete request", () => requestLock.Set());
|
||||||
|
AddUntilStep("Request sent", () => request != null);
|
||||||
|
AddUntilStep("Info message displayed", () => channelManager.CurrentChannel.Value.Messages.Last(), () => Is.InstanceOf(typeof(InfoMessage)));
|
||||||
|
}
|
||||||
|
|
||||||
private void joinTestChannel(int i)
|
private void joinTestChannel(int i)
|
||||||
{
|
{
|
||||||
AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i]));
|
AddStep($"Join test channel {i}", () => channelManager.JoinChannel(testChannels[i]));
|
||||||
|
@ -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);
|
||||||
|
|
||||||
|
133
osu.Game/Graphics/UserInterfaceV2/ReportPopover.cs
Normal file
133
osu.Game/Graphics/UserInterfaceV2/ReportPopover.cs
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
using osuTK;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterfaceV2
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A generic popover for sending an online report about something.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="TReportReason">An enumeration type with all valid reasons for the report.</typeparam>
|
||||||
|
public abstract partial class ReportPopover<TReportReason> : OsuPopover
|
||||||
|
where TReportReason : struct, Enum
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The action to run when the report is finalised.
|
||||||
|
/// The arguments to this action are: the reason for the report, and an optional additional comment.
|
||||||
|
/// </summary>
|
||||||
|
public Action<TReportReason, string>? Action;
|
||||||
|
|
||||||
|
private OsuEnumDropdown<TReportReason> reasonDropdown = null!;
|
||||||
|
private OsuTextBox commentsTextBox = null!;
|
||||||
|
private RoundedButton submitButton = null!;
|
||||||
|
|
||||||
|
private readonly LocalisableString header;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="ReportPopover{TReportReason}"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="headerString">The text to display in the header of the popover.</param>
|
||||||
|
protected ReportPopover(LocalisableString headerString)
|
||||||
|
{
|
||||||
|
header = headerString;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OsuColour colours)
|
||||||
|
{
|
||||||
|
Child = new ReverseChildIDFillFlowContainer<Drawable>
|
||||||
|
{
|
||||||
|
Direction = FillDirection.Vertical,
|
||||||
|
Width = 500,
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
Spacing = new Vector2(7),
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
new SpriteIcon
|
||||||
|
{
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Icon = FontAwesome.Solid.ExclamationTriangle,
|
||||||
|
Size = new Vector2(36),
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Text = header,
|
||||||
|
Font = OsuFont.Torus.With(size: 25),
|
||||||
|
Margin = new MarginPadding { Bottom = 10 }
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Text = UsersStrings.ReportReason,
|
||||||
|
},
|
||||||
|
new Container
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Height = 40,
|
||||||
|
Child = reasonDropdown = new OsuEnumDropdown<TReportReason>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X
|
||||||
|
}
|
||||||
|
},
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Text = UsersStrings.ReportComments,
|
||||||
|
},
|
||||||
|
commentsTextBox = new OsuTextBox
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
PlaceholderText = UsersStrings.ReportPlaceholder,
|
||||||
|
},
|
||||||
|
submitButton = new RoundedButton
|
||||||
|
{
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Width = 200,
|
||||||
|
BackgroundColour = colours.Red3,
|
||||||
|
Text = UsersStrings.ReportActionsSend,
|
||||||
|
Action = () =>
|
||||||
|
{
|
||||||
|
Action?.Invoke(reasonDropdown.Current.Value, commentsTextBox.Text);
|
||||||
|
this.HidePopover();
|
||||||
|
},
|
||||||
|
Margin = new MarginPadding { Bottom = 5, Top = 10 },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
commentsTextBox.Current.BindValueChanged(_ => updateStatus());
|
||||||
|
|
||||||
|
reasonDropdown.Current.BindValueChanged(_ => updateStatus());
|
||||||
|
|
||||||
|
updateStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateStatus()
|
||||||
|
{
|
||||||
|
submitButton.Enabled.Value = !string.IsNullOrWhiteSpace(commentsTextBox.Current.Value) || !IsCommentRequired(reasonDropdown.Current.Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether an additional comment is required for submitting the report with the supplied <paramref name="reason"/>.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool IsCommentRequired(TReportReason reason) => true;
|
||||||
|
}
|
||||||
|
}
|
@ -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}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
38
osu.Game/Online/API/Requests/ChatReportRequest.cs
Normal file
38
osu.Game/Online/API/Requests/ChatReportRequest.cs
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
// 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.Net.Http;
|
||||||
|
using osu.Framework.IO.Network;
|
||||||
|
using osu.Game.Overlays.Chat;
|
||||||
|
|
||||||
|
namespace osu.Game.Online.API.Requests
|
||||||
|
{
|
||||||
|
public class ChatReportRequest : APIRequest
|
||||||
|
{
|
||||||
|
public readonly long? MessageId;
|
||||||
|
public readonly ChatReportReason Reason;
|
||||||
|
public readonly string Comment;
|
||||||
|
|
||||||
|
public ChatReportRequest(long? id, ChatReportReason reason, string comment)
|
||||||
|
{
|
||||||
|
MessageId = id;
|
||||||
|
Reason = reason;
|
||||||
|
Comment = comment;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override WebRequest CreateWebRequest()
|
||||||
|
{
|
||||||
|
var req = base.CreateWebRequest();
|
||||||
|
req.Method = HttpMethod.Post;
|
||||||
|
|
||||||
|
req.AddParameter(@"reportable_type", @"message");
|
||||||
|
req.AddParameter(@"reportable_id", $"{MessageId}");
|
||||||
|
req.AddParameter(@"reason", Reason.ToString());
|
||||||
|
req.AddParameter(@"comments", Comment);
|
||||||
|
|
||||||
|
return req;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override string Target => @"reports";
|
||||||
|
}
|
||||||
|
}
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,28 @@
|
|||||||
// 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.Linq;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
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.Extensions.Color4Extensions;
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
using osu.Framework.Extensions.LocalisationExtensions;
|
using osu.Framework.Extensions.LocalisationExtensions;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Game.Configuration;
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Chat
|
namespace osu.Game.Overlays.Chat
|
||||||
{
|
{
|
||||||
public partial class ChatLine : CompositeDrawable
|
public partial class ChatLine : CompositeDrawable, IHasPopover
|
||||||
{
|
{
|
||||||
private Message message = null!;
|
private Message message = null!;
|
||||||
|
|
||||||
@ -55,7 +58,7 @@ namespace osu.Game.Overlays.Chat
|
|||||||
|
|
||||||
private readonly OsuSpriteText drawableTimestamp;
|
private readonly OsuSpriteText drawableTimestamp;
|
||||||
|
|
||||||
private readonly DrawableUsername drawableUsername;
|
private readonly DrawableChatUsername drawableUsername;
|
||||||
|
|
||||||
private readonly LinkFlowContainer drawableContentFlow;
|
private readonly LinkFlowContainer drawableContentFlow;
|
||||||
|
|
||||||
@ -92,7 +95,7 @@ namespace osu.Game.Overlays.Chat
|
|||||||
Font = OsuFont.GetFont(size: FontSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true),
|
Font = OsuFont.GetFont(size: FontSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true),
|
||||||
AlwaysPresent = true,
|
AlwaysPresent = true,
|
||||||
},
|
},
|
||||||
drawableUsername = new DrawableUsername(message.Sender)
|
drawableUsername = new DrawableChatUsername(message.Sender)
|
||||||
{
|
{
|
||||||
Width = UsernameWidth,
|
Width = UsernameWidth,
|
||||||
FontSize = FontSize,
|
FontSize = FontSize,
|
||||||
@ -100,6 +103,7 @@ namespace osu.Game.Overlays.Chat
|
|||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.TopRight,
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.TopRight,
|
||||||
Margin = new MarginPadding { Horizontal = Spacing },
|
Margin = new MarginPadding { Horizontal = Spacing },
|
||||||
|
ReportRequested = this.ShowPopover,
|
||||||
},
|
},
|
||||||
drawableContentFlow = new LinkFlowContainer(styleMessageContent)
|
drawableContentFlow = new LinkFlowContainer(styleMessageContent)
|
||||||
{
|
{
|
||||||
@ -128,6 +132,8 @@ namespace osu.Game.Overlays.Chat
|
|||||||
FinishTransforms(true);
|
FinishTransforms(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Popover GetPopover() => new ReportChatPopover(message);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Performs a highlight animation on this <see cref="ChatLine"/>.
|
/// Performs a highlight animation on this <see cref="ChatLine"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
37
osu.Game/Overlays/Chat/ChatReportReason.cs
Normal file
37
osu.Game/Overlays/Chat/ChatReportReason.cs
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// 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.ComponentModel;
|
||||||
|
using osu.Framework.Localisation;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Chat
|
||||||
|
{
|
||||||
|
/// <remarks>
|
||||||
|
/// References:
|
||||||
|
/// https://github.com/ppy/osu-web/blob/0a41b13acf5f47bb0d2b08bab42a9646b7ab5821/app/Models/UserReport.php#L50
|
||||||
|
/// https://github.com/ppy/osu-web/blob/0a41b13acf5f47bb0d2b08bab42a9646b7ab5821/app/Models/UserReport.php#L39
|
||||||
|
/// </remarks>
|
||||||
|
public enum ChatReportReason
|
||||||
|
{
|
||||||
|
[Description("Insulting People")]
|
||||||
|
[LocalisableDescription(typeof(UsersStrings), nameof(UsersStrings.ReportOptionsInsults))]
|
||||||
|
Insults,
|
||||||
|
|
||||||
|
[Description("Spam")]
|
||||||
|
[LocalisableDescription(typeof(UsersStrings), nameof(UsersStrings.ReportOptionsSpam))]
|
||||||
|
Spam,
|
||||||
|
|
||||||
|
[Description("Unwanted Content")]
|
||||||
|
[LocalisableDescription(typeof(UsersStrings), nameof(UsersStrings.ReportOptionsUnwantedContent))]
|
||||||
|
UnwantedContent,
|
||||||
|
|
||||||
|
[Description("Nonsense")]
|
||||||
|
[LocalisableDescription(typeof(UsersStrings), nameof(UsersStrings.ReportOptionsNonsense))]
|
||||||
|
Nonsense,
|
||||||
|
|
||||||
|
[Description("Other")]
|
||||||
|
[LocalisableDescription(typeof(UsersStrings), nameof(UsersStrings.ReportOptionsOther))]
|
||||||
|
Other
|
||||||
|
}
|
||||||
|
}
|
@ -29,8 +29,10 @@ using ChatStrings = osu.Game.Localisation.ChatStrings;
|
|||||||
|
|
||||||
namespace osu.Game.Overlays.Chat
|
namespace osu.Game.Overlays.Chat
|
||||||
{
|
{
|
||||||
public partial class DrawableUsername : OsuClickableContainer, IHasContextMenu
|
public partial class DrawableChatUsername : OsuClickableContainer, IHasContextMenu
|
||||||
{
|
{
|
||||||
|
public Action? ReportRequested;
|
||||||
|
|
||||||
public Color4 AccentColour { get; }
|
public Color4 AccentColour { get; }
|
||||||
|
|
||||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) =>
|
||||||
@ -75,7 +77,7 @@ namespace osu.Game.Overlays.Chat
|
|||||||
|
|
||||||
private readonly Drawable colouredDrawable;
|
private readonly Drawable colouredDrawable;
|
||||||
|
|
||||||
public DrawableUsername(APIUser user)
|
public DrawableChatUsername(APIUser user)
|
||||||
{
|
{
|
||||||
this.user = user;
|
this.user = user;
|
||||||
|
|
||||||
@ -169,6 +171,9 @@ namespace osu.Game.Overlays.Chat
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!user.Equals(api.LocalUser.Value))
|
||||||
|
items.Add(new OsuMenuItem("Report", MenuItemType.Destructive, ReportRequested));
|
||||||
|
|
||||||
return items.ToArray();
|
return items.ToArray();
|
||||||
}
|
}
|
||||||
}
|
}
|
41
osu.Game/Overlays/Chat/ReportChatPopover.cs
Normal file
41
osu.Game/Overlays/Chat/ReportChatPopover.cs
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// 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.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.Chat;
|
||||||
|
using osu.Game.Resources.Localisation.Web;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Chat
|
||||||
|
{
|
||||||
|
public partial class ReportChatPopover : ReportPopover<ChatReportReason>
|
||||||
|
{
|
||||||
|
[Resolved]
|
||||||
|
private IAPIProvider api { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private ChannelManager channelManager { get; set; } = null!;
|
||||||
|
|
||||||
|
private readonly Message message;
|
||||||
|
|
||||||
|
public ReportChatPopover(Message message)
|
||||||
|
: base(ReportStrings.UserTitle(message.Sender?.Username ?? @"Someone"))
|
||||||
|
{
|
||||||
|
this.message = message;
|
||||||
|
Action = report;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool IsCommentRequired(ChatReportReason reason) => reason == ChatReportReason.Other;
|
||||||
|
|
||||||
|
private void report(ChatReportReason reason, string comments)
|
||||||
|
{
|
||||||
|
var request = new ChatReportRequest(message.Id, reason, comments);
|
||||||
|
|
||||||
|
request.Success += () => channelManager.CurrentChannel.Value.AddNewMessages(new InfoMessage(UsersStrings.ReportThanks.ToString()));
|
||||||
|
|
||||||
|
api.Queue(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Input;
|
using osu.Framework.Input;
|
||||||
using osu.Framework.Input.Bindings;
|
using osu.Framework.Input.Bindings;
|
||||||
@ -134,9 +135,13 @@ namespace osu.Game.Overlays
|
|||||||
},
|
},
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
currentChannelContainer = new Container<DrawableChannel>
|
new PopoverContainer
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Child = currentChannelContainer = new Container<DrawableChannel>
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
}
|
||||||
},
|
},
|
||||||
loading = new LoadingLayer(true),
|
loading = new LoadingLayer(true),
|
||||||
channelListing = new ChannelListing
|
channelListing = new ChannelListing
|
||||||
|
@ -57,6 +57,11 @@ namespace osu.Game.Overlays.Comments
|
|||||||
link.AddLink(ReportStrings.CommentButton.ToLower(), this.ShowPopover);
|
link.AddLink(ReportStrings.CommentButton.ToLower(), this.ShowPopover);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Popover GetPopover() => new ReportCommentPopover(comment)
|
||||||
|
{
|
||||||
|
Action = report
|
||||||
|
};
|
||||||
|
|
||||||
private void report(CommentReportReason reason, string comments)
|
private void report(CommentReportReason reason, string comments)
|
||||||
{
|
{
|
||||||
var request = new CommentReportRequest(comment.Id, reason, comments);
|
var request = new CommentReportRequest(comment.Id, reason, comments);
|
||||||
@ -83,10 +88,5 @@ namespace osu.Game.Overlays.Comments
|
|||||||
|
|
||||||
api.Queue(request);
|
api.Queue(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Popover GetPopover() => new ReportCommentPopover(comment)
|
|
||||||
{
|
|
||||||
Action = report
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,111 +1,17 @@
|
|||||||
// 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 osu.Framework.Allocation;
|
|
||||||
using osu.Framework.Extensions;
|
|
||||||
using osu.Framework.Graphics;
|
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using osu.Framework.Graphics.Sprites;
|
|
||||||
using osu.Game.Graphics;
|
|
||||||
using osu.Game.Graphics.Containers;
|
|
||||||
using osu.Game.Graphics.Sprites;
|
|
||||||
using osu.Game.Graphics.UserInterface;
|
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Resources.Localisation.Web;
|
using osu.Game.Resources.Localisation.Web;
|
||||||
using osuTK;
|
|
||||||
|
|
||||||
namespace osu.Game.Overlays.Comments
|
namespace osu.Game.Overlays.Comments
|
||||||
{
|
{
|
||||||
public partial class ReportCommentPopover : OsuPopover
|
public partial class ReportCommentPopover : ReportPopover<CommentReportReason>
|
||||||
{
|
{
|
||||||
public Action<CommentReportReason, string>? Action;
|
|
||||||
|
|
||||||
private readonly Comment? comment;
|
|
||||||
|
|
||||||
private OsuEnumDropdown<CommentReportReason> reasonDropdown = null!;
|
|
||||||
private OsuTextBox commentsTextBox = null!;
|
|
||||||
private RoundedButton submitButton = null!;
|
|
||||||
|
|
||||||
public ReportCommentPopover(Comment? comment)
|
public ReportCommentPopover(Comment? comment)
|
||||||
|
: base(ReportStrings.CommentTitle(comment?.User?.Username ?? comment?.LegacyName ?? @"Someone"))
|
||||||
{
|
{
|
||||||
this.comment = comment;
|
|
||||||
}
|
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
|
||||||
private void load(OsuColour colours)
|
|
||||||
{
|
|
||||||
Child = new ReverseChildIDFillFlowContainer<Drawable>
|
|
||||||
{
|
|
||||||
Direction = FillDirection.Vertical,
|
|
||||||
Width = 500,
|
|
||||||
AutoSizeAxes = Axes.Y,
|
|
||||||
Spacing = new Vector2(7),
|
|
||||||
Children = new Drawable[]
|
|
||||||
{
|
|
||||||
new SpriteIcon
|
|
||||||
{
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Icon = FontAwesome.Solid.ExclamationTriangle,
|
|
||||||
Size = new Vector2(36),
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Text = ReportStrings.CommentTitle(comment?.User?.Username ?? comment?.LegacyName ?? @"Someone"),
|
|
||||||
Font = OsuFont.Torus.With(size: 25),
|
|
||||||
Margin = new MarginPadding { Bottom = 10 }
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Text = UsersStrings.ReportReason,
|
|
||||||
},
|
|
||||||
new Container
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
Height = 40,
|
|
||||||
Child = reasonDropdown = new OsuEnumDropdown<CommentReportReason>
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X
|
|
||||||
}
|
|
||||||
},
|
|
||||||
new OsuSpriteText
|
|
||||||
{
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Text = UsersStrings.ReportComments,
|
|
||||||
},
|
|
||||||
commentsTextBox = new OsuTextBox
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.X,
|
|
||||||
PlaceholderText = UsersStrings.ReportPlaceholder,
|
|
||||||
},
|
|
||||||
submitButton = new RoundedButton
|
|
||||||
{
|
|
||||||
Origin = Anchor.TopCentre,
|
|
||||||
Anchor = Anchor.TopCentre,
|
|
||||||
Width = 200,
|
|
||||||
BackgroundColour = colours.Red3,
|
|
||||||
Text = UsersStrings.ReportActionsSend,
|
|
||||||
Action = () =>
|
|
||||||
{
|
|
||||||
Action?.Invoke(reasonDropdown.Current.Value, commentsTextBox.Text);
|
|
||||||
this.HidePopover();
|
|
||||||
},
|
|
||||||
Margin = new MarginPadding { Bottom = 5, Top = 10 },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
commentsTextBox.Current.BindValueChanged(e =>
|
|
||||||
{
|
|
||||||
submitButton.Enabled.Value = !string.IsNullOrWhiteSpace(e.NewValue);
|
|
||||||
}, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,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,17 +3,18 @@
|
|||||||
|
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Objects.Types;
|
namespace osu.Game.Rulesets.Objects.Types
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// A HitObject that has a slider velocity multiplier.
|
|
||||||
/// </summary>
|
|
||||||
public interface IHasSliderVelocity
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The slider velocity multiplier.
|
/// A HitObject that has a slider velocity multiplier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
double SliderVelocity { get; set; }
|
public interface IHasSliderVelocity
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The slider velocity multiplier.
|
||||||
|
/// </summary>
|
||||||
|
double SliderVelocity { get; set; }
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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