diff --git a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
index 00fd8247d8..fed1013ae1 100644
--- a/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Catch/Properties/AssemblyInfo.cs
@@ -2,11 +2,10 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Runtime.CompilerServices;
-using osu.Framework.Testing;
// We publish our internal attributes to other sub-projects of the framework.
// Note, that we omit visual tests as they are meant to test the framework
// behavior "in the wild".
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests")]
-[assembly: InternalsVisibleTo(DynamicClassCompiler.DYNAMIC_ASSEMBLY_NAME)]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Catch.Tests.Dynamic")]
diff --git a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs
index c2c65433ec..515aeab9df 100644
--- a/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Mania/Properties/AssemblyInfo.cs
@@ -2,11 +2,10 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Runtime.CompilerServices;
-using osu.Framework.Testing;
// We publish our internal attributes to other sub-projects of the framework.
// Note, that we omit visual tests as they are meant to test the framework
// behavior "in the wild".
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests")]
-[assembly: InternalsVisibleTo(DynamicClassCompiler.DYNAMIC_ASSEMBLY_NAME)]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Mania.Tests.Dynamic")]
diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs
index b48dd73bb5..89a7686581 100644
--- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs
@@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
Size = hitCircle.Size;
Scale = hitCircle.Scale;
+ CornerRadius = Size.X / 2;
+
AddInternal(new RingPiece());
hitCircle.HitObject.PositionChanged += _ => Position = hitCircle.Position;
diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs
index 586b516a11..96ff14205e 100644
--- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs
@@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
Scale = slider.HeadCircle.Scale;
AddInternal(new RingPiece());
+
+ Select();
}
[BackgroundDependencyLoader]
@@ -52,5 +54,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
RelativeAnchorPosition = hitObject.RelativeAnchorPosition;
}
+
+ // Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input.
+ public override bool HandleMouseInput => false;
}
}
diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs
index 53f02617cd..629bce1847 100644
--- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs
+++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs
@@ -3,6 +3,7 @@
using osu.Framework.Allocation;
using osu.Framework.Graphics;
+using osu.Framework.Graphics.Primitives;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Objects;
@@ -59,5 +60,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
}
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => body.ReceiveMouseInputAt(screenSpacePos);
+
+ public override Vector2 SelectionPoint => ToScreenSpace(OriginPosition);
+ public override Quad SelectionQuad => body.PathDrawQuad;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
index 3872821b96..5373926138 100644
--- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
+++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs
@@ -10,7 +10,6 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Judgements;
-using osu.Framework.Graphics.Primitives;
using osu.Game.Configuration;
using osu.Game.Rulesets.Scoring;
using OpenTK.Graphics;
@@ -177,8 +176,5 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
public Drawable ProxiedLayer => HeadCircle.ApproachCircle;
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Body.ReceiveMouseInputAt(screenSpacePos);
-
- public override Vector2 SelectionPoint => ToScreenSpace(OriginPosition);
- public override Quad SelectionQuad => Body.PathDrawQuad;
}
}
diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
index c00c30ced9..f64db6ba9e 100644
--- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
+++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs
@@ -7,10 +7,11 @@ using osu.Game.Rulesets.Objects;
using OpenTK;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
+using osu.Game.Rulesets.Edit.Types;
namespace osu.Game.Rulesets.Osu.Objects
{
- public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition
+ public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasEditablePosition
{
public const double OBJECT_RADIUS = 64;
diff --git a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs
index 7532646a32..ea2c2c6729 100644
--- a/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Osu/Properties/AssemblyInfo.cs
@@ -2,11 +2,10 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Runtime.CompilerServices;
-using osu.Framework.Testing;
// We publish our internal attributes to other sub-projects of the framework.
// Note, that we omit visual tests as they are meant to test the framework
// behavior "in the wild".
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests")]
-[assembly: InternalsVisibleTo(DynamicClassCompiler.DYNAMIC_ASSEMBLY_NAME)]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Osu.Tests.Dynamic")]
diff --git a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs
index b7ed9f86b0..77218af5e1 100644
--- a/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs
+++ b/osu.Game.Rulesets.Taiko/Properties/AssemblyInfo.cs
@@ -2,11 +2,10 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Runtime.CompilerServices;
-using osu.Framework.Testing;
// We publish our internal attributes to other sub-projects of the framework.
// Note, that we omit visual tests as they are meant to test the framework
// behavior "in the wild".
[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests")]
-[assembly: InternalsVisibleTo(DynamicClassCompiler.DYNAMIC_ASSEMBLY_NAME)]
+[assembly: InternalsVisibleTo("osu.Game.Rulesets.Taiko.Tests.Dynamic")]
diff --git a/osu.Game.Tests/Visual/TestCaseCursors.cs b/osu.Game.Tests/Visual/TestCaseCursors.cs
index 72e699c54b..4f4fdbeb5b 100644
--- a/osu.Game.Tests/Visual/TestCaseCursors.cs
+++ b/osu.Game.Tests/Visual/TestCaseCursors.cs
@@ -9,7 +9,6 @@ using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.MathUtils;
-using osu.Framework.Testing.Input;
using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.Sprites;
using OpenTK;
@@ -18,88 +17,74 @@ using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
{
[TestFixture]
- public class TestCaseCursors : OsuTestCase
+ public class TestCaseCursors : ManualInputManagerTestCase
{
- private readonly ManualInputManager inputManager;
private readonly CursorOverrideContainer cursorOverrideContainer;
private readonly CustomCursorBox[] cursorBoxes = new CustomCursorBox[6];
public TestCaseCursors()
{
- Child = inputManager = new ManualInputManager
+ Child = cursorOverrideContainer = new CursorOverrideContainer
{
- Child = cursorOverrideContainer = new CursorOverrideContainer
+ RelativeSizeAxes = Axes.Both,
+ Children = new[]
{
- RelativeSizeAxes = Axes.Both,
- Children = new[]
+ // Middle user
+ cursorBoxes[0] = new CustomCursorBox(Color4.Green)
{
- // Middle user
- cursorBoxes[0] = new CustomCursorBox(Color4.Green)
- {
- Anchor = Anchor.Centre,
- Origin = Anchor.Centre,
- RelativeSizeAxes = Axes.Both,
- Size = new Vector2(0.5f),
- },
- // Top-left user
- cursorBoxes[1] = new CustomCursorBox(Color4.Blue)
- {
- RelativeSizeAxes = Axes.Both,
- Size = new Vector2(0.4f)
- },
- // Bottom-right user
- cursorBoxes[2] = new CustomCursorBox(Color4.Red)
- {
- Anchor = Anchor.BottomRight,
- Origin = Anchor.BottomRight,
- RelativeSizeAxes = Axes.Both,
- Size = new Vector2(0.4f)
- },
- // Bottom-left local
- cursorBoxes[3] = new CustomCursorBox(Color4.Magenta, false)
- {
- Anchor = Anchor.BottomLeft,
- Origin = Anchor.BottomLeft,
- RelativeSizeAxes = Axes.Both,
- Size = new Vector2(0.4f)
- },
- // Top-right local
- cursorBoxes[4] = new CustomCursorBox(Color4.Cyan, false)
- {
- Anchor = Anchor.TopRight,
- Origin = Anchor.TopRight,
- RelativeSizeAxes = Axes.Both,
- Size = new Vector2(0.4f)
- },
- // Left-local
- cursorBoxes[5] = new CustomCursorBox(Color4.Yellow, false)
- {
- Anchor = Anchor.CentreLeft,
- Origin = Anchor.CentreLeft,
- RelativeSizeAxes = Axes.Both,
- Size = new Vector2(0.2f, 1),
- },
- }
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.5f),
+ },
+ // Top-left user
+ cursorBoxes[1] = new CustomCursorBox(Color4.Blue)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.4f)
+ },
+ // Bottom-right user
+ cursorBoxes[2] = new CustomCursorBox(Color4.Red)
+ {
+ Anchor = Anchor.BottomRight,
+ Origin = Anchor.BottomRight,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.4f)
+ },
+ // Bottom-left local
+ cursorBoxes[3] = new CustomCursorBox(Color4.Magenta, false)
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.4f)
+ },
+ // Top-right local
+ cursorBoxes[4] = new CustomCursorBox(Color4.Cyan, false)
+ {
+ Anchor = Anchor.TopRight,
+ Origin = Anchor.TopRight,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.4f)
+ },
+ // Left-local
+ cursorBoxes[5] = new CustomCursorBox(Color4.Yellow, false)
+ {
+ Anchor = Anchor.CentreLeft,
+ Origin = Anchor.CentreLeft,
+ RelativeSizeAxes = Axes.Both,
+ Size = new Vector2(0.2f, 1),
+ },
}
};
- returnUserInput();
-
AddToggleStep("Smooth transitions", b => cursorBoxes.ForEach(box => box.SmoothTransition = b));
testUserCursor();
testLocalCursor();
testUserCursorOverride();
testMultipleLocalCursors();
- returnUserInput();
- }
-
- ///
- /// Returns input back to the user.
- ///
- private void returnUserInput()
- {
- AddStep("Return user input", () => inputManager.UseParentState = true);
+ ReturnUserInput();
}
///
@@ -109,7 +94,7 @@ namespace osu.Game.Tests.Visual
///
private void testUserCursor()
{
- AddStep("Move to green area", () => inputManager.MoveMouseTo(cursorBoxes[0]));
+ AddStep("Move to green area", () => InputManager.MoveMouseTo(cursorBoxes[0]));
AddAssert("Check green cursor visible", () => checkVisible(cursorBoxes[0].Cursor));
AddAssert("Check green cursor at mouse", () => checkAtMouse(cursorBoxes[0].Cursor));
AddStep("Move out", moveOut);
@@ -124,7 +109,7 @@ namespace osu.Game.Tests.Visual
///
private void testLocalCursor()
{
- AddStep("Move to purple area", () => inputManager.MoveMouseTo(cursorBoxes[3]));
+ AddStep("Move to purple area", () => InputManager.MoveMouseTo(cursorBoxes[3]));
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor));
AddAssert("Check global cursor visible", () => checkVisible(cursorOverrideContainer.Cursor));
@@ -141,7 +126,7 @@ namespace osu.Game.Tests.Visual
///
private void testUserCursorOverride()
{
- AddStep("Move to blue-green boundary", () => inputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
+ AddStep("Move to blue-green boundary", () => InputManager.MoveMouseTo(cursorBoxes[1].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor));
AddAssert("Check green cursor invisible", () => !checkVisible(cursorBoxes[0].Cursor));
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor));
@@ -156,7 +141,7 @@ namespace osu.Game.Tests.Visual
///
private void testMultipleLocalCursors()
{
- AddStep("Move to yellow-purple boundary", () => inputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
+ AddStep("Move to yellow-purple boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.BottomRight - new Vector2(10)));
AddAssert("Check purple cursor visible", () => checkVisible(cursorBoxes[3].Cursor));
AddAssert("Check purple cursor at mouse", () => checkAtMouse(cursorBoxes[3].Cursor));
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
@@ -172,7 +157,7 @@ namespace osu.Game.Tests.Visual
///
private void testUserOverrideWithLocal()
{
- AddStep("Move to yellow-blue boundary", () => inputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10)));
+ AddStep("Move to yellow-blue boundary", () => InputManager.MoveMouseTo(cursorBoxes[5].ScreenSpaceDrawQuad.TopRight - new Vector2(10)));
AddAssert("Check blue cursor visible", () => checkVisible(cursorBoxes[1].Cursor));
AddAssert("Check blue cursor at mouse", () => checkAtMouse(cursorBoxes[1].Cursor));
AddAssert("Check yellow cursor visible", () => checkVisible(cursorBoxes[5].Cursor));
@@ -186,7 +171,7 @@ namespace osu.Game.Tests.Visual
/// Moves the cursor to a point not covered by any cursor containers.
///
private void moveOut()
- => inputManager.MoveMouseTo(new Vector2(inputManager.ScreenSpaceDrawQuad.Centre.X, inputManager.ScreenSpaceDrawQuad.TopLeft.Y));
+ => InputManager.MoveMouseTo(new Vector2(InputManager.ScreenSpaceDrawQuad.Centre.X, InputManager.ScreenSpaceDrawQuad.TopLeft.Y));
///
/// Checks if a cursor is visible.
@@ -199,7 +184,7 @@ namespace osu.Game.Tests.Visual
///
/// The cursor to check.
private bool checkAtMouse(CursorContainer cursorContainer)
- => Precision.AlmostEquals(inputManager.CurrentState.Mouse.NativeState.Position, cursorContainer.ToScreenSpace(cursorContainer.ActiveCursor.DrawPosition));
+ => Precision.AlmostEquals(InputManager.CurrentState.Mouse.NativeState.Position, cursorContainer.ToScreenSpace(cursorContainer.ActiveCursor.DrawPosition));
private class CustomCursorBox : Container, IProvideCursor
{
diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs
similarity index 71%
rename from osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs
rename to osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs
index 24f5905131..72d60d8e01 100644
--- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs
+++ b/osu.Game.Tests/Visual/TestCaseHitObjectComposer.cs
@@ -3,15 +3,16 @@
using System;
using System.Collections.Generic;
+using JetBrains.Annotations;
using NUnit.Framework;
using osu.Framework.Allocation;
+using osu.Framework.Timing;
using OpenTK;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Edit;
-using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Screens.Compose.Layers;
using osu.Game.Tests.Beatmaps;
@@ -19,21 +20,23 @@ using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual
{
[TestFixture]
- public class TestCaseEditorSelectionLayer : EditorClockTestCase
+ public class TestCaseHitObjectComposer : OsuTestCase
{
public override IReadOnlyList RequiredTypes => new[]
{
- typeof(SelectionLayer),
- typeof(SelectionBox),
+ typeof(MaskSelection),
+ typeof(DragLayer),
typeof(HitObjectComposer),
typeof(OsuHitObjectComposer),
typeof(HitObjectMaskLayer),
- typeof(HitObjectMask),
- typeof(HitCircleMask),
- typeof(SliderMask),
- typeof(SliderCircleMask)
+ typeof(NotNullAttribute)
};
+ private DependencyContainer dependencies;
+
+ protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
+ => dependencies = new DependencyContainer(parent);
+
[BackgroundDependencyLoader]
private void load(OsuGameBase osuGame)
{
@@ -59,6 +62,10 @@ namespace osu.Game.Tests.Visual
},
});
+ var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
+ dependencies.CacheAs(clock);
+ dependencies.CacheAs(clock);
+
Child = new OsuHitObjectComposer(new OsuRuleset());
}
}
diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs
index 7050e34712..3afb22f0ad 100644
--- a/osu.Game/Database/ArchiveModelManager.cs
+++ b/osu.Game/Database/ArchiveModelManager.cs
@@ -35,11 +35,13 @@ namespace osu.Game.Database
///
/// Fired when a new becomes available in the database.
+ /// This is not guaranteed to run on the update thread.
///
public event Action ItemAdded;
///
/// Fired when a is removed from the database.
+ /// This is not guaranteed to run on the update thread.
///
public event Action ItemRemoved;
diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs
index b9a35ec1f0..8056dbff3c 100644
--- a/osu.Game/Overlays/BeatmapSet/Header.cs
+++ b/osu.Game/Overlays/BeatmapSet/Header.cs
@@ -246,11 +246,11 @@ namespace osu.Game.Overlays.BeatmapSet
if (beatmaps != null) beatmaps.ItemAdded -= handleBeatmapAdd;
}
- private void handleBeatmapAdd(BeatmapSetInfo beatmap)
+ private void handleBeatmapAdd(BeatmapSetInfo beatmap) => Schedule(() =>
{
if (beatmap.OnlineBeatmapSetID == BeatmapSet?.OnlineBeatmapSetID)
downloadButtonsContainer.FadeOut(transition_duration);
- }
+ });
private void download(bool noVideo)
{
diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs
index 8d8a4aebaa..3f1aa04c36 100644
--- a/osu.Game/Overlays/DirectOverlay.cs
+++ b/osu.Game/Overlays/DirectOverlay.cs
@@ -188,12 +188,12 @@ namespace osu.Game.Overlays
beatmaps.ItemAdded += setAdded;
}
- private void setAdded(BeatmapSetInfo set)
+ private void setAdded(BeatmapSetInfo set) => Schedule(() =>
{
// if a new map was imported, we should remove it from search results (download completed etc.)
panels?.FirstOrDefault(p => p.SetInfo.OnlineBeatmapSetID == set.OnlineBeatmapSetID)?.FadeOut(400).Expire();
BeatmapSets = BeatmapSets?.Where(b => b.OnlineBeatmapSetID != set.OnlineBeatmapSetID);
- }
+ });
private void updateResultCounts()
{
@@ -323,6 +323,14 @@ namespace osu.Game.Overlays
private int distinctCount(List list) => list.Distinct().ToArray().Length;
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (beatmaps != null)
+ beatmaps.ItemAdded -= setAdded;
+ }
+
public class ResultCounts
{
public readonly int Artists;
diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs
index ac7ec6257b..c981e5f493 100644
--- a/osu.Game/Overlays/Music/PlaylistOverlay.cs
+++ b/osu.Game/Overlays/Music/PlaylistOverlay.cs
@@ -74,8 +74,8 @@ namespace osu.Game.Overlays.Music
},
};
- beatmaps.ItemAdded += list.AddBeatmapSet;
- beatmaps.ItemRemoved += list.RemoveBeatmapSet;
+ beatmaps.ItemAdded += handleBeatmapAdded;
+ beatmaps.ItemRemoved += handleBeatmapRemoved;
list.BeatmapSets = beatmaps.GetAllUsableBeatmapSets();
@@ -95,6 +95,9 @@ namespace osu.Game.Overlays.Music
beatmapBacking.TriggerChange();
}
+ private void handleBeatmapAdded(BeatmapSetInfo setInfo) => Schedule(() => list.AddBeatmapSet(setInfo));
+ private void handleBeatmapRemoved(BeatmapSetInfo setInfo) => Schedule(() => list.RemoveBeatmapSet(setInfo));
+
protected override void PopIn()
{
filter.Search.HoldFocus = true;
@@ -153,6 +156,17 @@ namespace osu.Game.Overlays.Music
track.Restart();
}
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (beatmaps != null)
+ {
+ beatmaps.ItemAdded -= handleBeatmapAdded;
+ beatmaps.ItemRemoved -= handleBeatmapRemoved;
+ }
+ }
}
//todo: placeholder
diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
index 5df5304751..a2215035dd 100644
--- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs
+++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs
@@ -21,9 +21,13 @@ namespace osu.Game.Overlays.Settings.Sections
public override FontAwesome Icon => FontAwesome.fa_paint_brush;
+ private SkinManager skins;
+
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, SkinManager skins)
{
+ this.skins = skins;
+
FlowContent.Spacing = new Vector2(0, 5);
Children = new Drawable[]
{
@@ -47,15 +51,29 @@ namespace osu.Game.Overlays.Settings.Sections
},
};
- void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.ToString(), s.ID));
- skins.ItemAdded += _ => reloadSkins();
- skins.ItemRemoved += _ => reloadSkins();
+ skins.ItemAdded += onItemsChanged;
+ skins.ItemRemoved += onItemsChanged;
reloadSkins();
skinDropdown.Bindable = config.GetBindable(OsuSetting.Skin);
}
+ private void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.ToString(), s.ID));
+
+ private void onItemsChanged(SkinInfo _) => Schedule(reloadSkins);
+
+ protected override void Dispose(bool isDisposing)
+ {
+ base.Dispose(isDisposing);
+
+ if (skins != null)
+ {
+ skins.ItemAdded -= onItemsChanged;
+ skins.ItemRemoved -= onItemsChanged;
+ }
+ }
+
private class SizeSlider : OsuSliderBar
{
public override string TooltipText => Current.Value.ToString(@"0.##x");
diff --git a/osu.Game/Properties/AssemblyInfo.cs b/osu.Game/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..9384740308
--- /dev/null
+++ b/osu.Game/Properties/AssemblyInfo.cs
@@ -0,0 +1,11 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System.Runtime.CompilerServices;
+
+// We publish our internal attributes to other sub-projects of the framework.
+// Note, that we omit visual tests as they are meant to test the framework
+// behavior "in the wild".
+
+[assembly: InternalsVisibleTo("osu.Game.Tests")]
+[assembly: InternalsVisibleTo("osu.Game.Tests.Dynamic")]
diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
index 9d0246b2fc..1e7416e63d 100644
--- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs
@@ -53,9 +53,6 @@ namespace osu.Game.Rulesets.Edit
return;
}
- HitObjectMaskLayer hitObjectMaskLayer = new HitObjectMaskLayer(this);
- SelectionLayer selectionLayer = new SelectionLayer(rulesetContainer.Playfield);
-
var layerBelowRuleset = new BorderLayer
{
RelativeSizeAxes = Axes.Both,
@@ -63,12 +60,7 @@ namespace osu.Game.Rulesets.Edit
};
var layerAboveRuleset = CreateLayerContainer();
- layerAboveRuleset.Children = new Drawable[]
- {
- selectionLayer, // Below object overlays for input
- hitObjectMaskLayer,
- selectionLayer.CreateProxy() // Proxy above object overlays for selections
- };
+ layerAboveRuleset.Child = new HitObjectMaskLayer(rulesetContainer.Playfield, this);
layerContainers.Add(layerBelowRuleset);
layerContainers.Add(layerAboveRuleset);
@@ -110,11 +102,6 @@ namespace osu.Game.Rulesets.Edit
}
};
- selectionLayer.ObjectSelected += hitObjectMaskLayer.AddOverlay;
- selectionLayer.ObjectDeselected += hitObjectMaskLayer.RemoveOverlay;
- selectionLayer.SelectionCleared += hitObjectMaskLayer.RemoveSelectionOverlay;
- selectionLayer.SelectionFinished += hitObjectMaskLayer.AddSelectionOverlay;
-
toolboxCollection.Items =
CompositionTools.Select(t => new RadioButton(t.Name, () => setCompositionTool(t)))
.Prepend(new RadioButton("Select", () => setCompositionTool(null)))
@@ -149,11 +136,10 @@ namespace osu.Game.Rulesets.Edit
public virtual HitObjectMask CreateMaskFor(DrawableHitObject hitObject) => null;
///
- /// Creates a which outlines s
- /// and handles all hitobject movement/pattern adjustments.
+ /// Creates a which outlines s
+ /// and handles hitobject pattern adjustments.
///
- /// The overlays.
- public virtual SelectionBox CreateSelectionOverlay(IReadOnlyList overlays) => new SelectionBox(overlays);
+ public virtual MaskSelection CreateMaskSelection() => new MaskSelection();
///
/// Creates a which provides a layer above or below the .
diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs
index 051b42fec6..9f055ffc5d 100644
--- a/osu.Game/Rulesets/Edit/HitObjectMask.cs
+++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs
@@ -1,21 +1,146 @@
// Copyright (c) 2007-2018 ppy Pty Ltd .
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+using System;
+using osu.Framework;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Input;
using osu.Game.Rulesets.Objects.Drawables;
+using OpenTK;
namespace osu.Game.Rulesets.Edit
{
///
/// A mask placed above a adding editing functionality.
///
- public class HitObjectMask : Container
+ public class HitObjectMask : CompositeDrawable, IStateful
{
+ ///
+ /// Invoked when this has been selected.
+ ///
+ public event Action Selected;
+
+ ///
+ /// Invoked when this has been deselected.
+ ///
+ public event Action Deselected;
+
+ ///
+ /// Invoked when this has requested selection.
+ /// Will fire even if already selected. Does not actually perform selection.
+ ///
+ public event Action SelectionRequested;
+
+ ///
+ /// Invoked when this has requested drag.
+ ///
+ public event Action DragRequested;
+
+ ///
+ /// The which this applies to.
+ ///
public readonly DrawableHitObject HitObject;
+ protected override bool ShouldBeAlive => HitObject.IsAlive && HitObject.IsPresent || State == SelectionState.Selected;
+ public override bool HandleMouseInput => ShouldBeAlive;
+ public override bool RemoveWhenNotAlive => false;
+
public HitObjectMask(DrawableHitObject hitObject)
{
HitObject = hitObject;
+
+ AlwaysPresent = true;
+ Alpha = 0;
}
+
+ private SelectionState state;
+
+ public event Action StateChanged;
+
+ public SelectionState State
+ {
+ get => state;
+ set
+ {
+ if (state == value) return;
+
+ state = value;
+ switch (state)
+ {
+ case SelectionState.Selected:
+ Show();
+ Selected?.Invoke(this);
+ break;
+ case SelectionState.NotSelected:
+ Hide();
+ Deselected?.Invoke(this);
+ break;
+ }
+ }
+ }
+
+ ///
+ /// Selects this , causing it to become visible.
+ ///
+ public void Select() => State = SelectionState.Selected;
+
+ ///
+ /// Deselects this , causing it to become invisible.
+ ///
+ public void Deselect() => State = SelectionState.NotSelected;
+
+ public bool IsSelected => State == SelectionState.Selected;
+
+ private bool selectionRequested;
+
+ protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+ {
+ selectionRequested = false;
+
+ if (State == SelectionState.NotSelected)
+ {
+ SelectionRequested?.Invoke(this, state);
+ selectionRequested = true;
+ }
+
+ return IsSelected;
+ }
+
+ protected override bool OnClick(InputState state)
+ {
+ if (State == SelectionState.Selected && !selectionRequested)
+ {
+ selectionRequested = true;
+ SelectionRequested?.Invoke(this, state);
+ return true;
+ }
+
+ return base.OnClick(state);
+ }
+
+ protected override bool OnDragStart(InputState state) => true;
+
+ protected override bool OnDrag(InputState state)
+ {
+ DragRequested?.Invoke(this, state);
+ return true;
+ }
+
+ ///
+ /// The screen-space point that causes this to be selected.
+ ///
+ public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre;
+
+ ///
+ /// The screen-space quad that outlines this for selections.
+ ///
+ public virtual Quad SelectionQuad => ScreenSpaceDrawQuad;
+ }
+
+ public enum SelectionState
+ {
+ NotSelected,
+ Selected
}
}
diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
index 348364a2bf..fdfef14a88 100644
--- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
+++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs
@@ -6,14 +6,12 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
-using osu.Framework.Graphics.Primitives;
using osu.Game.Audio;
using osu.Game.Graphics;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
-using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Objects.Drawables
@@ -231,16 +229,6 @@ namespace osu.Game.Rulesets.Objects.Drawables
protected virtual void CheckForJudgements(bool userTriggered, double timeOffset)
{
}
-
- ///
- /// The screen-space point that causes this to be selected in the Editor.
- ///
- public virtual Vector2 SelectionPoint => ScreenSpaceDrawQuad.Centre;
-
- ///
- /// The screen-space quad that outlines this for selections in the Editor.
- ///
- public virtual Quad SelectionQuad => ScreenSpaceDrawQuad;
}
public abstract class DrawableHitObject : DrawableHitObject
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs
new file mode 100644
index 0000000000..51bb61b607
--- /dev/null
+++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/DragLayer.cs
@@ -0,0 +1,92 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Primitives;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
+using osu.Game.Rulesets.Edit;
+using OpenTK.Graphics;
+
+namespace osu.Game.Screens.Edit.Screens.Compose.Layers
+{
+ ///
+ /// A layer that handles and displays drag selection for a collection of s.
+ ///
+ public class DragLayer : CompositeDrawable
+ {
+ private readonly Action performSelection;
+
+ ///
+ /// Invoked when the drag selection has finished.
+ ///
+ public event Action DragEnd;
+
+ private Drawable box;
+
+ ///
+ /// Creates a new .
+ ///
+ /// The selectable s.
+ public DragLayer(Action performSelection)
+ {
+ this.performSelection = performSelection;
+
+ RelativeSizeAxes = Axes.Both;
+ AlwaysPresent = true;
+ Alpha = 0;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ InternalChild = box = new Container
+ {
+ Masking = true,
+ BorderColour = Color4.White,
+ BorderThickness = MaskSelection.BORDER_RADIUS,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ Alpha = 0.1f
+ }
+ };
+ }
+
+ protected override bool OnDragStart(InputState state)
+ {
+ this.FadeIn(250, Easing.OutQuint);
+ return true;
+ }
+
+ protected override bool OnDrag(InputState state)
+ {
+ var dragPosition = state.Mouse.NativeState.Position;
+ var dragStartPosition = state.Mouse.NativeState.PositionMouseDown ?? dragPosition;
+
+ var dragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y);
+
+ // We use AABBFloat instead of RectangleF since it handles negative sizes for us
+ var dragRectangle = dragQuad.AABBFloat;
+
+ var topLeft = ToLocalSpace(dragRectangle.TopLeft);
+ var bottomRight = ToLocalSpace(dragRectangle.BottomRight);
+
+ box.Position = topLeft;
+ box.Size = bottomRight - topLeft;
+
+ performSelection?.Invoke(dragRectangle);
+ return true;
+ }
+
+ protected override bool OnDragEnd(InputState state)
+ {
+ this.FadeOut(250, Easing.OutQuint);
+ DragEnd?.Invoke();
+ return true;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs
index 46b09e2c23..423cf0ed29 100644
--- a/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs
+++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs
@@ -2,65 +2,92 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
+using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
+using osu.Framework.Input;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables;
+using osu.Game.Rulesets.UI;
namespace osu.Game.Screens.Edit.Screens.Compose.Layers
{
public class HitObjectMaskLayer : CompositeDrawable
{
+ private readonly Playfield playfield;
private readonly HitObjectComposer composer;
- private readonly Container overlayContainer;
- public HitObjectMaskLayer(HitObjectComposer composer)
+ private MaskContainer maskContainer;
+
+ public HitObjectMaskLayer(Playfield playfield, HitObjectComposer composer)
{
+ // we need the playfield as HitObjects may not be initialised until its BDL.
+ this.playfield = playfield;
+
this.composer = composer;
+
RelativeSizeAxes = Axes.Both;
+ }
- InternalChild = overlayContainer = new Container { RelativeSizeAxes = Axes.Both };
+ [BackgroundDependencyLoader]
+ private void load()
+ {
+ maskContainer = new MaskContainer();
+
+ var maskSelection = composer.CreateMaskSelection();
+
+ maskContainer.MaskSelected += maskSelection.HandleSelected;
+ maskContainer.MaskDeselected += maskSelection.HandleDeselected;
+ maskContainer.MaskSelectionRequested += maskSelection.HandleSelectionRequested;
+ maskContainer.MaskDragRequested += maskSelection.HandleDrag;
+
+ maskSelection.DeselectAll = maskContainer.DeselectAll;
+
+ var dragLayer = new DragLayer(maskContainer.Select);
+ dragLayer.DragEnd += () => maskSelection.UpdateVisibility();
+
+ InternalChildren = new Drawable[]
+ {
+ dragLayer,
+ maskSelection,
+ maskContainer,
+ dragLayer.CreateProxy()
+ };
+
+ foreach (var obj in playfield.HitObjects.Objects)
+ addMask(obj);
+ }
+
+ protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+ {
+ maskContainer.DeselectAll();
+ return true;
}
///
- /// Adds an overlay for a which adds movement support.
+ /// Adds a mask for a which adds movement support.
///
- /// The to create an overlay for.
- public void AddOverlay(DrawableHitObject hitObject)
+ /// The to create a mask for.
+ private void addMask(DrawableHitObject hitObject)
{
- var overlay = composer.CreateMaskFor(hitObject);
- if (overlay == null)
+ var mask = composer.CreateMaskFor(hitObject);
+ if (mask == null)
return;
- overlayContainer.Add(overlay);
+ maskContainer.Add(mask);
}
///
- /// Removes the overlay for a .
+ /// Removes the mask for a .
///
- /// The to remove the overlay for.
- public void RemoveOverlay(DrawableHitObject hitObject)
+ /// The to remove the mask for.
+ private void removeMask(DrawableHitObject hitObject)
{
- var existing = overlayContainer.FirstOrDefault(h => h.HitObject == hitObject);
- if (existing == null)
+ var mask = maskContainer.FirstOrDefault(h => h.HitObject == hitObject);
+ if (mask == null)
return;
- existing.Hide();
- existing.Expire();
- }
-
- private SelectionBox currentSelectionBox;
-
- public void AddSelectionOverlay()
- {
- if (overlayContainer.Count > 0)
- AddInternal(currentSelectionBox = composer.CreateSelectionOverlay(overlayContainer));
- }
-
- public void RemoveSelectionOverlay()
- {
- currentSelectionBox?.Hide();
- currentSelectionBox?.Expire();
+ maskContainer.Remove(mask);
}
}
}
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs
new file mode 100644
index 0000000000..b631628c9e
--- /dev/null
+++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskContainer.cs
@@ -0,0 +1,123 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Input;
+using osu.Game.Rulesets.Edit;
+using RectangleF = osu.Framework.Graphics.Primitives.RectangleF;
+
+namespace osu.Game.Screens.Edit.Screens.Compose.Layers
+{
+ public class MaskContainer : Container
+ {
+ ///
+ /// Invoked when any is selected.
+ ///
+ public event Action MaskSelected;
+
+ ///
+ /// Invoked when any is deselected.
+ ///
+ public event Action MaskDeselected;
+
+ ///
+ /// Invoked when any requests selection.
+ ///
+ public event Action MaskSelectionRequested;
+
+ ///
+ /// Invoked when any requests drag.
+ ///
+ public event Action MaskDragRequested;
+
+ private IEnumerable aliveMasks => AliveInternalChildren.Cast();
+
+ public MaskContainer()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ public override void Add(HitObjectMask drawable)
+ {
+ base.Add(drawable);
+
+ drawable.Selected += onMaskSelected;
+ drawable.Deselected += onMaskDeselected;
+ drawable.SelectionRequested += onSelectionRequested;
+ drawable.DragRequested += onDragRequested;
+ }
+
+ public override bool Remove(HitObjectMask drawable)
+ {
+ var result = base.Remove(drawable);
+
+ if (result)
+ {
+ drawable.Selected -= onMaskSelected;
+ drawable.Deselected -= onMaskDeselected;
+ drawable.SelectionRequested -= onSelectionRequested;
+ drawable.DragRequested -= onDragRequested;
+ }
+
+ return result;
+ }
+
+ ///
+ /// Select all masks in a given rectangle selection area.
+ ///
+ /// The rectangle to perform a selection on in screen-space coordinates.
+ public void Select(RectangleF rect)
+ {
+ foreach (var mask in aliveMasks.ToList())
+ {
+ if (mask.IsPresent && rect.Contains(mask.SelectionPoint))
+ mask.Select();
+ else
+ mask.Deselect();
+ }
+ }
+
+ ///
+ /// Deselects all selected s.
+ ///
+ public void DeselectAll() => aliveMasks.ToList().ForEach(m => m.Deselect());
+
+ private void onMaskSelected(HitObjectMask mask)
+ {
+ MaskSelected?.Invoke(mask);
+ ChangeChildDepth(mask, 1);
+ }
+
+ private void onMaskDeselected(HitObjectMask mask)
+ {
+ MaskDeselected?.Invoke(mask);
+ ChangeChildDepth(mask, 0);
+ }
+
+ private void onSelectionRequested(HitObjectMask mask, InputState state) => MaskSelectionRequested?.Invoke(mask, state);
+ private void onDragRequested(HitObjectMask mask, InputState state) => MaskDragRequested?.Invoke(mask, state);
+
+ protected override int Compare(Drawable x, Drawable y)
+ {
+ if (!(x is HitObjectMask xMask) || !(y is HitObjectMask yMask))
+ return base.Compare(x, y);
+ return Compare(xMask, yMask);
+ }
+
+ public int Compare(HitObjectMask x, HitObjectMask y)
+ {
+ // dpeth is used to denote selected status (we always want selected masks to handle input first).
+ int d = x.Depth.CompareTo(y.Depth);
+ if (d != 0)
+ return d;
+
+ // Put earlier hitobjects towards the end of the list, so they handle input first
+ int i = y.HitObject.HitObject.StartTime.CompareTo(x.HitObject.HitObject.StartTime);
+ return i == 0 ? CompareReverseChildID(x, y) : i;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs
new file mode 100644
index 0000000000..76b8027b07
--- /dev/null
+++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/MaskSelection.cs
@@ -0,0 +1,164 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using osu.Framework.Allocation;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Input;
+using osu.Game.Graphics;
+using osu.Game.Rulesets.Edit;
+using osu.Game.Rulesets.Edit.Types;
+using OpenTK;
+
+namespace osu.Game.Screens.Edit.Screens.Compose.Layers
+{
+ ///
+ /// A box which surrounds s and provides interactive handles, context menus etc.
+ ///
+ public class MaskSelection : CompositeDrawable
+ {
+ public const float BORDER_RADIUS = 2;
+
+ private readonly List selectedMasks;
+
+ private Drawable outline;
+
+ public MaskSelection()
+ {
+ selectedMasks = new List();
+
+ RelativeSizeAxes = Axes.Both;
+ AlwaysPresent = true;
+ Alpha = 0;
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ InternalChild = outline = new Container
+ {
+ Masking = true,
+ BorderThickness = BORDER_RADIUS,
+ BorderColour = colours.Yellow,
+ Child = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ AlwaysPresent = true,
+ Alpha = 0
+ }
+ };
+ }
+
+ #region User Input Handling
+
+ public void HandleDrag(HitObjectMask m, InputState state)
+ {
+ // Todo: Various forms of snapping
+
+ foreach (var mask in selectedMasks)
+ {
+ switch (mask.HitObject.HitObject)
+ {
+ case IHasEditablePosition editablePosition:
+ editablePosition.OffsetPosition(state.Mouse.Delta);
+ break;
+ }
+ }
+ }
+
+ #endregion
+
+ #region Selection Handling
+
+ ///
+ /// Bind an action to deselect all selected masks.
+ ///
+ public Action DeselectAll { private get; set; }
+
+ ///
+ /// Handle a mask becoming selected.
+ ///
+ /// The mask.
+ public void HandleSelected(HitObjectMask mask) => selectedMasks.Add(mask);
+
+ ///
+ /// Handle a mask becoming deselected.
+ ///
+ /// The mask.
+ public void HandleDeselected(HitObjectMask mask)
+ {
+ selectedMasks.Remove(mask);
+
+ // We don't want to update visibility if > 0, since we may be deselecting masks during drag-selection
+ if (selectedMasks.Count == 0)
+ UpdateVisibility();
+ }
+
+ ///
+ /// Handle a mask requesting selection.
+ ///
+ /// The mask.
+ public void HandleSelectionRequested(HitObjectMask mask, InputState state)
+ {
+ if (state.Keyboard.ControlPressed)
+ {
+ if (mask.IsSelected)
+ mask.Deselect();
+ else
+ mask.Select();
+ }
+ else
+ {
+ if (mask.IsSelected)
+ return;
+
+ DeselectAll?.Invoke();
+ mask.Select();
+ }
+
+ UpdateVisibility();
+ }
+
+ #endregion
+
+ ///
+ /// Updates whether this is visible.
+ ///
+ internal void UpdateVisibility()
+ {
+ if (selectedMasks.Count > 0)
+ Show();
+ else
+ Hide();
+ }
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (selectedMasks.Count == 0)
+ return;
+
+ // Move the rectangle to cover the hitobjects
+ var topLeft = new Vector2(float.MaxValue, float.MaxValue);
+ var bottomRight = new Vector2(float.MinValue, float.MinValue);
+
+ bool hasSelection = false;
+
+ foreach (var mask in selectedMasks)
+ {
+ topLeft = Vector2.ComponentMin(topLeft, ToLocalSpace(mask.SelectionQuad.TopLeft));
+ bottomRight = Vector2.ComponentMax(bottomRight, ToLocalSpace(mask.SelectionQuad.BottomRight));
+ }
+
+ topLeft -= new Vector2(5);
+ bottomRight += new Vector2(5);
+
+ outline.Size = bottomRight - topLeft;
+ outline.Position = topLeft;
+ }
+ }
+}
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs
deleted file mode 100644
index 0e5d824559..0000000000
--- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs
+++ /dev/null
@@ -1,102 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System.Collections.Generic;
-using System.Linq;
-using osu.Framework.Allocation;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input;
-using osu.Game.Graphics;
-using osu.Game.Rulesets.Edit;
-using osu.Game.Rulesets.Edit.Types;
-using osu.Game.Rulesets.Objects.Drawables;
-using OpenTK;
-
-namespace osu.Game.Screens.Edit.Screens.Compose.Layers
-{
- ///
- /// A box which surrounds s and provides interactive handles, context menus etc.
- ///
- public class SelectionBox : VisibilityContainer
- {
- private readonly IReadOnlyList overlays;
-
- public const float BORDER_RADIUS = 2;
-
- public SelectionBox(IReadOnlyList overlays)
- {
- this.overlays = overlays;
-
- Masking = true;
- BorderThickness = BORDER_RADIUS;
-
- InternalChild = new Box
- {
- RelativeSizeAxes = Axes.Both,
- AlwaysPresent = true,
- Alpha = 0
- };
-
- State = Visibility.Visible;
- }
-
- [BackgroundDependencyLoader]
- private void load(OsuColour colours)
- {
- BorderColour = colours.Yellow;
- }
-
- protected override void Update()
- {
- base.Update();
-
- // Todo: We might need to optimise this
-
- // Move the rectangle to cover the hitobjects
- var topLeft = new Vector2(float.MaxValue, float.MaxValue);
- var bottomRight = new Vector2(float.MinValue, float.MinValue);
-
- foreach (var obj in overlays)
- {
- topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(obj.HitObject.SelectionQuad.TopLeft));
- bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(obj.HitObject.SelectionQuad.BottomRight));
- }
-
- topLeft -= new Vector2(5);
- bottomRight += new Vector2(5);
-
- Size = bottomRight - topLeft;
- Position = topLeft;
- }
-
- public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => overlays.Any(o => o.ReceiveMouseInputAt(screenSpacePos));
-
- protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
-
- protected override bool OnDragStart(InputState state) => true;
-
- protected override bool OnDrag(InputState state)
- {
- // Todo: Various forms of snapping
- foreach (var hitObject in overlays.Select(o => o.HitObject.HitObject))
- {
- switch (hitObject)
- {
- case IHasEditablePosition editablePosition:
- editablePosition.OffsetPosition(state.Mouse.Delta);
- break;
- }
- }
- return true;
- }
-
- protected override bool OnDragEnd(InputState state) => true;
-
- public override bool DisposeOnDeathRemoval => true;
-
- protected override void PopIn() => this.FadeIn();
- protected override void PopOut() => this.FadeOut();
- }
-}
diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs
deleted file mode 100644
index ab51385980..0000000000
--- a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs
+++ /dev/null
@@ -1,240 +0,0 @@
-// Copyright (c) 2007-2018 ppy Pty Ltd .
-// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
-
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using osu.Framework.Extensions.IEnumerableExtensions;
-using osu.Framework.Graphics;
-using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Primitives;
-using osu.Framework.Graphics.Shapes;
-using osu.Framework.Input;
-using osu.Game.Rulesets.Objects.Drawables;
-using osu.Game.Rulesets.UI;
-using OpenTK;
-using OpenTK.Graphics;
-using RectangleF = osu.Framework.Graphics.Primitives.RectangleF;
-
-namespace osu.Game.Screens.Edit.Screens.Compose.Layers
-{
- public class SelectionLayer : CompositeDrawable
- {
- ///
- /// Invoked when a is selected.
- ///
- public event Action ObjectSelected;
-
- ///
- /// Invoked when a is deselected.
- ///
- public event Action ObjectDeselected;
-
- ///
- /// Invoked when the selection has been cleared.
- ///
- public event Action SelectionCleared;
-
- ///
- /// Invoked when the user has finished selecting all s.
- ///
- public event Action SelectionFinished;
-
- private readonly Playfield playfield;
-
- public SelectionLayer(Playfield playfield)
- {
- this.playfield = playfield;
-
- RelativeSizeAxes = Axes.Both;
- }
-
- private DragBox dragBox;
-
- private readonly HashSet selectedHitObjects = new HashSet();
-
- protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
- {
- DeselectAll();
- return true;
- }
-
- protected override bool OnDragStart(InputState state)
- {
- AddInternal(dragBox = new DragBox());
- return true;
- }
-
- protected override bool OnDrag(InputState state)
- {
- dragBox.Show();
-
- var dragPosition = state.Mouse.NativeState.Position;
- var dragStartPosition = state.Mouse.NativeState.PositionMouseDown ?? dragPosition;
-
- var screenSpaceDragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y);
-
- dragBox.SetDragRectangle(screenSpaceDragQuad.AABBFloat);
- selectQuad(screenSpaceDragQuad);
-
- return true;
- }
-
- protected override bool OnDragEnd(InputState state)
- {
- dragBox.Hide();
- dragBox.Expire();
-
- finishSelection();
-
- return true;
- }
-
- protected override bool OnClick(InputState state)
- {
- selectPoint(state.Mouse.NativeState.Position);
- finishSelection();
-
- return true;
- }
-
- ///
- /// Selects a .
- ///
- /// The to select.
- public void Select(DrawableHitObject hitObject)
- {
- if (!select(hitObject))
- return;
-
- clearSelection();
- finishSelection();
- }
-
- ///
- /// Selects a without performing capture updates.
- ///
- /// The to select.
- /// Whether was selected.
- private bool select(DrawableHitObject hitObject)
- {
- if (!selectedHitObjects.Add(hitObject))
- return false;
-
- ObjectSelected?.Invoke(hitObject);
- return true;
- }
-
- ///
- /// Deselects a .
- ///
- /// The to deselect.
- public void Deselect(DrawableHitObject hitObject)
- {
- if (!deselect(hitObject))
- return;
-
- clearSelection();
- finishSelection();
- }
-
- ///
- /// Deselects a without performing capture updates.
- ///
- /// The to deselect.
- /// Whether the was deselected.
- private bool deselect(DrawableHitObject hitObject)
- {
- if (!selectedHitObjects.Remove(hitObject))
- return false;
-
- ObjectDeselected?.Invoke(hitObject);
- return true;
- }
-
- ///
- /// Deselects all selected s.
- ///
- public void DeselectAll()
- {
- selectedHitObjects.ForEach(h => ObjectDeselected?.Invoke(h));
- selectedHitObjects.Clear();
-
- clearSelection();
- }
-
- ///
- /// Selects all hitobjects that are present within the area of a .
- ///
- /// The selection .
- // Todo: If needed we can severely reduce allocations in this method
- private void selectQuad(Quad screenSpaceQuad)
- {
- var expectedSelection = playfield.HitObjects.Objects.Where(h => h.IsAlive && h.IsPresent && screenSpaceQuad.Contains(h.SelectionPoint)).ToList();
-
- var toRemove = selectedHitObjects.Except(expectedSelection).ToList();
- foreach (var obj in toRemove)
- deselect(obj);
-
- expectedSelection.ForEach(h => select(h));
- }
-
- ///
- /// Selects the top-most hitobject that is present under a specific point.
- ///
- /// The to select at.
- private void selectPoint(Vector2 screenSpacePoint)
- {
- var target = playfield.HitObjects.Objects.Reverse().Where(h => h.IsAlive && h.IsPresent).FirstOrDefault(h => h.ReceiveMouseInputAt(screenSpacePoint));
- if (target == null)
- return;
-
- select(target);
- }
-
- private void clearSelection() => SelectionCleared?.Invoke();
-
- private void finishSelection()
- {
- if (selectedHitObjects.Count == 0)
- return;
- SelectionFinished?.Invoke();
- }
-
- ///
- /// A box that represents a drag selection.
- ///
- private class DragBox : VisibilityContainer
- {
- ///
- /// Creates a new .
- ///
- public DragBox()
- {
- Masking = true;
- BorderColour = Color4.White;
- BorderThickness = SelectionBox.BORDER_RADIUS;
-
- Child = new Box
- {
- RelativeSizeAxes = Axes.Both,
- Alpha = 0.1f
- };
- }
-
- public void SetDragRectangle(RectangleF rectangle)
- {
- var topLeft = Parent.ToLocalSpace(rectangle.TopLeft);
- var bottomRight = Parent.ToLocalSpace(rectangle.BottomRight);
-
- Position = topLeft;
- Size = bottomRight - topLeft;
- }
-
- public override bool DisposeOnDeathRemoval => true;
-
- protected override void PopIn() => this.FadeIn(250, Easing.OutQuint);
- protected override void PopOut() => this.FadeOut(250, Easing.OutQuint);
- }
- }
-}
diff --git a/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs
new file mode 100644
index 0000000000..c2595231c9
--- /dev/null
+++ b/osu.Game/Tests/Visual/ManualInputManagerTestCase.cs
@@ -0,0 +1,29 @@
+// Copyright (c) 2007-2018 ppy Pty Ltd .
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Testing.Input;
+
+namespace osu.Game.Tests.Visual
+{
+ public abstract class ManualInputManagerTestCase : OsuTestCase
+ {
+ protected override Container Content => InputManager;
+ protected readonly ManualInputManager InputManager;
+
+ protected ManualInputManagerTestCase()
+ {
+ base.Content.Add(InputManager = new ManualInputManager());
+ ReturnUserInput();
+ }
+
+ ///
+ /// Returns input back to the user.
+ ///
+ protected void ReturnUserInput()
+ {
+ AddStep("Return user input", () => InputManager.UseParentState = true);
+ }
+ }
+}