diff --git a/osu-framework b/osu-framework index d29c8365ba..cd6f6e93c9 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit d29c8365ba3cf7924b57cf22341f4af55658764c +Subproject commit cd6f6e93c958e3e4e98db08dd7a443cabcf4742f diff --git a/osu-resources b/osu-resources index 92ec3d10b1..7bb0782200 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit 92ec3d10b12c5e9bfc1d3b05d3db174a506efd6d +Subproject commit 7bb0782200abadf73b79ed1a3bc1d5b926c6a81e diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/OsuHitObjectOverlayLayer.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/OsuHitObjectOverlayLayer.cs deleted file mode 100644 index e0d1b34ca5..0000000000 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/OsuHitObjectOverlayLayer.cs +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Rulesets.Edit.Layers.Selection; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays; -using osu.Game.Rulesets.Osu.Objects.Drawables; - -namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection -{ - public class OsuHitObjectOverlayLayer : HitObjectOverlayLayer - { - protected override HitObjectOverlay CreateOverlayFor(DrawableHitObject hitObject) - { - switch (hitObject) - { - case DrawableHitCircle circle: - return new HitCircleOverlay(circle); - case DrawableSlider slider: - return new SliderOverlay(slider); - } - - return base.CreateOverlayFor(hitObject); - } - } -} diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs similarity index 75% rename from osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleOverlay.cs rename to osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs index 4e64783840..b48dd73bb5 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleOverlay.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleMask.cs @@ -4,15 +4,15 @@ using osu.Framework.Graphics; using osu.Framework.Allocation; using osu.Game.Graphics; -using osu.Game.Rulesets.Edit.Layers.Selection; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays { - public class HitCircleOverlay : HitObjectOverlay + public class HitCircleMask : HitObjectMask { - public HitCircleOverlay(DrawableHitCircle hitCircle) + public HitCircleMask(DrawableHitCircle hitCircle) : base(hitCircle) { Origin = Anchor.Centre; @@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays Scale = hitCircle.Scale; AddInternal(new RingPiece()); + + hitCircle.HitObject.PositionChanged += _ => Position = hitCircle.Position; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs similarity index 74% rename from osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleOverlay.cs rename to osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs index 50a325d6cc..586b516a11 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleOverlay.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleMask.cs @@ -4,7 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; -using osu.Game.Rulesets.Edit.Layers.Selection; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; @@ -12,21 +12,21 @@ using OpenTK; namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays { - public class SliderCircleOverlay : HitObjectOverlay + public class SliderCircleMask : HitObjectMask { - public SliderCircleOverlay(DrawableHitCircle sliderHead, DrawableSlider slider) + public SliderCircleMask(DrawableHitCircle sliderHead, DrawableSlider slider) : this(sliderHead, Vector2.Zero, slider) { } - public SliderCircleOverlay(DrawableSliderTail sliderTail, DrawableSlider slider) + public SliderCircleMask(DrawableSliderTail sliderTail, DrawableSlider slider) : this(sliderTail, ((Slider)slider.HitObject).Curve.PositionAt(1), slider) { } private readonly DrawableOsuHitObject hitObject; - private SliderCircleOverlay(DrawableOsuHitObject hitObject, Vector2 position, DrawableSlider slider) + private SliderCircleMask(DrawableOsuHitObject hitObject, Vector2 position, DrawableSlider slider) : base(hitObject) { this.hitObject = hitObject; diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs similarity index 64% rename from osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderOverlay.cs rename to osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs index a035a683e9..53f02617cd 100644 --- a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderOverlay.cs +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderMask.cs @@ -4,36 +4,41 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics; -using osu.Game.Rulesets.Edit.Layers.Selection; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using OpenTK; using OpenTK.Graphics; namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays { - public class SliderOverlay : HitObjectOverlay + public class SliderMask : HitObjectMask { private readonly SliderBody body; private readonly DrawableSlider slider; - public SliderOverlay(DrawableSlider slider) + public SliderMask(DrawableSlider slider) : base(slider) { this.slider = slider; - var obj = (Slider)slider.HitObject; + Position = slider.Position; + + var sliderObject = (Slider)slider.HitObject; InternalChildren = new Drawable[] { - body = new SliderBody(obj) + body = new SliderBody(sliderObject) { AccentColour = Color4.Transparent, - PathWidth = obj.Scale * 64 + PathWidth = sliderObject.Scale * 64 }, - new SliderCircleOverlay(slider.HeadCircle, slider), - new SliderCircleOverlay(slider.TailCircle, slider), + new SliderCircleMask(slider.HeadCircle, slider), + new SliderCircleMask(slider.TailCircle, slider), }; + + sliderObject.PositionChanged += _ => Position = slider.Position; } [BackgroundDependencyLoader] @@ -46,12 +51,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays { base.Update(); - Position = slider.Position; Size = slider.Size; OriginPosition = slider.OriginPosition; // Need to cause one update body.UpdateProgress(0); } + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => body.ReceiveMouseInputAt(screenSpacePos); } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 70d49a6b4f..026c85d909 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -5,10 +5,11 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Edit.Layers.Selection; using osu.Game.Rulesets.Edit.Tools; -using osu.Game.Rulesets.Osu.Edit.Layers.Selection; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; @@ -32,6 +33,17 @@ namespace osu.Game.Rulesets.Osu.Edit protected override ScalableContainer CreateLayerContainer() => new ScalableContainer(OsuPlayfield.BASE_SIZE.X) { RelativeSizeAxes = Axes.Both }; - protected override HitObjectOverlayLayer CreateHitObjectOverlayLayer() => new OsuHitObjectOverlayLayer(); + public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject) + { + switch (hitObject) + { + case DrawableHitCircle circle: + return new HitCircleMask(circle); + case DrawableSlider slider: + return new SliderMask(slider); + } + + return base.CreateMaskFor(hitObject); + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs index 959c87bbba..d70b26e181 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableHitCircle.cs @@ -66,6 +66,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables //may not be so correct Size = circle.DrawSize; + + HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; } protected override void CheckForJudgements(bool userTriggered, double timeOffset) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 5b9ed4d259..fb3294d319 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -83,6 +83,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables components.Add(drawableRepeatPoint); AddNested(drawableRepeatPoint); } + + HitObject.PositionChanged += _ => Position = HitObject.StackedPosition; } [BackgroundDependencyLoader] diff --git a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs index 9b9d88f0f6..d9aed23414 100644 --- a/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs +++ b/osu.Game.Rulesets.Osu/Objects/OsuHitObject.cs @@ -1,23 +1,41 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using osu.Game.Beatmaps; using osu.Game.Rulesets.Objects; using OpenTK; using osu.Game.Rulesets.Objects.Types; using OpenTK.Graphics; using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Edit.Types; namespace osu.Game.Rulesets.Osu.Objects { - public abstract class OsuHitObject : HitObject, IHasCombo, IHasPosition + public abstract class OsuHitObject : HitObject, IHasCombo, IHasEditablePosition { public const double OBJECT_RADIUS = 64; + public event Action PositionChanged; + public double TimePreempt = 600; public double TimeFadein = 400; - public Vector2 Position { get; set; } + private Vector2 position; + + public Vector2 Position + { + get => position; + set + { + if (position == value) + return; + position = value; + + PositionChanged?.Invoke(value); + } + } + public float X => Position.X; public float Y => Position.Y; @@ -48,5 +66,7 @@ namespace osu.Game.Rulesets.Osu.Objects Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; } + + public virtual void OffsetPosition(Vector2 offset) => Position += offset; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 76439ca530..a633e3957e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Objects private void createSliderEnds() { - HeadCircle = new HitCircle + HeadCircle = new SliderCircle(this) { StartTime = StartTime, Position = Position, @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Objects SampleControlPoint = SampleControlPoint }; - TailCircle = new HitCircle + TailCircle = new SliderCircle(this) { StartTime = EndTime, Position = EndPosition, diff --git a/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs b/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs new file mode 100644 index 0000000000..1e83d02735 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/SliderCircle.cs @@ -0,0 +1,19 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Objects +{ + public class SliderCircle : HitCircle + { + private readonly Slider slider; + + public SliderCircle(Slider slider) + { + this.slider = slider; + } + + public override void OffsetPosition(Vector2 offset) => slider.OffsetPosition(offset); + } +} diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseGameplayCursor.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseGameplayCursor.cs new file mode 100644 index 0000000000..273422f2e9 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseGameplayCursor.cs @@ -0,0 +1,33 @@ +// 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 NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; +using osu.Game.Graphics.Cursor; +using osu.Game.Rulesets.Osu.UI.Cursor; +using osu.Game.Tests.Visual; + +namespace osu.Game.Rulesets.Osu.Tests +{ + [TestFixture] + public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor + { + private GameplayCursor cursor; + + public override IReadOnlyList RequiredTypes => new [] { typeof(CursorTrail) }; + + public CursorContainer Cursor => cursor; + + public bool ProvidingUserCursor => true; + + [BackgroundDependencyLoader] + private void load() + { + Add(cursor = new GameplayCursor { RelativeSizeAxes = Axes.Both }); + } + } +} diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs index 37ca0c021b..dedfa28b7b 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/CursorTrail.cs @@ -3,9 +3,9 @@ using System; using System.Diagnostics; +using System.Runtime.InteropServices; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.OpenGL.Buffers; using osu.Framework.Graphics.OpenGL.Vertices; using osu.Framework.Graphics.Primitives; @@ -14,11 +14,12 @@ using osu.Framework.Graphics.Textures; using osu.Framework.Input; using osu.Framework.Timing; using OpenTK; +using OpenTK.Graphics; using OpenTK.Graphics.ES30; namespace osu.Game.Rulesets.Osu.UI.Cursor { - internal class CursorTrail : Drawable + internal class CursorTrail : Drawable, IRequireHighFrequencyMousePosition { private int currentIndex; @@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private float time; + public override bool IsPresent => true; + private readonly TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData(); private const int max_sprites = 2048; @@ -96,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor const int fade_clock_reset_threshold = 1000000; - time = (float)(Time.Current - timeOffset) / 500f; + time = (float)(Time.Current - timeOffset) / 300f; if (time > fade_clock_reset_threshold) resetTime(); } @@ -115,14 +118,16 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor protected override bool OnMouseMove(InputState state) { + Vector2 pos = state.Mouse.NativeState.Position; + if (lastPosition == null) { - lastPosition = state.Mouse.NativeState.Position; + lastPosition = pos; resampler.AddPosition(lastPosition.Value); return base.OnMouseMove(state); } - foreach (Vector2 pos2 in resampler.AddPosition(state.Mouse.NativeState.Position)) + foreach (Vector2 pos2 in resampler.AddPosition(pos)) { Trace.Assert(lastPosition.HasValue); @@ -162,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor private class TrailDrawNodeSharedData { - public VertexBuffer VertexBuffer; + public VertexBuffer VertexBuffer; } private class TrailDrawNode : DrawNode @@ -188,7 +193,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor public override void Draw(Action vertexAction) { if (Shared.VertexBuffer == null) - Shared.VertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); + Shared.VertexBuffer = new QuadVertexBuffer(max_sprites, BufferUsageHint.DynamicDraw); Shader.GetUniform("g_FadeClock").Value = Time; @@ -205,17 +210,19 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor int end = start; Vector2 pos = Parts[i].Position; - ColourInfo colour = DrawInfo.Colour; - colour.TopLeft.Linear.A = Parts[i].Time + colour.TopLeft.Linear.A; - colour.TopRight.Linear.A = Parts[i].Time + colour.TopRight.Linear.A; - colour.BottomLeft.Linear.A = Parts[i].Time + colour.BottomLeft.Linear.A; - colour.BottomRight.Linear.A = Parts[i].Time + colour.BottomRight.Linear.A; + float time = Parts[i].Time; Texture.DrawQuad( new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y), - colour, + DrawInfo.Colour, null, - v => Shared.VertexBuffer.Vertices[end++] = v); + v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex + { + Position = v.Position, + TexturePosition = v.TexturePosition, + Time = time + 1, + Colour = v.Colour, + }); Parts[i].WasUpdated = false; } @@ -240,5 +247,26 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor Shader.Unbind(); } } + + [StructLayout(LayoutKind.Sequential)] + public struct TexturedTrailVertex : IEquatable, IVertex + { + [VertexMember(2, VertexAttribPointerType.Float)] + public Vector2 Position; + [VertexMember(4, VertexAttribPointerType.Float)] + public Color4 Colour; + [VertexMember(2, VertexAttribPointerType.Float)] + public Vector2 TexturePosition; + [VertexMember(1, VertexAttribPointerType.Float)] + public float Time; + + public bool Equals(TexturedTrailVertex other) + { + return Position.Equals(other.Position) + && TexturePosition.Equals(other.TexturePosition) + && Colour.Equals(other.Colour) + && Time.Equals(other.Time); + } + } } } diff --git a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs index 0aeb14514d..34940a084a 100644 --- a/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs +++ b/osu.Game.Rulesets.Osu/UI/Cursor/GameplayCursor.cs @@ -20,13 +20,66 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor { protected override Drawable CreateCursor() => new OsuCursor(); + protected override Container Content => fadeContainer; + + private readonly Container fadeContainer; + public GameplayCursor() { - Add(new CursorTrail { Depth = 1 }); + InternalChild = fadeContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new CursorTrail { Depth = 1 } + } + }; } private int downCount; + public bool OnPressed(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + downCount++; + ActiveCursor.ScaleTo(1).ScaleTo(1.2f, 100, Easing.OutQuad); + break; + } + + return false; + } + + public bool OnReleased(OsuAction action) + { + switch (action) + { + case OsuAction.LeftButton: + case OsuAction.RightButton: + if (--downCount == 0) + ActiveCursor.ScaleTo(1, 200, Easing.OutQuad); + break; + } + + return false; + } + + public override bool HandleMouseInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input. + + protected override void PopIn() + { + fadeContainer.FadeTo(1, 300, Easing.OutQuint); + ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); + } + + protected override void PopOut() + { + fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint); + ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint); + } + public class OsuCursor : Container { private Container cursorContainer; @@ -131,45 +184,5 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor cursorContainer.Scale = new Vector2(scale); } } - - public bool OnPressed(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - downCount++; - ActiveCursor.ScaleTo(1).ScaleTo(1.2f, 100, Easing.OutQuad); - break; - } - - return false; - } - - public bool OnReleased(OsuAction action) - { - switch (action) - { - case OsuAction.LeftButton: - case OsuAction.RightButton: - if (--downCount == 0) - ActiveCursor.ScaleTo(1, 200, Easing.OutQuad); - break; - } - - return false; - } - - protected override void PopIn() - { - ActiveCursor.FadeTo(1, 250, Easing.OutQuint); - ActiveCursor.ScaleTo(1, 400, Easing.OutQuint); - } - - protected override void PopOut() - { - ActiveCursor.FadeTo(0, 250, Easing.OutQuint); - ActiveCursor.ScaleTo(0.6f, 250, Easing.In); - } } } diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index b4c5654de4..8b7383b6b7 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -64,10 +64,9 @@ - - - - + + + @@ -118,6 +117,7 @@ + @@ -131,6 +131,7 @@ + diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index 8d12dfc517..a7e104dd81 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -8,13 +8,12 @@ using osu.Framework.Allocation; using OpenTK; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Edit.Layers.Selection; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Edit; -using osu.Game.Rulesets.Osu.Edit.Layers.Selection; 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; namespace osu.Game.Tests.Visual @@ -24,17 +23,15 @@ namespace osu.Game.Tests.Visual { public override IReadOnlyList RequiredTypes => new[] { - typeof(SelectionBox), typeof(SelectionLayer), - typeof(CaptureBox), + typeof(SelectionBox), typeof(HitObjectComposer), typeof(OsuHitObjectComposer), - typeof(HitObjectOverlayLayer), - typeof(OsuHitObjectOverlayLayer), - typeof(HitObjectOverlay), - typeof(HitCircleOverlay), - typeof(SliderOverlay), - typeof(SliderCircleOverlay) + typeof(HitObjectMaskLayer), + typeof(HitObjectMask), + typeof(HitCircleMask), + typeof(SliderMask), + typeof(SliderCircleMask) }; [BackgroundDependencyLoader] diff --git a/osu.Game.Tests/Visual/TestCaseSkipButton.cs b/osu.Game.Tests/Visual/TestCaseSkipButton.cs index a4d2019cd7..df94d5147f 100644 --- a/osu.Game.Tests/Visual/TestCaseSkipButton.cs +++ b/osu.Game.Tests/Visual/TestCaseSkipButton.cs @@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual { base.LoadComplete(); - Add(new SkipButton(Clock.CurrentTime + 5000)); + Add(new SkipOverlay(Clock.CurrentTime + 5000)); } } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index e6a51cc39b..3dd8d503ed 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -10,10 +10,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Rulesets.Edit.Layers; -using osu.Game.Rulesets.Edit.Layers.Selection; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; +using osu.Game.Screens.Edit.Screens.Compose.Layers; using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; namespace osu.Game.Rulesets.Edit @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Edit return; } - HitObjectOverlayLayer hitObjectOverlayLayer = CreateHitObjectOverlayLayer(); + HitObjectMaskLayer hitObjectMaskLayer = new HitObjectMaskLayer(this); SelectionLayer selectionLayer = new SelectionLayer(rulesetContainer.Playfield); var layerBelowRuleset = new BorderLayer @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Edit layerAboveRuleset.Children = new Drawable[] { selectionLayer, // Below object overlays for input - hitObjectOverlayLayer, + hitObjectMaskLayer, selectionLayer.CreateProxy() // Proxy above object overlays for selections }; @@ -106,8 +106,10 @@ namespace osu.Game.Rulesets.Edit } }; - selectionLayer.ObjectSelected += hitObjectOverlayLayer.AddOverlay; - selectionLayer.ObjectDeselected += hitObjectOverlayLayer.RemoveOverlay; + selectionLayer.ObjectSelected += hitObjectMaskLayer.AddOverlay; + selectionLayer.ObjectDeselected += hitObjectMaskLayer.RemoveOverlay; + selectionLayer.SelectionCleared += hitObjectMaskLayer.RemoveSelectionOverlay; + selectionLayer.SelectionFinished += hitObjectMaskLayer.AddSelectionOverlay; toolboxCollection.Items = new[] { new RadioButton("Select", () => setCompositionTool(null)) } @@ -138,14 +140,22 @@ namespace osu.Game.Rulesets.Edit protected abstract IReadOnlyList CompositionTools { get; } + /// + /// Creates a for a specific . + /// + /// The to create the overlay for. + public virtual HitObjectMask CreateMaskFor(DrawableHitObject hitObject) => null; + + /// + /// Creates a which outlines s + /// and handles all hitobject movement/pattern adjustments. + /// + /// The overlays. + public virtual SelectionBox CreateSelectionOverlay(IReadOnlyList overlays) => new SelectionBox(overlays); + /// /// Creates a which provides a layer above or below the . /// protected virtual ScalableContainer CreateLayerContainer() => new ScalableContainer { RelativeSizeAxes = Axes.Both }; - - /// - /// Creates the which overlays selected s. - /// - protected virtual HitObjectOverlayLayer CreateHitObjectOverlayLayer() => new HitObjectOverlayLayer(); } } diff --git a/osu.Game/Rulesets/Edit/HitObjectMask.cs b/osu.Game/Rulesets/Edit/HitObjectMask.cs new file mode 100644 index 0000000000..051b42fec6 --- /dev/null +++ b/osu.Game/Rulesets/Edit/HitObjectMask.cs @@ -0,0 +1,21 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Edit +{ + /// + /// A mask placed above a adding editing functionality. + /// + public class HitObjectMask : Container + { + public readonly DrawableHitObject HitObject; + + public HitObjectMask(DrawableHitObject hitObject) + { + HitObject = hitObject; + } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs deleted file mode 100644 index 269dd79bf7..0000000000 --- a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs +++ /dev/null @@ -1,68 +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 osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; - -namespace osu.Game.Rulesets.Edit.Layers.Selection -{ - /// - /// A box which encloses s. - /// - public class CaptureBox : VisibilityContainer - { - private readonly IDrawable captureArea; - private readonly IReadOnlyList capturedObjects; - - public CaptureBox(IDrawable captureArea, IReadOnlyList capturedObjects) - { - this.captureArea = captureArea; - this.capturedObjects = capturedObjects; - - Masking = true; - BorderThickness = SelectionBox.BORDER_RADIUS; - - InternalChild = new Box - { - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0 - }; - - State = Visibility.Visible; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - BorderColour = colours.Yellow; - - // 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 capturedObjects) - { - topLeft = Vector2.ComponentMin(topLeft, captureArea.ToLocalSpace(obj.SelectionQuad.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, captureArea.ToLocalSpace(obj.SelectionQuad.BottomRight)); - } - - topLeft -= new Vector2(5); - bottomRight += new Vector2(5); - - Size = bottomRight - topLeft; - Position = topLeft; - } - - public override bool DisposeOnDeathRemoval => true; - - protected override void PopIn() => this.FadeIn(); - protected override void PopOut() => this.FadeOut(); - } -} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlay.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlay.cs deleted file mode 100644 index 543dd2cc54..0000000000 --- a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlay.cs +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.Edit.Layers.Selection -{ - public class HitObjectOverlay : OverlayContainer - { - // ReSharper disable once NotAccessedField.Local - // This will be used later to handle drag movement, etc - private readonly DrawableHitObject hitObject; - - public HitObjectOverlay(DrawableHitObject hitObject) - { - this.hitObject = hitObject; - - State = Visibility.Visible; - } - - protected override void PopIn() => Alpha = 1; - protected override void PopOut() => Alpha = 0; - } -} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs deleted file mode 100644 index 1c25846ee3..0000000000 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs +++ /dev/null @@ -1,49 +0,0 @@ -// 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.Graphics.Primitives; -using osu.Framework.Graphics.Shapes; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Edit.Layers.Selection -{ - /// - /// A box that represents a drag selection. - /// - public class SelectionBox : VisibilityContainer - { - public const float BORDER_RADIUS = 2; - - /// - /// Creates a new . - /// - public SelectionBox() - { - Masking = true; - BorderColour = Color4.White; - BorderThickness = 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/Rulesets/Edit/Types/IHasEditablePosition.cs b/osu.Game/Rulesets/Edit/Types/IHasEditablePosition.cs new file mode 100644 index 0000000000..fa101ed835 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Types/IHasEditablePosition.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Objects.Types; +using OpenTK; + +namespace osu.Game.Rulesets.Edit.Types +{ + public interface IHasEditablePosition : IHasPosition + { + void OffsetPosition(Vector2 offset); + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/BorderLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/BorderLayer.cs similarity index 92% rename from osu.Game/Rulesets/Edit/Layers/BorderLayer.cs rename to osu.Game/Screens/Edit/Screens/Compose/Layers/BorderLayer.cs index 54c30b8d89..49cf078d36 100644 --- a/osu.Game/Rulesets/Edit/Layers/BorderLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/BorderLayer.cs @@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using OpenTK.Graphics; -namespace osu.Game.Rulesets.Edit.Layers +namespace osu.Game.Screens.Edit.Screens.Compose.Layers { public class BorderLayer : Container { diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlayLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs similarity index 50% rename from osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlayLayer.cs rename to osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs index 0b6e63d1fe..63b5538ad7 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlayLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/HitObjectMaskLayer.cs @@ -1,20 +1,25 @@ // 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.Graphics; using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects.Drawables; -namespace osu.Game.Rulesets.Edit.Layers.Selection +namespace osu.Game.Screens.Edit.Screens.Compose.Layers { - public class HitObjectOverlayLayer : CompositeDrawable + public class HitObjectMaskLayer : CompositeDrawable { - private readonly Dictionary existingOverlays = new Dictionary(); + private readonly HitObjectComposer composer; + private readonly Container overlayContainer; - public HitObjectOverlayLayer() + public HitObjectMaskLayer(HitObjectComposer composer) { + this.composer = composer; RelativeSizeAxes = Axes.Both; + + InternalChild = overlayContainer = new Container { RelativeSizeAxes = Axes.Both }; } /// @@ -23,12 +28,11 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// The to create an overlay for. public void AddOverlay(DrawableHitObject hitObject) { - var overlay = CreateOverlayFor(hitObject); + var overlay = composer.CreateMaskFor(hitObject); if (overlay == null) return; - existingOverlays[hitObject] = overlay; - AddInternal(overlay); + overlayContainer.Add(overlay); } /// @@ -37,17 +41,22 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// The to remove the overlay for. public void RemoveOverlay(DrawableHitObject hitObject) { - if (!existingOverlays.TryGetValue(hitObject, out var existing)) + var existing = overlayContainer.FirstOrDefault(h => h.HitObject == hitObject); + if (existing == null) return; existing.Hide(); existing.Expire(); } - /// - /// Creates a for a specific . - /// - /// The to create the overlay for. - protected virtual HitObjectOverlay CreateOverlayFor(DrawableHitObject hitObject) => null; + private SelectionBox currentSelectionBox; + + public void AddSelectionOverlay() => AddInternal(currentSelectionBox = composer.CreateSelectionOverlay(overlayContainer)); + + public void RemoveSelectionOverlay() + { + currentSelectionBox?.Hide(); + currentSelectionBox?.Expire(); + } } } diff --git a/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs new file mode 100644 index 0000000000..0e5d824559 --- /dev/null +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionBox.cs @@ -0,0 +1,102 @@ +// 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/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs similarity index 71% rename from osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs rename to osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs index 3895d34d7f..8c66007bb7 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Screens/Edit/Screens/Compose/Layers/SelectionLayer.cs @@ -8,12 +8,14 @@ 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; -namespace osu.Game.Rulesets.Edit.Layers.Selection +namespace osu.Game.Screens.Edit.Screens.Compose.Layers { public class SelectionLayer : CompositeDrawable { @@ -27,6 +29,16 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// 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) @@ -36,8 +48,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection RelativeSizeAxes = Axes.Both; } - private SelectionBox selectionBox; - private CaptureBox captureBox; + private DragBox dragBox; private readonly HashSet selectedHitObjects = new HashSet(); @@ -49,20 +60,20 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnDragStart(InputState state) { - AddInternal(selectionBox = new SelectionBox()); + AddInternal(dragBox = new DragBox()); return true; } protected override bool OnDrag(InputState state) { - selectionBox.Show(); + 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); - selectionBox.SetDragRectangle(screenSpaceDragQuad.AABBFloat); + dragBox.SetDragRectangle(screenSpaceDragQuad.AABBFloat); selectQuad(screenSpaceDragQuad); return true; @@ -70,8 +81,8 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnDragEnd(InputState state) { - selectionBox.Hide(); - selectionBox.Expire(); + dragBox.Hide(); + dragBox.Expire(); finishSelection(); @@ -95,7 +106,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection if (!select(hitObject)) return; - clearCapture(); + clearSelection(); finishSelection(); } @@ -122,7 +133,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection if (!deselect(hitObject)) return; - clearCapture(); + clearSelection(); finishSelection(); } @@ -148,7 +159,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection selectedHitObjects.ForEach(h => ObjectDeselected?.Invoke(h)); selectedHitObjects.Clear(); - clearCapture(); + clearSelection(); } /// @@ -180,18 +191,49 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection select(target); } - private void clearCapture() - { - captureBox?.Hide(); - captureBox?.Expire(); - } + private void clearSelection() => SelectionCleared?.Invoke(); private void finishSelection() { if (selectedHitObjects.Count == 0) return; + SelectionFinished?.Invoke(); + } - AddInternal(captureBox = new CaptureBox(this, selectedHitObjects.ToList())); + /// + /// 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/Screens/Edit/Screens/EditorScreen.cs b/osu.Game/Screens/Edit/Screens/EditorScreen.cs index 2e654b4373..009830502e 100644 --- a/osu.Game/Screens/Edit/Screens/EditorScreen.cs +++ b/osu.Game/Screens/Edit/Screens/EditorScreen.cs @@ -8,6 +8,9 @@ using osu.Game.Beatmaps; namespace osu.Game.Screens.Edit.Screens { + /// + /// TODO: eventually make this inherit Screen and add a local scren stack inside the Editor. + /// public class EditorScreen : Container { public readonly Bindable Beatmap = new Bindable(); diff --git a/osu.Game/Screens/Play/GameplayMenuOverlay.cs b/osu.Game/Screens/Play/GameplayMenuOverlay.cs index 615c124ea7..29b68abc21 100644 --- a/osu.Game/Screens/Play/GameplayMenuOverlay.cs +++ b/osu.Game/Screens/Play/GameplayMenuOverlay.cs @@ -18,7 +18,7 @@ using System.Collections.Generic; namespace osu.Game.Screens.Play { - public abstract class GameplayMenuOverlay : OverlayContainer, IRequireHighFrequencyMousePosition + public abstract class GameplayMenuOverlay : OverlayContainer { private const int transition_duration = 200; private const int button_height = 70; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index c8ff261a93..8502812f26 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -164,7 +164,7 @@ namespace osu.Game.Screens.Play Alpha = 0, }, RulesetContainer, - new SkipButton(firstObjectTime) + new SkipOverlay(firstObjectTime) { Clock = Clock, // skip button doesn't want to use the audio clock directly ProcessCustomClock = false, diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 784dcf7657..31e7313c0b 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -5,12 +5,14 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using OpenTK; using osu.Framework.Localisation; +using osu.Framework.Threading; using osu.Game.Screens.Menu; using osu.Game.Screens.Play.PlayerSettings; @@ -21,7 +23,6 @@ namespace osu.Game.Screens.Play private Player player; private BeatmapMetadataDisplay info; - private VisualSettings visualSettings; private bool showOverlays = true; public override bool ShowOverlaysOnEnter => showOverlays; @@ -46,7 +47,8 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre, }); - Add(visualSettings = new VisualSettings + + Add(new VisualSettings { Anchor = Anchor.TopRight, Origin = Anchor.TopRight, @@ -93,7 +95,7 @@ namespace osu.Game.Screens.Play contentIn(); info.Delay(750).FadeIn(500); - this.Delay(2150).Schedule(pushWhenLoaded); + this.Delay(1800).Schedule(pushWhenLoaded); } protected override void LogoArriving(OsuLogo logo, bool resuming) @@ -109,29 +111,65 @@ namespace osu.Game.Screens.Play logo.Delay(resuming ? 0 : 500).MoveToOffset(new Vector2(0, -0.24f), 500, Easing.InOutExpo); } + private bool weHandledMouseDown; + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + weHandledMouseDown = true; + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + weHandledMouseDown = false; + return base.OnMouseUp(state, args); + } + + private ScheduledDelegate pushDebounce; + + private bool readyForPush => player.LoadState == LoadState.Ready && IsHovered && (!GetContainingInputManager().CurrentState.Mouse.HasAnyButtonPressed || weHandledMouseDown); + private void pushWhenLoaded() { - if (player.LoadState != LoadState.Ready || visualSettings.IsHovered) + if (!IsCurrentScreen) return; + + try + { + if (!readyForPush) + { + // as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce + // if we become unready for push during the delay. + pushDebounce?.Cancel(); + pushDebounce = null; + return; + } + + if (pushDebounce != null) + return; + + pushDebounce = Scheduler.AddDelayed(() => + { + contentOut(); + + this.Delay(250).Schedule(() => + { + if (!IsCurrentScreen) return; + + if (!Push(player)) + Exit(); + else + { + //By default, we want to load the player and never be returned to. + //Note that this may change if the player we load requested a re-run. + ValidForResume = false; + } + }); + }, 500); + } + finally { Schedule(pushWhenLoaded); - return; } - - contentOut(); - - this.Delay(250).Schedule(() => - { - if (!IsCurrentScreen) return; - - if (!Push(player)) - Exit(); - else - { - //By default, we want to load the player and never be returned to. - //Note that this may change if the player we load requested a re-run. - ValidForResume = false; - } - }); } protected override bool OnExiting(Screen next) diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index 95b464154a..e0de89535e 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -5,6 +5,7 @@ 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.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -133,5 +134,8 @@ namespace osu.Game.Screens.Play.PlayerSettings } protected override Container Content => content; + + protected override bool OnHover(InputState state) => true; + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true; } } diff --git a/osu.Game/Screens/Play/SkipButton.cs b/osu.Game/Screens/Play/SkipOverlay.cs similarity index 89% rename from osu.Game/Screens/Play/SkipButton.cs rename to osu.Game/Screens/Play/SkipOverlay.cs index 08bb26c72b..19ee0cb989 100644 --- a/osu.Game/Screens/Play/SkipButton.cs +++ b/osu.Game/Screens/Play/SkipOverlay.cs @@ -21,7 +21,7 @@ using osu.Game.Input.Bindings; namespace osu.Game.Screens.Play { - public class SkipButton : OverlayContainer, IKeyBindingHandler + public class SkipOverlay : OverlayContainer, IKeyBindingHandler { private readonly double startTime; @@ -35,8 +35,9 @@ namespace osu.Game.Screens.Play private double displayTime; public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + protected override bool BlockPassThroughMouse => false; - public SkipButton(double startTime) + public SkipOverlay(double startTime) { this.startTime = startTime; @@ -51,12 +52,6 @@ namespace osu.Game.Screens.Play Origin = Anchor.Centre; } - protected override bool OnMouseMove(InputState state) - { - fadeContainer.State = Visibility.Visible; - return base.OnMouseMove(state); - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -121,15 +116,9 @@ namespace osu.Game.Screens.Play Expire(); } - protected override void PopIn() - { - this.FadeIn(); - } + protected override void PopIn() => this.FadeIn(); - protected override void PopOut() - { - this.FadeOut(); - } + protected override void PopOut() => this.FadeOut(); protected override void Update() { @@ -137,6 +126,13 @@ namespace osu.Game.Screens.Play remainingTimeBox.ResizeWidthTo((float)Math.Max(0, 1 - (Time.Current - displayTime) / (beginFadeTime - displayTime)), 120, Easing.OutQuint); } + protected override bool OnMouseMove(InputState state) + { + if (!state.Mouse.HasAnyButtonPressed) + fadeContainer.State = Visibility.Visible; + return base.OnMouseMove(state); + } + public bool OnPressed(GlobalAction action) { switch (action) @@ -176,7 +172,7 @@ namespace osu.Game.Screens.Play if (stateChanged) this.FadeIn(500, Easing.OutExpo); - if (!IsHovered) + if (!IsHovered && !IsDragged) using (BeginDelayedSequence(1000)) scheduledHide = Schedule(() => State = Visibility.Hidden); break; @@ -194,6 +190,18 @@ namespace osu.Game.Screens.Play base.LoadComplete(); State = Visibility.Visible; } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + scheduledHide?.Cancel(); + return base.OnMouseDown(state, args); + } + + protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) + { + State = Visibility.Visible; + return base.OnMouseUp(state, args); + } } private class Button : OsuClickableContainer @@ -274,7 +282,7 @@ namespace osu.Game.Screens.Play flow.TransformSpacingTo(new Vector2(5), 500, Easing.OutQuint); box.FadeColour(colourHover, 500, Easing.OutQuint); background.FadeTo(0.4f, 500, Easing.OutQuint); - return base.OnHover(state); + return true; } protected override void OnHoverLost(InputState state) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5f8c941bdd..1e381545d4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -364,10 +364,8 @@ - - - - + + @@ -382,6 +380,10 @@ + + + + @@ -396,8 +398,6 @@ - - @@ -811,7 +811,7 @@ - +