From cfb2b3f1e81fc0dbb09aa3ac89418bcc70c73619 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Feb 2018 16:02:42 +0900 Subject: [PATCH 01/69] Rework SelctionLayer to support click-selections --- .../Objects/Drawables/DrawableSlider.cs | 2 + .../Objects/Drawables/Pieces/SliderBody.cs | 2 + .../Visual/TestCaseEditorSelectionLayer.cs | 7 +- .../Layers/Selection/HitObjectCapturer.cs | 56 ++++++++++++++ .../Layers/Selection/HitObjectSelectionBox.cs | 73 ++++--------------- .../Edit/Layers/Selection/SelectionLayer.cs | 71 ++++++++++++++---- osu.Game/osu.Game.csproj | 1 + 7 files changed, 139 insertions(+), 73 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/HitObjectCapturer.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 5795bb8405..41df7ae4a4 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -157,6 +157,8 @@ 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(Body.Position); public override Quad SelectionQuad => Body.PathDrawQuad; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 901d1c568d..89af67ba2a 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -78,6 +78,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces container.Attach(RenderbufferInternalFormat.DepthComponent16); } + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => path.ReceiveMouseInputAt(screenSpacePos); + public void SetRange(double p0, double p1) { if (p0 > p1) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index 755800c4e1..ae5296b70e 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -19,7 +19,12 @@ namespace osu.Game.Tests.Visual { public class TestCaseEditorSelectionLayer : OsuTestCase { - public override IReadOnlyList RequiredTypes => new[] { typeof(SelectionLayer) }; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(HitObjectCapturer), + typeof(HitObjectSelectionBox), + typeof(SelectionLayer) + }; [BackgroundDependencyLoader] private void load() diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectCapturer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectCapturer.cs new file mode 100644 index 0000000000..e141f06816 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectCapturer.cs @@ -0,0 +1,56 @@ +// 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.Primitives; +using osu.Game.Rulesets.Objects.Drawables; +using OpenTK; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + public class HitObjectCapturer + { + public event Action HitObjectCaptured; + + private readonly IEnumerable capturableHitObjects; + + public HitObjectCapturer(IEnumerable capturableHitObjects) + { + this.capturableHitObjects = capturableHitObjects; + } + + /// + /// Captures all hitobjects that are present within the area of a . + /// + /// The capture . + /// If any s were captured. + public bool CaptureQuad(Quad screenSpaceQuad) + { + bool anyCaptured = false; + foreach (var obj in capturableHitObjects.Where(h => h.IsAlive && h.IsPresent && screenSpaceQuad.Contains(h.SelectionPoint))) + { + HitObjectCaptured?.Invoke(obj); + anyCaptured = true; + } + + return anyCaptured; + } + + /// + /// Captures the top-most hitobject that is present under a specific point. + /// + /// The to capture at. + /// Whether a was captured. + public bool CapturePoint(Vector2 screenSpacePoint) + { + var captured = capturableHitObjects.Reverse().Where(h => h.IsAlive && h.IsPresent).FirstOrDefault(h => h.ReceiveMouseInputAt(screenSpacePoint)); + if (captured == null) + return false; + + HitObjectCaptured?.Invoke(captured); + return true; + } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs index fcb2f8f57f..c887cd0b7b 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs @@ -1,7 +1,6 @@ // 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; @@ -12,7 +11,6 @@ using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using OpenTK; using OpenTK.Graphics; -using osu.Framework.Configuration; namespace osu.Game.Rulesets.Edit.Layers.Selection { @@ -21,29 +19,18 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// public class HitObjectSelectionBox : CompositeDrawable { - public readonly Bindable Selection = new Bindable(); - - /// - /// The s that can be selected through a drag-selection. - /// - public IEnumerable CapturableObjects; - private readonly Container borderMask; private readonly Drawable background; private readonly HandleContainer handles; private Color4 captureFinishedColour; - - private readonly Vector2 startPos; + private RectangleF dragRectangle; /// /// Creates a new . /// - /// The point at which the drag was initiated, in the parent's coordinates. - public HitObjectSelectionBox(Vector2 startPos) + public HitObjectSelectionBox() { - this.startPos = startPos; - InternalChildren = new Drawable[] { new Container @@ -70,8 +57,8 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection RelativeSizeAxes = Axes.Both, Alpha = 0, GetDragRectangle = () => dragRectangle, - UpdateDragRectangle = updateDragRectangle, - FinishDrag = FinishCapture + UpdateDragRectangle = SetDragRectangle, + FinishDrag = () => FinishCapture() } }; } @@ -82,49 +69,29 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection captureFinishedColour = colours.Yellow; } - /// - /// The secondary corner of the drag selection box. A rectangle will be fit between the starting position and this value. - /// - public Vector2 DragEndPosition { set => updateDragRectangle(RectangleF.FromLTRB(startPos.X, startPos.Y, value.X, value.Y)); } - - private RectangleF dragRectangle; - private void updateDragRectangle(RectangleF rectangle) + public void SetDragRectangle(RectangleF rectangle) { dragRectangle = rectangle; - Position = new Vector2( - Math.Min(rectangle.Left, rectangle.Right), - Math.Min(rectangle.Top, rectangle.Bottom)); + var topLeft = Parent.ToLocalSpace(rectangle.TopLeft); + var bottomRight = Parent.ToLocalSpace(rectangle.BottomRight); - Size = new Vector2( - Math.Max(rectangle.Left, rectangle.Right) - Position.X, - Math.Max(rectangle.Top, rectangle.Bottom) - Position.Y); + Position = topLeft; + Size = bottomRight - topLeft; } private readonly List capturedHitObjects = new List(); - /// - /// Processes hitobjects to determine which ones are captured by the drag selection. - /// Captured hitobjects will be enclosed by the drag selection upon . - /// - public void BeginCapture() - { - capturedHitObjects.Clear(); + public bool HasCaptured => capturedHitObjects.Count > 0; - foreach (var obj in CapturableObjects) - { - if (!obj.IsAlive || !obj.IsPresent) - continue; + public void AddCaptured(DrawableHitObject hitObject) => capturedHitObjects.Add(hitObject); - if (ScreenSpaceDrawQuad.Contains(obj.SelectionPoint)) - capturedHitObjects.Add(obj); - } - } + public void ClearCaptured() => capturedHitObjects.Clear(); /// /// Encloses hitobjects captured through in the drag selection box. /// - public void FinishCapture() + public void FinishCapture(bool instant = false) { if (capturedHitObjects.Count == 0) { @@ -145,8 +112,8 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection topLeft -= new Vector2(5); bottomRight += new Vector2(5); - this.MoveTo(topLeft, 200, Easing.OutQuint) - .ResizeTo(bottomRight - topLeft, 200, Easing.OutQuint); + this.MoveTo(topLeft, instant ? 0 : 100, Easing.OutQuint) + .ResizeTo(bottomRight - topLeft, instant ? 0 : 100, Easing.OutQuint); dragRectangle = RectangleF.FromLTRB(topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y); @@ -156,12 +123,6 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection // Transform into markers to let the user modify the drag selection further. background.Delay(50).FadeOut(200); handles.FadeIn(200); - - Selection.Value = new SelectionInfo - { - Objects = capturedHitObjects, - SelectionQuad = Parent.ToScreenSpace(dragRectangle) - }; } private bool isActive = true; @@ -171,9 +132,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection public override void Hide() { isActive = false; - this.FadeOut(400, Easing.OutQuint).Expire(); - - Selection.Value = null; + this.FadeOut(400, Easing.OutQuint); } } } diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index 93755d400a..b9ece8759c 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -1,18 +1,18 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.Configuration; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Primitives; using osu.Framework.Input; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Edit.Layers.Selection { public class SelectionLayer : CompositeDrawable { - public readonly Bindable Selection = new Bindable(); - private readonly Playfield playfield; public SelectionLayer(Playfield playfield) @@ -22,39 +22,80 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection RelativeSizeAxes = Axes.Both; } - private HitObjectSelectionBox selectionBoxBox; + private HitObjectSelectionBox selectionBox; + private HitObjectCapturer capturer; + + [BackgroundDependencyLoader] + private void load() + { + capturer = new HitObjectCapturer(playfield.HitObjects.Objects); + capturer.HitObjectCaptured += hitObjectCaptured; + } + + private void hitObjectCaptured(DrawableHitObject hitObject) => selectionBox.AddCaptured(hitObject); protected override bool OnDragStart(InputState state) { // Hide the previous drag box - we won't be working with it any longer - selectionBoxBox?.Hide(); + selectionBox?.Hide(); + selectionBox?.Expire(); - AddInternal(selectionBoxBox = new HitObjectSelectionBox(ToLocalSpace(state.Mouse.NativeState.Position)) - { - CapturableObjects = playfield.HitObjects.Objects, - }); - - Selection.BindTo(selectionBoxBox.Selection); + AddInternal(selectionBox = new HitObjectSelectionBox()); return true; } protected override bool OnDrag(InputState state) { - selectionBoxBox.DragEndPosition = ToLocalSpace(state.Mouse.NativeState.Position); - selectionBoxBox.BeginCapture(); + 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); + capturer.CaptureQuad(screenSpaceDragQuad); + return true; } protected override bool OnDragEnd(InputState state) { - selectionBoxBox.FinishCapture(); + // Due to https://github.com/ppy/osu-framework/issues/1382, we may get here after OnClick has set the selectionBox to null + // In the case that the user dragged within the click distance out of an object + if (selectionBox == null) + return true; + + selectionBox.FinishCapture(); + + // If there are no hitobjects, remove the selection box + if (!selectionBox.HasCaptured) + { + selectionBox.Expire(); + selectionBox = null; + } + return true; } protected override bool OnClick(InputState state) { - selectionBoxBox?.Hide(); + // We could be coming here without a previous selection box + if (selectionBox == null) + AddInternal(selectionBox = new HitObjectSelectionBox { Position = ToLocalSpace(state.Mouse.NativeState.Position), Alpha = 0 }); + + // If we're coming here with a previous selection, unselect those hitobjects + selectionBox.ClearCaptured(); + if (capturer.CapturePoint(state.Mouse.NativeState.Position)) + { + selectionBox.Alpha = 1; + selectionBox.FinishCapture(true); + } + else + { + selectionBox.Hide(); + selectionBox = null; + } + return true; } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index bb9925abbc..e1f5eee182 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -332,6 +332,7 @@ + From 1adbe3585c14c0f5b37e7192dd8c0aa270bb150f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Feb 2018 16:03:59 +0900 Subject: [PATCH 02/69] HitObjectSelectionBox -> SelectionDragger --- .../Visual/TestCaseEditorSelectionLayer.cs | 2 +- .../Rulesets/Edit/Layers/Selection/Handle.cs | 2 +- ...ectSelectionBox.cs => SelectionDragger.cs} | 6 ++-- .../Edit/Layers/Selection/SelectionLayer.cs | 36 +++++++++---------- osu.Game/osu.Game.csproj | 2 +- 5 files changed, 24 insertions(+), 24 deletions(-) rename osu.Game/Rulesets/Edit/Layers/Selection/{HitObjectSelectionBox.cs => SelectionDragger.cs} (94%) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index ae5296b70e..a7220718f5 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual public override IReadOnlyList RequiredTypes => new[] { typeof(HitObjectCapturer), - typeof(HitObjectSelectionBox), + typeof(SelectionDragger), typeof(SelectionLayer) }; diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs b/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs index d275022a15..e85096110b 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs @@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection { /// /// Represents a marker visible on the border of a which exposes - /// properties that are used to resize a . + /// properties that are used to resize a . /// public class Handle : CompositeDrawable { diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionDragger.cs similarity index 94% rename from osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs rename to osu.Game/Rulesets/Edit/Layers/Selection/SelectionDragger.cs index c887cd0b7b..b08d208bbd 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectSelectionBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionDragger.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// /// A box that represents a drag selection. /// - public class HitObjectSelectionBox : CompositeDrawable + public class SelectionDragger : CompositeDrawable { private readonly Container borderMask; private readonly Drawable background; @@ -27,9 +27,9 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection private RectangleF dragRectangle; /// - /// Creates a new . + /// Creates a new . /// - public HitObjectSelectionBox() + public SelectionDragger() { InternalChildren = new Drawable[] { diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index b9ece8759c..19891b0783 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection RelativeSizeAxes = Axes.Both; } - private HitObjectSelectionBox selectionBox; + private SelectionDragger selectionDragger; private HitObjectCapturer capturer; [BackgroundDependencyLoader] @@ -32,15 +32,15 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection capturer.HitObjectCaptured += hitObjectCaptured; } - private void hitObjectCaptured(DrawableHitObject hitObject) => selectionBox.AddCaptured(hitObject); + private void hitObjectCaptured(DrawableHitObject hitObject) => selectionDragger.AddCaptured(hitObject); protected override bool OnDragStart(InputState state) { // Hide the previous drag box - we won't be working with it any longer - selectionBox?.Hide(); - selectionBox?.Expire(); + selectionDragger?.Hide(); + selectionDragger?.Expire(); - AddInternal(selectionBox = new HitObjectSelectionBox()); + AddInternal(selectionDragger = new SelectionDragger()); return true; } @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection var screenSpaceDragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); - selectionBox.SetDragRectangle(screenSpaceDragQuad.AABBFloat); + selectionDragger.SetDragRectangle(screenSpaceDragQuad.AABBFloat); capturer.CaptureQuad(screenSpaceDragQuad); return true; @@ -62,16 +62,16 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection { // Due to https://github.com/ppy/osu-framework/issues/1382, we may get here after OnClick has set the selectionBox to null // In the case that the user dragged within the click distance out of an object - if (selectionBox == null) + if (selectionDragger == null) return true; - selectionBox.FinishCapture(); + selectionDragger.FinishCapture(); // If there are no hitobjects, remove the selection box - if (!selectionBox.HasCaptured) + if (!selectionDragger.HasCaptured) { - selectionBox.Expire(); - selectionBox = null; + selectionDragger.Expire(); + selectionDragger = null; } return true; @@ -80,20 +80,20 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnClick(InputState state) { // We could be coming here without a previous selection box - if (selectionBox == null) - AddInternal(selectionBox = new HitObjectSelectionBox { Position = ToLocalSpace(state.Mouse.NativeState.Position), Alpha = 0 }); + if (selectionDragger == null) + AddInternal(selectionDragger = new SelectionDragger { Position = ToLocalSpace(state.Mouse.NativeState.Position), Alpha = 0 }); // If we're coming here with a previous selection, unselect those hitobjects - selectionBox.ClearCaptured(); + selectionDragger.ClearCaptured(); if (capturer.CapturePoint(state.Mouse.NativeState.Position)) { - selectionBox.Alpha = 1; - selectionBox.FinishCapture(true); + selectionDragger.Alpha = 1; + selectionDragger.FinishCapture(true); } else { - selectionBox.Hide(); - selectionBox = null; + selectionDragger.Hide(); + selectionDragger = null; } return true; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e1f5eee182..77e594c37c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -353,7 +353,7 @@ - + From ee168f9a77962e25c8419d3a2fdccd55eb945807 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Feb 2018 18:19:55 +0900 Subject: [PATCH 03/69] Split out drag selection box from post-capture box Cleans up a lot of code, removes handles for now. --- .../Visual/TestCaseEditorSelectionLayer.cs | 3 +- .../Edit/Layers/Selection/CaptureBox.cs | 76 ++++++++++++++++ .../Edit/Layers/Selection/SelectionDragger.cs | 87 +------------------ .../Edit/Layers/Selection/SelectionLayer.cs | 61 ++++++------- osu.Game/osu.Game.csproj | 1 + 5 files changed, 108 insertions(+), 120 deletions(-) create mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index a7220718f5..9796d1715f 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -23,7 +23,8 @@ namespace osu.Game.Tests.Visual { typeof(HitObjectCapturer), typeof(SelectionDragger), - typeof(SelectionLayer) + typeof(SelectionLayer), + typeof(CaptureBox) }; [BackgroundDependencyLoader] diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs new file mode 100644 index 0000000000..f49a294129 --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs @@ -0,0 +1,76 @@ +// 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 encapsulates captured s. + /// + public class CaptureBox : VisibilityContainer + { + private readonly IDrawable captureArea; + private readonly IReadOnlyList capturedObjects; + + private readonly Container borderContainer; + + public CaptureBox(IDrawable captureArea, IReadOnlyList capturedObjects) + { + this.captureArea = captureArea; + this.capturedObjects = capturedObjects; + + Origin = Anchor.Centre; + + InternalChild = borderContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderThickness = 3, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + AlwaysPresent = true, + Alpha = 0 + } + }; + + State = Visibility.Visible; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + borderContainer.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 + Size / 2f; + } + + protected override void PopIn() => this.ScaleTo(1.1f) + .Then() + .ScaleTo(1f, 300, Easing.OutQuint).FadeIn(300, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(300, Easing.OutQuint); + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionDragger.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionDragger.cs index b08d208bbd..661d09d883 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionDragger.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionDragger.cs @@ -1,15 +1,10 @@ // 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.Primitives; using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; using OpenTK.Graphics; namespace osu.Game.Rulesets.Edit.Layers.Selection @@ -19,13 +14,6 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// public class SelectionDragger : CompositeDrawable { - private readonly Container borderMask; - private readonly Drawable background; - private readonly HandleContainer handles; - - private Color4 captureFinishedColour; - private RectangleF dragRectangle; - /// /// Creates a new . /// @@ -37,42 +25,26 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection { RelativeSizeAxes = Axes.Both, Padding = new MarginPadding(-1), - Child = borderMask = new Container + Child = new Container { RelativeSizeAxes = Axes.Both, Masking = true, BorderColour = Color4.White, BorderThickness = 2, MaskingSmoothness = 1, - Child = background = new Box + Child = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0.1f, AlwaysPresent = true }, } - }, - handles = new HandleContainer - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - GetDragRectangle = () => dragRectangle, - UpdateDragRectangle = SetDragRectangle, - FinishDrag = () => FinishCapture() } }; } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - captureFinishedColour = colours.Yellow; - } - public void SetDragRectangle(RectangleF rectangle) { - dragRectangle = rectangle; - var topLeft = Parent.ToLocalSpace(rectangle.TopLeft); var bottomRight = Parent.ToLocalSpace(rectangle.BottomRight); @@ -80,59 +52,6 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection Size = bottomRight - topLeft; } - private readonly List capturedHitObjects = new List(); - - public bool HasCaptured => capturedHitObjects.Count > 0; - - public void AddCaptured(DrawableHitObject hitObject) => capturedHitObjects.Add(hitObject); - - public void ClearCaptured() => capturedHitObjects.Clear(); - - /// - /// Encloses hitobjects captured through in the drag selection box. - /// - public void FinishCapture(bool instant = false) - { - if (capturedHitObjects.Count == 0) - { - Hide(); - return; - } - - // 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 capturedHitObjects) - { - topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(obj.SelectionQuad.TopLeft)); - bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(obj.SelectionQuad.BottomRight)); - } - - topLeft -= new Vector2(5); - bottomRight += new Vector2(5); - - this.MoveTo(topLeft, instant ? 0 : 100, Easing.OutQuint) - .ResizeTo(bottomRight - topLeft, instant ? 0 : 100, Easing.OutQuint); - - dragRectangle = RectangleF.FromLTRB(topLeft.X, topLeft.Y, bottomRight.X, bottomRight.Y); - - borderMask.BorderThickness = 3; - borderMask.FadeColour(captureFinishedColour, 200); - - // Transform into markers to let the user modify the drag selection further. - background.Delay(50).FadeOut(200); - handles.FadeIn(200); - } - - private bool isActive = true; - public override bool HandleKeyboardInput => isActive; - public override bool HandleMouseInput => isActive; - - public override void Hide() - { - isActive = false; - this.FadeOut(400, Easing.OutQuint); - } + public override void Hide() => this.FadeOut(400, Easing.OutQuint).Expire(); } } diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index 19891b0783..a90d01646e 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -1,6 +1,8 @@ // 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; @@ -23,25 +25,28 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection } private SelectionDragger selectionDragger; + private CaptureBox captureBox; private HitObjectCapturer capturer; + private readonly List capturedHitObjects = new List(); + [BackgroundDependencyLoader] private void load() { capturer = new HitObjectCapturer(playfield.HitObjects.Objects); - capturer.HitObjectCaptured += hitObjectCaptured; + capturer.HitObjectCaptured += h => capturedHitObjects.Add(h); } - private void hitObjectCaptured(DrawableHitObject hitObject) => selectionDragger.AddCaptured(hitObject); + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + capturedHitObjects.Clear(); + captureBox?.Hide(); + return true; + } protected override bool OnDragStart(InputState state) { - // Hide the previous drag box - we won't be working with it any longer - selectionDragger?.Hide(); - selectionDragger?.Expire(); - AddInternal(selectionDragger = new SelectionDragger()); - return true; } @@ -60,43 +65,29 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnDragEnd(InputState state) { - // Due to https://github.com/ppy/osu-framework/issues/1382, we may get here after OnClick has set the selectionBox to null - // In the case that the user dragged within the click distance out of an object - if (selectionDragger == null) - return true; - - selectionDragger.FinishCapture(); - - // If there are no hitobjects, remove the selection box - if (!selectionDragger.HasCaptured) - { - selectionDragger.Expire(); - selectionDragger = null; - } + selectionDragger.Hide(); + finishCapture(); return true; } protected override bool OnClick(InputState state) { - // We could be coming here without a previous selection box - if (selectionDragger == null) - AddInternal(selectionDragger = new SelectionDragger { Position = ToLocalSpace(state.Mouse.NativeState.Position), Alpha = 0 }); - - // If we're coming here with a previous selection, unselect those hitobjects - selectionDragger.ClearCaptured(); if (capturer.CapturePoint(state.Mouse.NativeState.Position)) - { - selectionDragger.Alpha = 1; - selectionDragger.FinishCapture(true); - } - else - { - selectionDragger.Hide(); - selectionDragger = null; - } + finishCapture(); return true; } + + private void finishCapture() + { + if (capturedHitObjects.Count == 0) + return; + + // Due to https://github.com/ppy/osu-framework/issues/1382, we may get here through both + // OnDragEnd and OnClick methods within a single frame, OnMouseDown doesn't help us here + captureBox?.Hide(); + AddInternal(captureBox = new CaptureBox(this, capturedHitObjects.ToList())); + } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 77e594c37c..ce0813f393 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -332,6 +332,7 @@ + From 20c0dee17df2bb5bfd74bfa60a7226e7013b53db Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Feb 2018 18:22:52 +0900 Subject: [PATCH 04/69] Remove SelectionInfo --- .../Edit/Layers/Selection/SelectionInfo.cs | 22 ------------------- osu.Game/osu.Game.csproj | 1 - 2 files changed, 23 deletions(-) delete mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs deleted file mode 100644 index beedb415c2..0000000000 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionInfo.cs +++ /dev/null @@ -1,22 +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.Graphics.Primitives; -using osu.Game.Rulesets.Objects.Drawables; - -namespace osu.Game.Rulesets.Edit.Layers.Selection -{ - public class SelectionInfo - { - /// - /// The objects that are captured by the selection. - /// - public IEnumerable Objects; - - /// - /// The screen space quad of the selection box surrounding . - /// - public Quad SelectionQuad; - } -} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ce0813f393..7e884fdc24 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -357,7 +357,6 @@ - From e10bb2db0591c2ef51cf6418bbdc2ba007b0ac28 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Feb 2018 18:35:01 +0900 Subject: [PATCH 05/69] Unify SelectionLayer and HitObjectCapturer, rename SelectionBox --- .../Visual/TestCaseEditorSelectionLayer.cs | 3 +- .../Layers/Selection/HitObjectCapturer.cs | 56 ------------------- .../{SelectionDragger.cs => SelectionBox.cs} | 6 +- .../Edit/Layers/Selection/SelectionLayer.cs | 47 ++++++++++------ osu.Game/osu.Game.csproj | 3 +- 5 files changed, 36 insertions(+), 79 deletions(-) delete mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/HitObjectCapturer.cs rename osu.Game/Rulesets/Edit/Layers/Selection/{SelectionDragger.cs => SelectionBox.cs} (89%) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index 9796d1715f..50a39e6c33 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -21,8 +21,7 @@ namespace osu.Game.Tests.Visual { public override IReadOnlyList RequiredTypes => new[] { - typeof(HitObjectCapturer), - typeof(SelectionDragger), + typeof(SelectionBox), typeof(SelectionLayer), typeof(CaptureBox) }; diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectCapturer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectCapturer.cs deleted file mode 100644 index e141f06816..0000000000 --- a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectCapturer.cs +++ /dev/null @@ -1,56 +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.Graphics.Primitives; -using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; - -namespace osu.Game.Rulesets.Edit.Layers.Selection -{ - public class HitObjectCapturer - { - public event Action HitObjectCaptured; - - private readonly IEnumerable capturableHitObjects; - - public HitObjectCapturer(IEnumerable capturableHitObjects) - { - this.capturableHitObjects = capturableHitObjects; - } - - /// - /// Captures all hitobjects that are present within the area of a . - /// - /// The capture . - /// If any s were captured. - public bool CaptureQuad(Quad screenSpaceQuad) - { - bool anyCaptured = false; - foreach (var obj in capturableHitObjects.Where(h => h.IsAlive && h.IsPresent && screenSpaceQuad.Contains(h.SelectionPoint))) - { - HitObjectCaptured?.Invoke(obj); - anyCaptured = true; - } - - return anyCaptured; - } - - /// - /// Captures the top-most hitobject that is present under a specific point. - /// - /// The to capture at. - /// Whether a was captured. - public bool CapturePoint(Vector2 screenSpacePoint) - { - var captured = capturableHitObjects.Reverse().Where(h => h.IsAlive && h.IsPresent).FirstOrDefault(h => h.ReceiveMouseInputAt(screenSpacePoint)); - if (captured == null) - return false; - - HitObjectCaptured?.Invoke(captured); - return true; - } - } -} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionDragger.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs similarity index 89% rename from osu.Game/Rulesets/Edit/Layers/Selection/SelectionDragger.cs rename to osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs index 661d09d883..87c6833f01 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionDragger.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs @@ -12,12 +12,12 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// /// A box that represents a drag selection. /// - public class SelectionDragger : CompositeDrawable + public class SelectionBox : CompositeDrawable { /// - /// Creates a new . + /// Creates a new . /// - public SelectionDragger() + public SelectionBox() { InternalChildren = new Drawable[] { diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index a90d01646e..e7a46569f1 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -3,13 +3,13 @@ using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.UI; +using OpenTK; namespace osu.Game.Rulesets.Edit.Layers.Selection { @@ -24,19 +24,11 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection RelativeSizeAxes = Axes.Both; } - private SelectionDragger selectionDragger; + private SelectionBox selectionBox; private CaptureBox captureBox; - private HitObjectCapturer capturer; private readonly List capturedHitObjects = new List(); - [BackgroundDependencyLoader] - private void load() - { - capturer = new HitObjectCapturer(playfield.HitObjects.Objects); - capturer.HitObjectCaptured += h => capturedHitObjects.Add(h); - } - protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { capturedHitObjects.Clear(); @@ -46,7 +38,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnDragStart(InputState state) { - AddInternal(selectionDragger = new SelectionDragger()); + AddInternal(selectionBox = new SelectionBox()); return true; } @@ -57,15 +49,15 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection var screenSpaceDragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); - selectionDragger.SetDragRectangle(screenSpaceDragQuad.AABBFloat); - capturer.CaptureQuad(screenSpaceDragQuad); + selectionBox.SetDragRectangle(screenSpaceDragQuad.AABBFloat); + captureQuad(screenSpaceDragQuad); return true; } protected override bool OnDragEnd(InputState state) { - selectionDragger.Hide(); + selectionBox.Hide(); finishCapture(); return true; @@ -73,12 +65,35 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnClick(InputState state) { - if (capturer.CapturePoint(state.Mouse.NativeState.Position)) - finishCapture(); + capturePoint(state.Mouse.NativeState.Position); + finishCapture(); return true; } + /// + /// Captures all hitobjects that are present within the area of a . + /// + /// The capture . + private void captureQuad(Quad screenSpaceQuad) + { + foreach (var obj in playfield.HitObjects.Objects.Where(h => h.IsAlive && h.IsPresent && screenSpaceQuad.Contains(h.SelectionPoint))) + capturedHitObjects.Add(obj); + } + + /// + /// Captures the top-most hitobject that is present under a specific point. + /// + /// The to capture at. + private void capturePoint(Vector2 screenSpacePoint) + { + var captured = playfield.HitObjects.Objects.Reverse().Where(h => h.IsAlive && h.IsPresent).FirstOrDefault(h => h.ReceiveMouseInputAt(screenSpacePoint)); + if (captured == null) + return; + + capturedHitObjects.Add(captured); + } + private void finishCapture() { if (capturedHitObjects.Count == 0) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7e884fdc24..69b5c45890 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -333,7 +333,6 @@ - @@ -354,9 +353,9 @@ - + From c3212d36efb74f5721419621e8480eb4952674c1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Feb 2018 18:36:16 +0900 Subject: [PATCH 06/69] Remove handles for now (will be re-implemented) --- .../Rulesets/Edit/Layers/Selection/Handle.cs | 105 ------------------ .../Edit/Layers/Selection/HandleContainer.cs | 92 --------------- .../Edit/Layers/Selection/OriginHandle.cs | 50 --------- osu.Game/osu.Game.csproj | 3 - 4 files changed, 250 deletions(-) delete mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs delete mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs delete mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/OriginHandle.cs diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs b/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs deleted file mode 100644 index e85096110b..0000000000 --- a/osu.Game/Rulesets/Edit/Layers/Selection/Handle.cs +++ /dev/null @@ -1,105 +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 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.Graphics; -using OpenTK; -using OpenTK.Graphics; - -namespace osu.Game.Rulesets.Edit.Layers.Selection -{ - /// - /// Represents a marker visible on the border of a which exposes - /// properties that are used to resize a . - /// - public class Handle : CompositeDrawable - { - private const float marker_size = 10; - - /// - /// Invoked when this requires the current drag rectangle. - /// - public Func GetDragRectangle; - - /// - /// Invoked when this wants to update the drag rectangle. - /// - public Action UpdateDragRectangle; - - /// - /// Invoked when this has finished updates to the drag rectangle. - /// - public Action FinishDrag; - - private Color4 normalColour; - private Color4 hoverColour; - - public Handle() - { - Size = new Vector2(marker_size); - - InternalChild = new CircularContainer - { - RelativeSizeAxes = Axes.Both, - Masking = true, - Child = new Box { RelativeSizeAxes = Axes.Both } - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = normalColour = colours.Yellow; - hoverColour = colours.YellowDarker; - } - - protected override bool OnDragStart(InputState state) => true; - - protected override bool OnDrag(InputState state) - { - var currentRectangle = GetDragRectangle(); - - float left = currentRectangle.Left; - float right = currentRectangle.Right; - float top = currentRectangle.Top; - float bottom = currentRectangle.Bottom; - - // Apply modifications to the capture rectangle - if ((Anchor & Anchor.y0) > 0) - top += state.Mouse.Delta.Y; - else if ((Anchor & Anchor.y2) > 0) - bottom += state.Mouse.Delta.Y; - - if ((Anchor & Anchor.x0) > 0) - left += state.Mouse.Delta.X; - else if ((Anchor & Anchor.x2) > 0) - right += state.Mouse.Delta.X; - - UpdateDragRectangle(RectangleF.FromLTRB(left, top, right, bottom)); - return true; - } - - protected override bool OnDragEnd(InputState state) - { - FinishDrag(); - return true; - } - - protected override bool OnHover(InputState state) - { - this.FadeColour(hoverColour, 100); - return true; - } - - protected override void OnHoverLost(InputState state) - { - this.FadeColour(normalColour, 100); - } - } -} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs deleted file mode 100644 index 359cdd009a..0000000000 --- a/osu.Game/Rulesets/Edit/Layers/Selection/HandleContainer.cs +++ /dev/null @@ -1,92 +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.Linq; -using osu.Framework.Extensions.IEnumerableExtensions; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Primitives; - -namespace osu.Game.Rulesets.Edit.Layers.Selection -{ - /// - /// A that has s around its border. - /// - public class HandleContainer : CompositeDrawable - { - /// - /// Invoked when a requires the current drag rectangle. - /// - public Func GetDragRectangle; - - /// - /// Invoked when a wants to update the drag rectangle. - /// - public Action UpdateDragRectangle; - - /// - /// Invoked when a has finished updates to the drag rectangle. - /// - public Action FinishDrag; - - public HandleContainer() - { - InternalChildren = new Drawable[] - { - new Handle - { - Anchor = Anchor.TopLeft, - Origin = Anchor.Centre - }, - new Handle - { - Anchor = Anchor.TopCentre, - Origin = Anchor.Centre - }, - new Handle - { - Anchor = Anchor.TopRight, - Origin = Anchor.Centre - }, - new Handle - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.Centre - }, - new Handle - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.Centre - }, - new Handle - { - Anchor = Anchor.CentreRight, - Origin = Anchor.Centre - }, - new Handle - { - Anchor = Anchor.BottomRight, - Origin = Anchor.Centre - }, - new Handle - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.Centre - }, - new OriginHandle - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre - } - }; - - InternalChildren.OfType().ForEach(m => - { - m.GetDragRectangle = () => GetDragRectangle(); - m.UpdateDragRectangle = r => UpdateDragRectangle(r); - m.FinishDrag = () => FinishDrag(); - }); - } - } -} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/OriginHandle.cs b/osu.Game/Rulesets/Edit/Layers/Selection/OriginHandle.cs deleted file mode 100644 index 6f8c946165..0000000000 --- a/osu.Game/Rulesets/Edit/Layers/Selection/OriginHandle.cs +++ /dev/null @@ -1,50 +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.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; -using OpenTK; - -namespace osu.Game.Rulesets.Edit.Layers.Selection -{ - /// - /// Represents the origin of a . - /// - public class OriginHandle : CompositeDrawable - { - private const float marker_size = 10; - private const float line_width = 2; - - public OriginHandle() - { - Size = new Vector2(marker_size); - - InternalChildren = new[] - { - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.X, - Height = line_width - }, - new Box - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - RelativeSizeAxes = Axes.Y, - Width = line_width - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - Colour = colours.Yellow; - } - } -} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 69b5c45890..3b62c849cb 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -352,9 +352,6 @@ - - - From bfedd1cee03a067d1e5f39ee821b9b7a5a2f688a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 12 Feb 2018 19:00:44 +0900 Subject: [PATCH 07/69] Remove nested container --- .../Edit/Layers/Selection/CaptureBox.cs | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs index f49a294129..423927481c 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs @@ -20,26 +20,20 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection private readonly IDrawable captureArea; private readonly IReadOnlyList capturedObjects; - private readonly Container borderContainer; - public CaptureBox(IDrawable captureArea, IReadOnlyList capturedObjects) { this.captureArea = captureArea; this.capturedObjects = capturedObjects; Origin = Anchor.Centre; + Masking = true; + BorderThickness = 3; - InternalChild = borderContainer = new Container + InternalChild = new Box { RelativeSizeAxes = Axes.Both, - Masking = true, - BorderThickness = 3, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - AlwaysPresent = true, - Alpha = 0 - } + AlwaysPresent = true, + Alpha = 0 }; State = Visibility.Visible; @@ -48,7 +42,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection [BackgroundDependencyLoader] private void load(OsuColour colours) { - borderContainer.BorderColour = colours.Yellow; + BorderColour = colours.Yellow; // Move the rectangle to cover the hitobjects var topLeft = new Vector2(float.MaxValue, float.MaxValue); From 344da5965c1fea626eb46615e18a49f8469e504a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Feb 2018 14:38:37 +0900 Subject: [PATCH 08/69] Add a CaptureBox that encloses hitobjects from the drag selection --- .../Edit/Layers/Selection/CaptureBox.cs | 59 ++++++++++++++++--- .../Edit/Layers/Selection/SelectionLayer.cs | 12 ++-- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs index 423927481c..5d1bcd1ef7 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs @@ -15,17 +15,26 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// /// A box which encapsulates captured s. /// - public class CaptureBox : VisibilityContainer + public abstract class CaptureBox : VisibilityContainer { + /// + /// Top-left corner of the rectangle that encloses the s. + /// + protected Vector2 FinalPosition { get; private set; } + + /// + /// Size of the rectangle that encloses the s. + /// + protected Vector2 FinalSize { get; private set; } + private readonly IDrawable captureArea; private readonly IReadOnlyList capturedObjects; - public CaptureBox(IDrawable captureArea, IReadOnlyList capturedObjects) + protected CaptureBox(IDrawable captureArea, IReadOnlyList capturedObjects) { this.captureArea = captureArea; this.capturedObjects = capturedObjects; - Origin = Anchor.Centre; Masking = true; BorderThickness = 3; @@ -57,13 +66,47 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection topLeft -= new Vector2(5); bottomRight += new Vector2(5); - Size = bottomRight - topLeft; - Position = topLeft + Size / 2f; + FinalSize = bottomRight - topLeft; + FinalPosition = topLeft; } - protected override void PopIn() => this.ScaleTo(1.1f) - .Then() - .ScaleTo(1f, 300, Easing.OutQuint).FadeIn(300, Easing.OutQuint); + protected override void PopIn() => this.MoveTo(FinalPosition).ResizeTo(FinalSize).FadeIn(); + protected override void PopOut() => this.FadeOut(); + } + + /// + /// A which fully encloses the s from the start. + /// + public class InstantCaptureBox : CaptureBox + { + public InstantCaptureBox(IDrawable captureArea, IReadOnlyList capturedObjects) + : base(captureArea, capturedObjects) + { + Origin = Anchor.Centre; + } + + protected override void PopIn() + => this.MoveTo(FinalPosition + FinalSize / 2f).ResizeTo(FinalSize).ScaleTo(1.1f) + .Then() + .ScaleTo(1f, 300, Easing.OutQuint).FadeIn(300, Easing.OutQuint); + + protected override void PopOut() => this.FadeOut(300, Easing.OutQuint); + } + + /// + /// A which moves from an initial position + size to enclose s. + /// + public class DragCaptureBox : CaptureBox + { + public DragCaptureBox(IDrawable captureArea, IReadOnlyList capturedObjects, Vector2 initialPosition, Vector2 initialSize) + : base(captureArea, capturedObjects) + { + Position = initialPosition; + Size = initialSize; + } + + protected override void PopIn() + => this.MoveTo(FinalPosition, 300, Easing.OutQuint).ResizeTo(FinalSize, 300, Easing.OutQuint).FadeIn(300, Easing.OutQuint); protected override void PopOut() => this.FadeOut(300, Easing.OutQuint); } diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index e7a46569f1..e1fc0d179a 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnDragEnd(InputState state) { selectionBox.Hide(); - finishCapture(); + finishCapture(true); return true; } @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnClick(InputState state) { capturePoint(state.Mouse.NativeState.Position); - finishCapture(); + finishCapture(false); return true; } @@ -94,7 +94,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection capturedHitObjects.Add(captured); } - private void finishCapture() + private void finishCapture(bool fromDrag) { if (capturedHitObjects.Count == 0) return; @@ -102,7 +102,11 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection // Due to https://github.com/ppy/osu-framework/issues/1382, we may get here through both // OnDragEnd and OnClick methods within a single frame, OnMouseDown doesn't help us here captureBox?.Hide(); - AddInternal(captureBox = new CaptureBox(this, capturedHitObjects.ToList())); + + if (fromDrag) + AddInternal(captureBox = new DragCaptureBox(this, capturedHitObjects.ToList(), selectionBox.Position, selectionBox.Size)); + else + AddInternal(captureBox = new InstantCaptureBox(this, capturedHitObjects.ToList())); } } } From 27c6f4ee83b085af771d2856403a38992f412300 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Feb 2018 14:41:06 +0900 Subject: [PATCH 09/69] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index eba12eb4a0..63c9440bfb 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit eba12eb4a0fa6238873dd266deb35bfdece21a6a +Subproject commit 63c9440bfbd2bfb36f14c9ee0a521a6c46849cec From a9d14eadacea77e5b479adb7396367f45c31132a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Feb 2018 17:53:04 +0900 Subject: [PATCH 10/69] Add clearSelection method --- .../Edit/Layers/Selection/SelectionLayer.cs | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index e1fc0d179a..a335108a0f 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -27,12 +27,11 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection private SelectionBox selectionBox; private CaptureBox captureBox; - private readonly List capturedHitObjects = new List(); + private readonly List selectedHitObjects = new List(); protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) { - capturedHitObjects.Clear(); - captureBox?.Hide(); + clearSelection(); return true; } @@ -71,6 +70,15 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection return true; } + /// + /// Deselects all selected s. + /// + private void clearSelection() + { + selectedHitObjects.Clear(); + captureBox?.Hide(); + } + /// /// Captures all hitobjects that are present within the area of a . /// @@ -78,7 +86,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection private void captureQuad(Quad screenSpaceQuad) { foreach (var obj in playfield.HitObjects.Objects.Where(h => h.IsAlive && h.IsPresent && screenSpaceQuad.Contains(h.SelectionPoint))) - capturedHitObjects.Add(obj); + selectedHitObjects.Add(obj); } /// @@ -91,12 +99,12 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection if (captured == null) return; - capturedHitObjects.Add(captured); + selectedHitObjects.Add(captured); } private void finishCapture(bool fromDrag) { - if (capturedHitObjects.Count == 0) + if (selectedHitObjects.Count == 0) return; // Due to https://github.com/ppy/osu-framework/issues/1382, we may get here through both @@ -104,9 +112,9 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection captureBox?.Hide(); if (fromDrag) - AddInternal(captureBox = new DragCaptureBox(this, capturedHitObjects.ToList(), selectionBox.Position, selectionBox.Size)); + AddInternal(captureBox = new DragCaptureBox(this, selectedHitObjects.ToList(), selectionBox.Position, selectionBox.Size)); else - AddInternal(captureBox = new InstantCaptureBox(this, capturedHitObjects.ToList())); + AddInternal(captureBox = new InstantCaptureBox(this, selectedHitObjects.ToList())); } } } From 115484741d694dab9602548e58b7f8b3d2617541 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 14 Feb 2018 17:54:43 +0900 Subject: [PATCH 11/69] Capture -> Select --- .../Edit/Layers/Selection/CaptureBox.cs | 2 +- .../Edit/Layers/Selection/SelectionLayer.cs | 28 +++++++++---------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs index 5d1bcd1ef7..86e96f6d84 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs @@ -13,7 +13,7 @@ using OpenTK; namespace osu.Game.Rulesets.Edit.Layers.Selection { /// - /// A box which encapsulates captured s. + /// A box which encloses s. /// public abstract class CaptureBox : VisibilityContainer { diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index a335108a0f..0fceced040 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection var screenSpaceDragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); selectionBox.SetDragRectangle(screenSpaceDragQuad.AABBFloat); - captureQuad(screenSpaceDragQuad); + selectQuad(screenSpaceDragQuad); return true; } @@ -57,15 +57,15 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnDragEnd(InputState state) { selectionBox.Hide(); - finishCapture(true); + finishSelection(true); return true; } protected override bool OnClick(InputState state) { - capturePoint(state.Mouse.NativeState.Position); - finishCapture(false); + selectPoint(state.Mouse.NativeState.Position); + finishSelection(false); return true; } @@ -80,29 +80,29 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection } /// - /// Captures all hitobjects that are present within the area of a . + /// Selects all hitobjects that are present within the area of a . /// - /// The capture . - private void captureQuad(Quad screenSpaceQuad) + /// The selection . + private void selectQuad(Quad screenSpaceQuad) { foreach (var obj in playfield.HitObjects.Objects.Where(h => h.IsAlive && h.IsPresent && screenSpaceQuad.Contains(h.SelectionPoint))) selectedHitObjects.Add(obj); } /// - /// Captures the top-most hitobject that is present under a specific point. + /// Selects the top-most hitobject that is present under a specific point. /// - /// The to capture at. - private void capturePoint(Vector2 screenSpacePoint) + /// The to select at. + private void selectPoint(Vector2 screenSpacePoint) { - var captured = playfield.HitObjects.Objects.Reverse().Where(h => h.IsAlive && h.IsPresent).FirstOrDefault(h => h.ReceiveMouseInputAt(screenSpacePoint)); - if (captured == null) + var selected = playfield.HitObjects.Objects.Reverse().Where(h => h.IsAlive && h.IsPresent).FirstOrDefault(h => h.ReceiveMouseInputAt(screenSpacePoint)); + if (selected == null) return; - selectedHitObjects.Add(captured); + selectedHitObjects.Add(selected); } - private void finishCapture(bool fromDrag) + private void finishSelection(bool fromDrag) { if (selectedHitObjects.Count == 0) return; From e0d28564d0d69b4132f6dea94f1e2a162d18c2e4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 14 Feb 2018 20:26:49 +0900 Subject: [PATCH 12/69] Move import logic to shared implementation --- osu.Desktop/OsuGameDesktop.cs | 25 ++- osu.Desktop/Program.cs | 2 +- .../Beatmaps/IO/ImportBeatmapTest.cs | 8 +- .../Beatmaps/ArchiveModelImportManager.cs | 181 ++++++++++++++++ osu.Game/Beatmaps/BeatmapManager.cs | 194 ++++-------------- osu.Game/Beatmaps/BeatmapSetFileInfo.cs | 3 +- osu.Game/Beatmaps/BeatmapSetInfo.cs | 3 +- osu.Game/Beatmaps/BeatmapStore.cs | 3 +- osu.Game/Beatmaps/ICanImportArchives.cs | 9 + osu.Game/Database/INamedFileInfo.cs | 13 ++ osu.Game/IO/IAddableStore.cs | 14 ++ osu.Game/IO/IHasFiles.cs | 9 + ...CChannel.cs => ArchiveImportIPCChannel.cs} | 21 +- osu.Game/Online/API/APIDownloadRequest.cs | 30 +++ osu.Game/Online/API/APIRequest.cs | 26 --- osu.Game/osu.Game.csproj | 8 +- 16 files changed, 340 insertions(+), 209 deletions(-) create mode 100644 osu.Game/Beatmaps/ArchiveModelImportManager.cs create mode 100644 osu.Game/Beatmaps/ICanImportArchives.cs create mode 100644 osu.Game/Database/INamedFileInfo.cs create mode 100644 osu.Game/IO/IAddableStore.cs create mode 100644 osu.Game/IO/IHasFiles.cs rename osu.Game/IPC/{BeatmapIPCChannel.cs => ArchiveImportIPCChannel.cs} (57%) create mode 100644 osu.Game/Online/API/APIDownloadRequest.cs diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index f37282366a..c563201f0a 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -111,14 +111,23 @@ namespace osu.Desktop { var filePaths = new [] { e.FileName }; - if (filePaths.All(f => Path.GetExtension(f) == @".osz")) - Task.Factory.StartNew(() => BeatmapManager.Import(filePaths), TaskCreationOptions.LongRunning); - else if (filePaths.All(f => Path.GetExtension(f) == @".osr")) - Task.Run(() => - { - var score = ScoreStore.ReadReplayFile(filePaths.First()); - Schedule(() => LoadScore(score)); - }); + var firstExtension = Path.GetExtension(filePaths.First()); + + if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return; + + switch (firstExtension) + { + case ".osz": + Task.Factory.StartNew(() => BeatmapManager.Import(filePaths), TaskCreationOptions.LongRunning); + return; + case ".osr": + Task.Run(() => + { + var score = ScoreStore.ReadReplayFile(filePaths.First()); + Schedule(() => LoadScore(score)); + }); + return; + } } private static readonly string[] allowed_extensions = { @".osz", @".osr" }; diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index 9760538197..048fe93c11 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -22,7 +22,7 @@ namespace osu.Desktop { if (!host.IsPrimaryInstance) { - var importer = new BeatmapIPCChannel(host); + var importer = new ArchiveImportIPCChannel(host); // Restore the cwd so relative paths given at the command line work correctly Directory.SetCurrentDirectory(cwd); foreach (var file in args) diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index cade50a9f3..6428881b54 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -165,7 +165,7 @@ namespace osu.Game.Tests.Beatmaps.IO var temp = prepareTempCopy(osz_path); Assert.IsTrue(File.Exists(temp)); - var importer = new BeatmapIPCChannel(client); + var importer = new ArchiveImportIPCChannel(client); if (!importer.ImportAsync(temp).Wait(10000)) Assert.Fail(@"IPC took too long to send"); @@ -209,7 +209,11 @@ namespace osu.Game.Tests.Beatmaps.IO Assert.IsTrue(File.Exists(temp)); - var imported = osu.Dependencies.Get().Import(temp); + var manager = osu.Dependencies.Get(); + + manager.Import(temp); + + var imported = manager.GetAllUsableBeatmapSets(); ensureLoaded(osu); diff --git a/osu.Game/Beatmaps/ArchiveModelImportManager.cs b/osu.Game/Beatmaps/ArchiveModelImportManager.cs new file mode 100644 index 0000000000..af0cdad0a3 --- /dev/null +++ b/osu.Game/Beatmaps/ArchiveModelImportManager.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Ionic.Zip; +using osu.Framework.Logging; +using osu.Framework.Platform; +using osu.Game.Beatmaps.IO; +using osu.Game.Database; +using osu.Game.IO; +using osu.Game.IPC; +using osu.Game.Overlays.Notifications; +using FileInfo = osu.Game.IO.FileInfo; + +namespace osu.Game.Beatmaps +{ + public abstract class ArchiveModelImportManager : ICanImportArchives + where TModel : class, IHasFiles + where TFileModel : INamedFileInfo, new() + { + /// + /// Set an endpoint for notifications to be posted to. + /// + public Action PostNotification { protected get; set; } + + public virtual string[] HandledExtensions => new[] { ".zip" }; + + protected readonly FileStore Files; + + protected readonly IDatabaseContextFactory ContextFactory; + + protected readonly IAddableStore ModelStore; + + // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) + private ArchiveImportIPCChannel ipc; + + protected ArchiveModelImportManager(Storage storage, IDatabaseContextFactory contextFactory, IAddableStore modelStore, IIpcHost importHost = null) + { + ContextFactory = contextFactory; + ModelStore = modelStore; + Files = new FileStore(contextFactory, storage); + + if (importHost != null) + ipc = new ArchiveImportIPCChannel(importHost, this); + } + + /// + /// Import one or more from filesystem . + /// This will post notifications tracking progress. + /// + /// One or more beatmap locations on disk. + public void Import(params string[] paths) + { + var notification = new ProgressNotification + { + Text = "Import is initialising...", + CompletionText = "Import successful!", + Progress = 0, + State = ProgressNotificationState.Active, + }; + + PostNotification?.Invoke(notification); + + List imported = new List(); + + int i = 0; + foreach (string path in paths) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + try + { + notification.Text = $"Importing ({i} of {paths.Length})\n{Path.GetFileName(path)}"; + using (ArchiveReader reader = getReaderFrom(path)) + imported.Add(Import(reader)); + + notification.Progress = (float)++i / paths.Length; + + // We may or may not want to delete the file depending on where it is stored. + // e.g. reconstructing/repairing database with beatmaps from default storage. + // Also, not always a single file, i.e. for LegacyFilesystemReader + // TODO: Add a check to prevent files from storage to be deleted. + try + { + if (File.Exists(path)) + File.Delete(path); + } + catch (Exception e) + { + Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})"); + } + } + catch (Exception e) + { + e = e.InnerException ?? e; + Logger.Error(e, $@"Could not import beatmap set ({Path.GetFileName(path)})"); + } + } + + notification.State = ProgressNotificationState.Completed; + } + + /// + /// Import a model from an . + /// + /// The beatmap to be imported. + public TModel Import(ArchiveReader archive) + { + using (ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. + { + // create a new set info (don't yet add to database) + var model = CreateModel(archive); + + var existing = CheckForExisting(model); + + if (existing != null) return existing; + + model.Files = createFileInfos(archive, Files); + + Populate(model, archive); + + // import to store + ModelStore.Add(model); + + return model; + } + } + + /// + /// Create all required s for the provided archive, adding them to the global file store. + /// + private List createFileInfos(ArchiveReader reader, FileStore files) + { + var fileInfos = new List(); + + // import files to manager + foreach (string file in reader.Filenames) + using (Stream s = reader.GetStream(file)) + fileInfos.Add(new TFileModel + { + Filename = file, + FileInfo = files.Add(s) + }); + + return fileInfos; + } + + /// + /// Create a barebones model from the provided archive. + /// Actual expensive population should be done in ; this should just prepare for duplicate checking. + /// + /// + /// + protected abstract TModel CreateModel(ArchiveReader archive); + + /// + /// Populate the provided model completely from the given archive. + /// After this method, the model should be in a state ready to commit to a store. + /// + /// The model to populate. + /// The archive to use as a reference for population. + protected virtual void Populate(TModel model, ArchiveReader archive) + { + } + + protected virtual TModel CheckForExisting(TModel beatmapSet) => null; + + /// + /// Creates an from a valid storage path. + /// + /// A file or folder path resolving the beatmap content. + /// A reader giving access to the beatmap's content. + private ArchiveReader getReaderFrom(string path) + { + if (ZipFile.IsZipFile(path)) + return new OszArchiveReader(Files.Storage.GetStream(path)); + return new LegacyFilesystemReader(path); + } + } +} diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 47773528a6..0a7bf255c5 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Linq.Expressions; using System.Threading.Tasks; -using Ionic.Zip; using Microsoft.EntityFrameworkCore; using osu.Framework.Extensions; using osu.Framework.Logging; @@ -16,8 +15,6 @@ using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.IO; using osu.Game.Database; using osu.Game.Graphics; -using osu.Game.IO; -using osu.Game.IPC; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Notifications; @@ -28,7 +25,7 @@ namespace osu.Game.Beatmaps /// /// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps. /// - public partial class BeatmapManager + public partial class BeatmapManager : ArchiveModelImportManager { /// /// Fired when a new becomes available in the database. @@ -60,9 +57,7 @@ namespace osu.Game.Beatmaps /// public WorkingBeatmap DefaultBeatmap { private get; set; } - private readonly IDatabaseContextFactory contextFactory; - - private readonly FileStore files; + public override string[] HandledExtensions => new[] { ".osz" }; private readonly RulesetStore rulesets; @@ -72,142 +67,58 @@ namespace osu.Game.Beatmaps private readonly List currentDownloads = new List(); - // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) - private BeatmapIPCChannel ipc; - - /// - /// Set an endpoint for notifications to be posted to. - /// - public Action PostNotification { private get; set; } - /// /// Set a storage with access to an osu-stable install for import purposes. /// public Func GetStableStorage { private get; set; } public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null) + : base(storage, contextFactory, new BeatmapStore(contextFactory), importHost) { - this.contextFactory = contextFactory; - - beatmaps = new BeatmapStore(contextFactory); - + beatmaps = (BeatmapStore)ModelStore; beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s); beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s); beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); - files = new FileStore(contextFactory, storage); - this.rulesets = rulesets; this.api = api; - if (importHost != null) - ipc = new BeatmapIPCChannel(importHost, this); - beatmaps.Cleanup(); } - /// - /// Import one or more from filesystem . - /// This will post notifications tracking progress. - /// - /// One or more beatmap locations on disk. - public List Import(params string[] paths) + protected override void Populate(BeatmapSetInfo model, ArchiveReader archive) { - var notification = new ProgressNotification - { - Text = "Beatmap import is initialising...", - CompletionText = "Import successful!", - Progress = 0, - State = ProgressNotificationState.Active, - }; + model.Beatmaps = createBeatmapDifficulties(archive); - PostNotification?.Invoke(notification); - - List imported = new List(); - - int i = 0; - foreach (string path in paths) - { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return imported; - - try - { - notification.Text = $"Importing ({i} of {paths.Length})\n{Path.GetFileName(path)}"; - using (ArchiveReader reader = getReaderFrom(path)) - imported.Add(Import(reader)); - - notification.Progress = (float)++i / paths.Length; - - // We may or may not want to delete the file depending on where it is stored. - // e.g. reconstructing/repairing database with beatmaps from default storage. - // Also, not always a single file, i.e. for LegacyFilesystemReader - // TODO: Add a check to prevent files from storage to be deleted. - try - { - if (File.Exists(path)) - File.Delete(path); - } - catch (Exception e) - { - Logger.Error(e, $@"Could not delete original file after import ({Path.GetFileName(path)})"); - } - } - catch (Exception e) - { - e = e.InnerException ?? e; - Logger.Error(e, $@"Could not import beatmap set ({Path.GetFileName(path)})"); - } - } - - notification.State = ProgressNotificationState.Completed; - return imported; + // remove metadata from difficulties where it matches the set + foreach (BeatmapInfo b in model.Beatmaps) + if (model.Metadata.Equals(b.Metadata)) + b.Metadata = null; } - /// - /// Import a beatmap from an . - /// - /// The beatmap to be imported. - public BeatmapSetInfo Import(ArchiveReader archive) + protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo beatmapSet) { - using (contextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. + // check if this beatmap has already been imported and exit early if so + var existingHashMatch = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == beatmapSet.Hash); + if (existingHashMatch != null) { - // create a new set info (don't yet add to database) - var beatmapSet = createBeatmapSetInfo(archive); - - // check if this beatmap has already been imported and exit early if so - var existingHashMatch = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == beatmapSet.Hash); - if (existingHashMatch != null) - { - Undelete(existingHashMatch); - return existingHashMatch; - } - - // check if a set already exists with the same online id - if (beatmapSet.OnlineBeatmapSetID != null) - { - var existingOnlineId = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID); - if (existingOnlineId != null) - { - Delete(existingOnlineId); - beatmaps.Cleanup(s => s.ID == existingOnlineId.ID); - } - } - - beatmapSet.Files = createFileInfos(archive, files); - beatmapSet.Beatmaps = createBeatmapDifficulties(archive); - - // remove metadata from difficulties where it matches the set - foreach (BeatmapInfo b in beatmapSet.Beatmaps) - if (beatmapSet.Metadata.Equals(b.Metadata)) - b.Metadata = null; - - // import to beatmap store - Import(beatmapSet); - return beatmapSet; + Undelete(existingHashMatch); + return existingHashMatch; } + + // check if a set already exists with the same online id + if (beatmapSet.OnlineBeatmapSetID != null) + { + var existingOnlineId = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID); + if (existingOnlineId != null) + { + Delete(existingOnlineId); + beatmaps.Cleanup(s => s.ID == existingOnlineId.ID); + } + } + + return null; } /// @@ -313,7 +224,7 @@ namespace osu.Game.Beatmaps /// The beatmap set to delete. public void Delete(BeatmapSetInfo beatmapSet) { - using (var usage = contextFactory.GetForWrite()) + using (var usage = ContextFactory.GetForWrite()) { var context = usage.Context; @@ -325,7 +236,7 @@ namespace osu.Game.Beatmaps if (beatmaps.Delete(beatmapSet)) { if (!beatmapSet.Protected) - files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); + Files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); } context.ChangeTracker.AutoDetectChangesEnabled = true; @@ -376,14 +287,14 @@ namespace osu.Game.Beatmaps if (beatmapSet.Protected) return; - using (var usage = contextFactory.GetForWrite()) + using (var usage = ContextFactory.GetForWrite()) { usage.Context.ChangeTracker.AutoDetectChangesEnabled = false; if (!beatmaps.Undelete(beatmapSet)) return; if (!beatmapSet.Protected) - files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); + Files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); usage.Context.ChangeTracker.AutoDetectChangesEnabled = true; } @@ -415,7 +326,7 @@ namespace osu.Game.Beatmaps if (beatmapInfo.Metadata == null) beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata; - WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(files.Store, beatmapInfo); + WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, beatmapInfo); previous?.TransferTo(working); @@ -519,19 +430,6 @@ namespace osu.Game.Beatmaps notification.State = ProgressNotificationState.Completed; } - /// - /// Creates an from a valid storage path. - /// - /// A file or folder path resolving the beatmap content. - /// A reader giving access to the beatmap's content. - private ArchiveReader getReaderFrom(string path) - { - if (ZipFile.IsZipFile(path)) - // ReSharper disable once InconsistentlySynchronizedField - return new OszArchiveReader(files.Storage.GetStream(path)); - return new LegacyFilesystemReader(path); - } - /// /// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content. /// @@ -546,10 +444,7 @@ namespace osu.Game.Beatmaps return hashable.ComputeSHA2Hash(); } - /// - /// Create a from a provided archive. - /// - private BeatmapSetInfo createBeatmapSetInfo(ArchiveReader reader) + protected override BeatmapSetInfo CreateModel(ArchiveReader reader) { // let's make sure there are actually .osu files to import. string mapName = reader.Filenames.FirstOrDefault(f => f.EndsWith(".osu")); @@ -568,25 +463,6 @@ namespace osu.Game.Beatmaps }; } - /// - /// Create all required s for the provided archive, adding them to the global file store. - /// - private List createFileInfos(ArchiveReader reader, FileStore files) - { - List fileInfos = new List(); - - // import files to manager - foreach (string file in reader.Filenames) - using (Stream s = reader.GetStream(file)) - fileInfos.Add(new BeatmapSetFileInfo - { - Filename = file, - FileInfo = files.Add(s) - }); - - return fileInfos; - } - /// /// Create all required s for the provided archive. /// diff --git a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs index ae4a6772a2..e88af6ed30 100644 --- a/osu.Game/Beatmaps/BeatmapSetFileInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetFileInfo.cs @@ -3,11 +3,12 @@ using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using osu.Game.Database; using osu.Game.IO; namespace osu.Game.Beatmaps { - public class BeatmapSetFileInfo + public class BeatmapSetFileInfo : INamedFileInfo { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 982e41c92c..0566807179 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -5,10 +5,11 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using osu.Game.Database; +using osu.Game.IO; namespace osu.Game.Beatmaps { - public class BeatmapSetInfo : IHasPrimaryKey + public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 29373c0715..8bc2dd8b13 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -6,13 +6,14 @@ using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore; using osu.Game.Database; +using osu.Game.IO; namespace osu.Game.Beatmaps { /// /// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing /// - public class BeatmapStore : DatabaseBackedStore + public class BeatmapStore : DatabaseBackedStore, IAddableStore { public event Action BeatmapSetAdded; public event Action BeatmapSetRemoved; diff --git a/osu.Game/Beatmaps/ICanImportArchives.cs b/osu.Game/Beatmaps/ICanImportArchives.cs new file mode 100644 index 0000000000..246c5d04b2 --- /dev/null +++ b/osu.Game/Beatmaps/ICanImportArchives.cs @@ -0,0 +1,9 @@ +namespace osu.Game.Beatmaps +{ + public interface ICanImportArchives + { + void Import(params string[] paths); + + string[] HandledExtensions { get; } + } +} diff --git a/osu.Game/Database/INamedFileInfo.cs b/osu.Game/Database/INamedFileInfo.cs new file mode 100644 index 0000000000..7922c72974 --- /dev/null +++ b/osu.Game/Database/INamedFileInfo.cs @@ -0,0 +1,13 @@ +using osu.Game.IO; + +namespace osu.Game.Database +{ + /// + /// Represent a join model which gives a filename and scope to a . + /// + public interface INamedFileInfo + { + FileInfo FileInfo { get; set; } + string Filename { get; set; } + } +} diff --git a/osu.Game/IO/IAddableStore.cs b/osu.Game/IO/IAddableStore.cs new file mode 100644 index 0000000000..2452dda3b4 --- /dev/null +++ b/osu.Game/IO/IAddableStore.cs @@ -0,0 +1,14 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.IO +{ + public interface IAddableStore + { + /// + /// Add an object to the store. + /// + /// The object to add. + void Add(T item); + } +} diff --git a/osu.Game/IO/IHasFiles.cs b/osu.Game/IO/IHasFiles.cs new file mode 100644 index 0000000000..df313b4eae --- /dev/null +++ b/osu.Game/IO/IHasFiles.cs @@ -0,0 +1,9 @@ +using System.Collections.Generic; + +namespace osu.Game.IO +{ + public interface IHasFiles + { + List Files { get; set; } + } +} diff --git a/osu.Game/IPC/BeatmapIPCChannel.cs b/osu.Game/IPC/ArchiveImportIPCChannel.cs similarity index 57% rename from osu.Game/IPC/BeatmapIPCChannel.cs rename to osu.Game/IPC/ArchiveImportIPCChannel.cs index 64e5d526e6..a5859e56a4 100644 --- a/osu.Game/IPC/BeatmapIPCChannel.cs +++ b/osu.Game/IPC/ArchiveImportIPCChannel.cs @@ -2,23 +2,25 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Diagnostics; +using System.IO; +using System.Linq; using System.Threading.Tasks; using osu.Framework.Platform; using osu.Game.Beatmaps; namespace osu.Game.IPC { - public class BeatmapIPCChannel : IpcChannel + public class ArchiveImportIPCChannel : IpcChannel { - private readonly BeatmapManager beatmaps; + private readonly ICanImportArchives importer; - public BeatmapIPCChannel(IIpcHost host, BeatmapManager beatmaps = null) + public ArchiveImportIPCChannel(IIpcHost host, ICanImportArchives importer = null) : base(host) { - this.beatmaps = beatmaps; + this.importer = importer; MessageReceived += msg => { - Debug.Assert(beatmaps != null); + Debug.Assert(importer != null); ImportAsync(msg.Path).ContinueWith(t => { if (t.Exception != null) throw t.Exception; @@ -28,18 +30,19 @@ namespace osu.Game.IPC public async Task ImportAsync(string path) { - if (beatmaps == null) + if (importer == null) { //we want to contact a remote osu! to handle the import. - await SendMessageAsync(new BeatmapImportMessage { Path = path }); + await SendMessageAsync(new ArchiveImportMessage { Path = path }); return; } - beatmaps.Import(path); + if (importer.HandledExtensions.Contains(Path.GetExtension(path))) + importer.Import(path); } } - public class BeatmapImportMessage + public class ArchiveImportMessage { public string Path; } diff --git a/osu.Game/Online/API/APIDownloadRequest.cs b/osu.Game/Online/API/APIDownloadRequest.cs new file mode 100644 index 0000000000..f1cbd1eb0b --- /dev/null +++ b/osu.Game/Online/API/APIDownloadRequest.cs @@ -0,0 +1,30 @@ +using osu.Framework.IO.Network; + +namespace osu.Game.Online.API +{ + public abstract class APIDownloadRequest : APIRequest + { + protected override WebRequest CreateWebRequest() + { + var request = new WebRequest(Uri); + request.DownloadProgress += request_Progress; + return request; + } + + private void request_Progress(long current, long total) => API.Scheduler.Add(delegate { Progress?.Invoke(current, total); }); + + protected APIDownloadRequest() + { + base.Success += onSuccess; + } + + private void onSuccess() + { + Success?.Invoke(WebRequest.ResponseData); + } + + public event APIProgressHandler Progress; + + public new event APISuccessHandler Success; + } +} \ No newline at end of file diff --git a/osu.Game/Online/API/APIRequest.cs b/osu.Game/Online/API/APIRequest.cs index ce6f3c7c7d..35af8eefd7 100644 --- a/osu.Game/Online/API/APIRequest.cs +++ b/osu.Game/Online/API/APIRequest.cs @@ -27,32 +27,6 @@ namespace osu.Game.Online.API public new event APISuccessHandler Success; } - public abstract class APIDownloadRequest : APIRequest - { - protected override WebRequest CreateWebRequest() - { - var request = new WebRequest(Uri); - request.DownloadProgress += request_Progress; - return request; - } - - private void request_Progress(long current, long total) => API.Scheduler.Add(delegate { Progress?.Invoke(current, total); }); - - protected APIDownloadRequest() - { - base.Success += onSuccess; - } - - private void onSuccess() - { - Success?.Invoke(WebRequest.ResponseData); - } - - public event APIProgressHandler Progress; - - public new event APISuccessHandler Success; - } - /// /// AN API request with no specified response type. /// diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 02801eb81f..189886f5d1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -243,6 +243,7 @@ + @@ -270,6 +271,7 @@ + @@ -278,9 +280,13 @@ + + + + @@ -470,7 +476,7 @@ - + From d8f84fcca3a40ccbb652b7fa01145f877aec0ae1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 10:20:23 +0900 Subject: [PATCH 13/69] Give ArchiveReader a filename --- osu.Game/Beatmaps/ArchiveModelImportManager.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 2 +- osu.Game/Beatmaps/IO/ArchiveReader.cs | 10 ++++++++++ osu.Game/Beatmaps/IO/LegacyFilesystemReader.cs | 2 +- osu.Game/Beatmaps/IO/OszArchiveReader.cs | 3 ++- osu.Game/Screens/Menu/Intro.cs | 2 +- 6 files changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Beatmaps/ArchiveModelImportManager.cs b/osu.Game/Beatmaps/ArchiveModelImportManager.cs index af0cdad0a3..beb5f47ad2 100644 --- a/osu.Game/Beatmaps/ArchiveModelImportManager.cs +++ b/osu.Game/Beatmaps/ArchiveModelImportManager.cs @@ -174,7 +174,7 @@ namespace osu.Game.Beatmaps private ArchiveReader getReaderFrom(string path) { if (ZipFile.IsZipFile(path)) - return new OszArchiveReader(Files.Storage.GetStream(path)); + return new OszArchiveReader(Files.Storage.GetStream(path), Path.GetFileName(path)); return new LegacyFilesystemReader(path); } } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 0a7bf255c5..3821d16103 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -171,7 +171,7 @@ namespace osu.Game.Beatmaps { // This gets scheduled back to the update thread, but we want the import to run in the background. using (var stream = new MemoryStream(data)) - using (var archive = new OszArchiveReader(stream)) + using (var archive = new OszArchiveReader(stream, beatmapSetInfo.ToString())) Import(archive); downloadNotification.State = ProgressNotificationState.Completed; diff --git a/osu.Game/Beatmaps/IO/ArchiveReader.cs b/osu.Game/Beatmaps/IO/ArchiveReader.cs index 453a03b882..7be03ffb1b 100644 --- a/osu.Game/Beatmaps/IO/ArchiveReader.cs +++ b/osu.Game/Beatmaps/IO/ArchiveReader.cs @@ -17,6 +17,16 @@ namespace osu.Game.Beatmaps.IO public abstract void Dispose(); + /// + /// The name of this archive (usually the containing filename). + /// + public readonly string Name; + + protected ArchiveReader(string name) + { + Name = name; + } + public abstract IEnumerable Filenames { get; } public virtual byte[] Get(string name) diff --git a/osu.Game/Beatmaps/IO/LegacyFilesystemReader.cs b/osu.Game/Beatmaps/IO/LegacyFilesystemReader.cs index 4a85f6f526..e0a54838e0 100644 --- a/osu.Game/Beatmaps/IO/LegacyFilesystemReader.cs +++ b/osu.Game/Beatmaps/IO/LegacyFilesystemReader.cs @@ -15,7 +15,7 @@ namespace osu.Game.Beatmaps.IO { private readonly string path; - public LegacyFilesystemReader(string path) + public LegacyFilesystemReader(string path) : base(Path.GetFileName(path)) { this.path = path; } diff --git a/osu.Game/Beatmaps/IO/OszArchiveReader.cs b/osu.Game/Beatmaps/IO/OszArchiveReader.cs index e5c971889b..fbac5d79f3 100644 --- a/osu.Game/Beatmaps/IO/OszArchiveReader.cs +++ b/osu.Game/Beatmaps/IO/OszArchiveReader.cs @@ -13,7 +13,8 @@ namespace osu.Game.Beatmaps.IO private readonly Stream archiveStream; private readonly ZipFile archive; - public OszArchiveReader(Stream archiveStream) + public OszArchiveReader(Stream archiveStream, string name = null) + : base(name) { this.archiveStream = archiveStream; archive = ZipFile.Read(archiveStream); diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index 10b08d704d..3298827d25 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Menu if (setInfo == null) { // we need to import the default menu background beatmap - setInfo = beatmaps.Import(new OszArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz"))); + setInfo = beatmaps.Import(new OszArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz"), "circles.osz")); setInfo.Protected = true; } } From 6ff63c2f0c8455e9c0d699a9a3ee93ec8944d459 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 12:21:11 +0900 Subject: [PATCH 14/69] Move deletion to ArchiveModelImportManager --- osu.Game/Beatmaps/BeatmapManager.cs | 36 +++------------ osu.Game/Beatmaps/BeatmapSetInfo.cs | 2 +- osu.Game/Beatmaps/BeatmapStore.cs | 24 +++++----- .../ArchiveModelImportManager.cs | 45 +++++++++++++++---- .../ICanImportArchives.cs | 2 +- osu.Game/Database/ISoftDelete.cs | 10 +++++ .../IO/{IAddableStore.cs => IMutableStore.cs} | 4 +- osu.Game/IPC/ArchiveImportIPCChannel.cs | 2 +- osu.Game/Screens/Menu/Intro.cs | 5 +-- osu.Game/osu.Game.csproj | 7 +-- 10 files changed, 76 insertions(+), 61 deletions(-) rename osu.Game/{Beatmaps => Database}/ArchiveModelImportManager.cs (79%) rename osu.Game/{Beatmaps => Database}/ICanImportArchives.cs (78%) create mode 100644 osu.Game/Database/ISoftDelete.cs rename osu.Game/IO/{IAddableStore.cs => IMutableStore.cs} (80%) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 3821d16103..802993bc58 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -97,10 +97,10 @@ namespace osu.Game.Beatmaps b.Metadata = null; } - protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo beatmapSet) + protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model) { // check if this beatmap has already been imported and exit early if so - var existingHashMatch = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == beatmapSet.Hash); + var existingHashMatch = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == model.Hash); if (existingHashMatch != null) { Undelete(existingHashMatch); @@ -108,9 +108,9 @@ namespace osu.Game.Beatmaps } // check if a set already exists with the same online id - if (beatmapSet.OnlineBeatmapSetID != null) + if (model.OnlineBeatmapSetID != null) { - var existingOnlineId = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == beatmapSet.OnlineBeatmapSetID); + var existingOnlineId = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID); if (existingOnlineId != null) { Delete(existingOnlineId); @@ -217,32 +217,6 @@ namespace osu.Game.Beatmaps /// The beatmap set to update. public void Update(BeatmapSetInfo beatmap) => beatmaps.Update(beatmap); - /// - /// Delete a beatmap from the manager. - /// Is a no-op for already deleted beatmaps. - /// - /// The beatmap set to delete. - public void Delete(BeatmapSetInfo beatmapSet) - { - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - context.ChangeTracker.AutoDetectChangesEnabled = false; - - // re-fetch the beatmap set on the import context. - beatmapSet = context.BeatmapSetInfo.Include(s => s.Files).ThenInclude(f => f.FileInfo).First(s => s.ID == beatmapSet.ID); - - if (beatmaps.Delete(beatmapSet)) - { - if (!beatmapSet.Protected) - Files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); - } - - context.ChangeTracker.AutoDetectChangesEnabled = true; - } - } - /// /// Restore all beatmaps that were previously deleted. /// This will post notifications tracking progress. @@ -351,7 +325,7 @@ namespace osu.Game.Beatmaps /// Returns a list of all usable s. /// /// A list of available . - public List GetAllUsableBeatmapSets() => beatmaps.BeatmapSets.Where(s => !s.DeletePending).ToList(); + public List GetAllUsableBeatmapSets() => beatmaps.BeatmapSets.Where(s => !s.DeletePending && !s.Protected).ToList(); /// /// Perform a lookup query on available s. diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 0566807179..79983becb0 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -9,7 +9,7 @@ using osu.Game.IO; namespace osu.Game.Beatmaps { - public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles + public class BeatmapSetInfo : IHasPrimaryKey, IHasFiles, ISoftDelete { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 8bc2dd8b13..330b5db853 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps /// /// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing /// - public class BeatmapStore : DatabaseBackedStore, IAddableStore + public class BeatmapStore : DatabaseBackedStore, IMutableStore { public event Action BeatmapSetAdded; public event Action BeatmapSetRemoved; @@ -79,7 +79,7 @@ namespace osu.Game.Beatmaps { Refresh(ref beatmapSet, BeatmapSets); - if (beatmapSet.DeletePending) return false; + if (beatmapSet.Protected || beatmapSet.DeletePending) return false; beatmapSet.DeletePending = true; } @@ -178,17 +178,17 @@ namespace osu.Game.Beatmaps } public IQueryable BeatmapSets => ContextFactory.Get().BeatmapSetInfo - .Include(s => s.Metadata) - .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) - .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) - .Include(s => s.Files).ThenInclude(f => f.FileInfo); + .Include(s => s.Metadata) + .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) + .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) + .Include(s => s.Files).ThenInclude(f => f.FileInfo); public IQueryable Beatmaps => ContextFactory.Get().BeatmapInfo - .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) - .Include(b => b.BeatmapSet).ThenInclude(s => s.Files).ThenInclude(f => f.FileInfo) - .Include(b => b.Metadata) - .Include(b => b.Ruleset) - .Include(b => b.BaseDifficulty); + .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) + .Include(b => b.BeatmapSet).ThenInclude(s => s.Files).ThenInclude(f => f.FileInfo) + .Include(b => b.Metadata) + .Include(b => b.Ruleset) + .Include(b => b.BaseDifficulty); } } diff --git a/osu.Game/Beatmaps/ArchiveModelImportManager.cs b/osu.Game/Database/ArchiveModelImportManager.cs similarity index 79% rename from osu.Game/Beatmaps/ArchiveModelImportManager.cs rename to osu.Game/Database/ArchiveModelImportManager.cs index beb5f47ad2..6b780a2866 100644 --- a/osu.Game/Beatmaps/ArchiveModelImportManager.cs +++ b/osu.Game/Database/ArchiveModelImportManager.cs @@ -1,20 +1,22 @@ using System; using System.Collections.Generic; using System.IO; +using System.Linq; using Ionic.Zip; +using Microsoft.EntityFrameworkCore; using osu.Framework.Logging; using osu.Framework.Platform; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.IO; -using osu.Game.Database; using osu.Game.IO; using osu.Game.IPC; using osu.Game.Overlays.Notifications; using FileInfo = osu.Game.IO.FileInfo; -namespace osu.Game.Beatmaps +namespace osu.Game.Database { public abstract class ArchiveModelImportManager : ICanImportArchives - where TModel : class, IHasFiles + where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete where TFileModel : INamedFileInfo, new() { /// @@ -28,12 +30,12 @@ namespace osu.Game.Beatmaps protected readonly IDatabaseContextFactory ContextFactory; - protected readonly IAddableStore ModelStore; + protected readonly IMutableStore ModelStore; // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ArchiveImportIPCChannel ipc; - protected ArchiveModelImportManager(Storage storage, IDatabaseContextFactory contextFactory, IAddableStore modelStore, IIpcHost importHost = null) + protected ArchiveModelImportManager(Storage storage, IDatabaseContextFactory contextFactory, IMutableStore modelStore, IIpcHost importHost = null) { ContextFactory = contextFactory; ModelStore = modelStore; @@ -127,6 +129,31 @@ namespace osu.Game.Beatmaps } } + /// + /// Delete a model from the manager. + /// Is a no-op for already deleted models. + /// + /// The model to delete. + public void Delete(TModel model) + { + using (var usage = ContextFactory.GetForWrite()) + { + var context = usage.Context; + + context.ChangeTracker.AutoDetectChangesEnabled = false; + + // re-fetch the model on the import context. + var foundModel = ContextFactory.Get().Set().Include(s => s.Files).ThenInclude(f => f.FileInfo).First(s => s.ID == model.ID); + + if (foundModel.DeletePending || !CheckCanDelete(foundModel)) return; + + if (ModelStore.Delete(foundModel)) + Files.Dereference(foundModel.Files.Select(f => f.FileInfo).ToArray()); + + context.ChangeTracker.AutoDetectChangesEnabled = true; + } + } + /// /// Create all required s for the provided archive, adding them to the global file store. /// @@ -164,13 +191,15 @@ namespace osu.Game.Beatmaps { } - protected virtual TModel CheckForExisting(TModel beatmapSet) => null; + protected virtual TModel CheckForExisting(TModel model) => null; + + protected virtual bool CheckCanDelete(TModel model) => true; /// /// Creates an from a valid storage path. /// - /// A file or folder path resolving the beatmap content. - /// A reader giving access to the beatmap's content. + /// A file or folder path resolving the archive content. + /// A reader giving access to the archive's content. private ArchiveReader getReaderFrom(string path) { if (ZipFile.IsZipFile(path)) diff --git a/osu.Game/Beatmaps/ICanImportArchives.cs b/osu.Game/Database/ICanImportArchives.cs similarity index 78% rename from osu.Game/Beatmaps/ICanImportArchives.cs rename to osu.Game/Database/ICanImportArchives.cs index 246c5d04b2..0f863f3044 100644 --- a/osu.Game/Beatmaps/ICanImportArchives.cs +++ b/osu.Game/Database/ICanImportArchives.cs @@ -1,4 +1,4 @@ -namespace osu.Game.Beatmaps +namespace osu.Game.Database { public interface ICanImportArchives { diff --git a/osu.Game/Database/ISoftDelete.cs b/osu.Game/Database/ISoftDelete.cs new file mode 100644 index 0000000000..19510421c4 --- /dev/null +++ b/osu.Game/Database/ISoftDelete.cs @@ -0,0 +1,10 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Database +{ + public interface ISoftDelete + { + bool DeletePending { get; set; } + } +} diff --git a/osu.Game/IO/IAddableStore.cs b/osu.Game/IO/IMutableStore.cs similarity index 80% rename from osu.Game/IO/IAddableStore.cs rename to osu.Game/IO/IMutableStore.cs index 2452dda3b4..ced1b29316 100644 --- a/osu.Game/IO/IAddableStore.cs +++ b/osu.Game/IO/IMutableStore.cs @@ -3,12 +3,14 @@ namespace osu.Game.IO { - public interface IAddableStore + public interface IMutableStore { /// /// Add an object to the store. /// /// The object to add. void Add(T item); + + bool Delete(T item); } } diff --git a/osu.Game/IPC/ArchiveImportIPCChannel.cs b/osu.Game/IPC/ArchiveImportIPCChannel.cs index a5859e56a4..6e9787ca5a 100644 --- a/osu.Game/IPC/ArchiveImportIPCChannel.cs +++ b/osu.Game/IPC/ArchiveImportIPCChannel.cs @@ -6,7 +6,7 @@ using System.IO; using System.Linq; using System.Threading.Tasks; using osu.Framework.Platform; -using osu.Game.Beatmaps; +using osu.Game.Database; namespace osu.Game.IPC { diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index 3298827d25..e0467d8f84 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -63,7 +63,9 @@ namespace osu.Game.Screens.Menu { // we need to import the default menu background beatmap setInfo = beatmaps.Import(new OszArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz"), "circles.osz")); + setInfo.Protected = true; + beatmaps.Update(setInfo); } } @@ -73,9 +75,6 @@ namespace osu.Game.Screens.Menu welcome = audio.Sample.Get(@"welcome"); seeya = audio.Sample.Get(@"seeya"); - - if (setInfo.Protected) - beatmaps.Delete(setInfo); } protected override void OnEntering(Screen last) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 189886f5d1..af2b1dfae5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -243,7 +243,6 @@ - @@ -271,20 +270,22 @@ - + + + - + From d340509b1d97fee381ab21f9cf47cc31a762934c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 12:56:22 +0900 Subject: [PATCH 15/69] Move ArchiveReaders to a more global namespace Also moves delete and action logic to a shared implementation --- .../Beatmaps/IO/OszArchiveReaderTest.cs | 8 +- osu.Game/Beatmaps/BeatmapManager.cs | 81 +---------- osu.Game/Beatmaps/BeatmapStore.cs | 88 +----------- ...mportManager.cs => ArchiveModelManager.cs} | 130 ++++++++++++++---- .../Database/MutableDatabaseBackedStore.cs | 76 ++++++++++ .../IO => IO/Archives}/ArchiveReader.cs | 2 +- .../Archives}/LegacyFilesystemReader.cs | 4 +- .../Archives/ZipArchiveReader.cs} | 6 +- osu.Game/IO/IMutableStore.cs | 16 --- osu.Game/Overlays/BeatmapSet/Header.cs | 4 +- osu.Game/Overlays/DirectOverlay.cs | 2 +- osu.Game/Overlays/Music/PlaylistOverlay.cs | 4 +- osu.Game/Screens/Menu/Intro.cs | 4 +- osu.Game/Screens/Select/SongSelect.cs | 8 +- osu.Game/osu.Game.csproj | 10 +- 15 files changed, 210 insertions(+), 233 deletions(-) rename osu.Game/Database/{ArchiveModelImportManager.cs => ArchiveModelManager.cs} (57%) create mode 100644 osu.Game/Database/MutableDatabaseBackedStore.cs rename osu.Game/{Beatmaps/IO => IO/Archives}/ArchiveReader.cs (94%) rename osu.Game/{Beatmaps/IO => IO/Archives}/LegacyFilesystemReader.cs (93%) rename osu.Game/{Beatmaps/IO/OszArchiveReader.cs => IO/Archives/ZipArchiveReader.cs} (86%) delete mode 100644 osu.Game/IO/IMutableStore.cs diff --git a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs index 44eb385e22..7a1c6d9b89 100644 --- a/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/OszArchiveReaderTest.cs @@ -5,9 +5,9 @@ using System.IO; using System.Linq; using NUnit.Framework; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.IO; using osu.Game.Tests.Resources; using osu.Game.Beatmaps.Formats; +using osu.Game.IO.Archives; namespace osu.Game.Tests.Beatmaps.IO { @@ -19,7 +19,7 @@ namespace osu.Game.Tests.Beatmaps.IO { using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) { - var reader = new OszArchiveReader(osz); + var reader = new ZipArchiveReader(osz); string[] expected = { "Soleily - Renatus (Deif) [Platter].osu", @@ -46,7 +46,7 @@ namespace osu.Game.Tests.Beatmaps.IO { using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) { - var reader = new OszArchiveReader(osz); + var reader = new ZipArchiveReader(osz); BeatmapMetadata meta; using (var stream = new StreamReader(reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) @@ -71,7 +71,7 @@ namespace osu.Game.Tests.Beatmaps.IO { using (var osz = Resource.OpenResource("Beatmaps.241526 Soleily - Renatus.osz")) { - var reader = new OszArchiveReader(osz); + var reader = new ZipArchiveReader(osz); using (var stream = new StreamReader( reader.GetStream("Soleily - Renatus (Deif) [Platter].osu"))) { diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 802993bc58..8bc1f72c1f 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -12,9 +12,9 @@ using osu.Framework.Extensions; using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Beatmaps.Formats; -using osu.Game.Beatmaps.IO; using osu.Game.Database; using osu.Game.Graphics; +using osu.Game.IO.Archives; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Notifications; @@ -25,23 +25,13 @@ namespace osu.Game.Beatmaps /// /// Handles the storage and retrieval of Beatmaps/WorkingBeatmaps. /// - public partial class BeatmapManager : ArchiveModelImportManager + public partial class BeatmapManager : ArchiveModelManager { - /// - /// Fired when a new becomes available in the database. - /// - public event Action BeatmapSetAdded; - /// /// Fired when a single difficulty has been hidden. /// public event Action BeatmapHidden; - /// - /// Fired when a is removed from the database. - /// - public event Action BeatmapSetRemoved; - /// /// Fired when a single difficulty has been restored. /// @@ -76,8 +66,6 @@ namespace osu.Game.Beatmaps : base(storage, contextFactory, new BeatmapStore(contextFactory), importHost) { beatmaps = (BeatmapStore)ModelStore; - beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s); - beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s); beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b); beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b); @@ -121,12 +109,6 @@ namespace osu.Game.Beatmaps return null; } - /// - /// Import a beatmap from a . - /// - /// The beatmap to be imported. - public void Import(BeatmapSetInfo beatmapSet) => beatmaps.Add(beatmapSet); - /// /// Downloads a beatmap. /// This will post notifications tracking progress. @@ -171,7 +153,7 @@ namespace osu.Game.Beatmaps { // This gets scheduled back to the update thread, but we want the import to run in the background. using (var stream = new MemoryStream(data)) - using (var archive = new OszArchiveReader(stream, beatmapSetInfo.ToString())) + using (var archive = new ZipArchiveReader(stream, beatmapSetInfo.ToString())) Import(archive); downloadNotification.State = ProgressNotificationState.Completed; @@ -217,63 +199,6 @@ namespace osu.Game.Beatmaps /// The beatmap set to update. public void Update(BeatmapSetInfo beatmap) => beatmaps.Update(beatmap); - /// - /// Restore all beatmaps that were previously deleted. - /// This will post notifications tracking progress. - /// - public void UndeleteAll() - { - var deleteMaps = QueryBeatmapSets(bs => bs.DeletePending).ToList(); - - if (!deleteMaps.Any()) return; - - var notification = new ProgressNotification - { - CompletionText = "Restored all deleted beatmaps!", - Progress = 0, - State = ProgressNotificationState.Active, - }; - - PostNotification?.Invoke(notification); - - int i = 0; - - foreach (var bs in deleteMaps) - { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; - - notification.Text = $"Restoring ({i} of {deleteMaps.Count})"; - notification.Progress = (float)++i / deleteMaps.Count; - Undelete(bs); - } - - notification.State = ProgressNotificationState.Completed; - } - - /// - /// Restore a beatmap that was previously deleted. Is a no-op if the beatmap is not in a deleted state, or has its protected flag set. - /// - /// The beatmap to restore - public void Undelete(BeatmapSetInfo beatmapSet) - { - if (beatmapSet.Protected) - return; - - using (var usage = ContextFactory.GetForWrite()) - { - usage.Context.ChangeTracker.AutoDetectChangesEnabled = false; - - if (!beatmaps.Undelete(beatmapSet)) return; - - if (!beatmapSet.Protected) - Files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray()); - - usage.Context.ChangeTracker.AutoDetectChangesEnabled = true; - } - } - /// /// Delete a beatmap difficulty. /// diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 330b5db853..3e4840f4e1 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -6,18 +6,14 @@ using System.Linq; using System.Linq.Expressions; using Microsoft.EntityFrameworkCore; using osu.Game.Database; -using osu.Game.IO; namespace osu.Game.Beatmaps { /// /// Handles the storage and retrieval of Beatmaps/BeatmapSets to the database backing /// - public class BeatmapStore : DatabaseBackedStore, IMutableStore + public class BeatmapStore : MutableDatabaseBackedStore { - public event Action BeatmapSetAdded; - public event Action BeatmapSetRemoved; - public event Action BeatmapHidden; public event Action BeatmapRestored; @@ -26,88 +22,6 @@ namespace osu.Game.Beatmaps { } - /// - /// Add a to the database. - /// - /// The beatmap to add. - public void Add(BeatmapSetInfo beatmapSet) - { - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; - - foreach (var beatmap in beatmapSet.Beatmaps.Where(b => b.Metadata != null)) - { - // If we detect a new metadata object it'll be attached to the current context so it can be reused - // to prevent duplicate entries when persisting. To accomplish this we look in the cache (.Local) - // of the corresponding table (.Set()) for matching entries to our criteria. - var contextMetadata = context.Set().Local.SingleOrDefault(e => e.Equals(beatmap.Metadata)); - if (contextMetadata != null) - beatmap.Metadata = contextMetadata; - else - context.BeatmapMetadata.Attach(beatmap.Metadata); - } - - context.BeatmapSetInfo.Attach(beatmapSet); - - BeatmapSetAdded?.Invoke(beatmapSet); - } - } - - /// - /// Update a in the database. TODO: This only supports very basic updates currently. - /// - /// The beatmap to update. - public void Update(BeatmapSetInfo beatmapSet) - { - BeatmapSetRemoved?.Invoke(beatmapSet); - - using (var usage = ContextFactory.GetForWrite()) - usage.Context.BeatmapSetInfo.Update(beatmapSet); - - BeatmapSetAdded?.Invoke(beatmapSet); - } - - /// - /// Delete a from the database. - /// - /// The beatmap to delete. - /// Whether the beatmap's was changed. - public bool Delete(BeatmapSetInfo beatmapSet) - { - using (ContextFactory.GetForWrite()) - { - Refresh(ref beatmapSet, BeatmapSets); - - if (beatmapSet.Protected || beatmapSet.DeletePending) return false; - - beatmapSet.DeletePending = true; - } - - BeatmapSetRemoved?.Invoke(beatmapSet); - return true; - } - - /// - /// Restore a previously deleted . - /// - /// The beatmap to restore. - /// Whether the beatmap's was changed. - public bool Undelete(BeatmapSetInfo beatmapSet) - { - using (ContextFactory.GetForWrite()) - { - Refresh(ref beatmapSet, BeatmapSets); - - if (!beatmapSet.DeletePending) return false; - - beatmapSet.DeletePending = false; - } - - BeatmapSetAdded?.Invoke(beatmapSet); - return true; - } - /// /// Hide a in the database. /// diff --git a/osu.Game/Database/ArchiveModelImportManager.cs b/osu.Game/Database/ArchiveModelManager.cs similarity index 57% rename from osu.Game/Database/ArchiveModelImportManager.cs rename to osu.Game/Database/ArchiveModelManager.cs index 6b780a2866..9c558a6c12 100644 --- a/osu.Game/Database/ArchiveModelImportManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -6,16 +6,21 @@ using Ionic.Zip; using Microsoft.EntityFrameworkCore; using osu.Framework.Logging; using osu.Framework.Platform; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.IO; using osu.Game.IO; +using osu.Game.IO.Archives; using osu.Game.IPC; using osu.Game.Overlays.Notifications; using FileInfo = osu.Game.IO.FileInfo; namespace osu.Game.Database { - public abstract class ArchiveModelImportManager : ICanImportArchives + /// + /// Encapsulates a model store class to give it import functionality. + /// Adds cross-functionality with to give access to the central file store for the provided model. + /// + /// The model type. + /// The associated file join type. + public abstract class ArchiveModelManager : ICanImportArchives where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete where TFileModel : INamedFileInfo, new() { @@ -24,21 +29,35 @@ namespace osu.Game.Database /// public Action PostNotification { protected get; set; } + /// + /// Fired when a new becomes available in the database. + /// + public event Action ItemAdded; + + /// + /// Fired when a is removed from the database. + /// + public event Action ItemRemoved; + public virtual string[] HandledExtensions => new[] { ".zip" }; protected readonly FileStore Files; protected readonly IDatabaseContextFactory ContextFactory; - protected readonly IMutableStore ModelStore; + protected readonly MutableDatabaseBackedStore ModelStore; // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ArchiveImportIPCChannel ipc; - protected ArchiveModelImportManager(Storage storage, IDatabaseContextFactory contextFactory, IMutableStore modelStore, IIpcHost importHost = null) + protected ArchiveModelManager(Storage storage, IDatabaseContextFactory contextFactory, MutableDatabaseBackedStore modelStore, IIpcHost importHost = null) { ContextFactory = contextFactory; + ModelStore = modelStore; + ModelStore.ItemAdded += s => ItemAdded?.Invoke(s); + ModelStore.ItemRemoved += s => ItemRemoved?.Invoke(s); + Files = new FileStore(contextFactory, storage); if (importHost != null) @@ -46,10 +65,10 @@ namespace osu.Game.Database } /// - /// Import one or more from filesystem . + /// Import one or more items from filesystem . /// This will post notifications tracking progress. /// - /// One or more beatmap locations on disk. + /// One or more archive locations on disk. public void Import(params string[] paths) { var notification = new ProgressNotification @@ -80,7 +99,7 @@ namespace osu.Game.Database notification.Progress = (float)++i / paths.Length; // We may or may not want to delete the file depending on where it is stored. - // e.g. reconstructing/repairing database with beatmaps from default storage. + // e.g. reconstructing/repairing database with items from default storage. // Also, not always a single file, i.e. for LegacyFilesystemReader // TODO: Add a check to prevent files from storage to be deleted. try @@ -96,7 +115,7 @@ namespace osu.Game.Database catch (Exception e) { e = e.InnerException ?? e; - Logger.Error(e, $@"Could not import beatmap set ({Path.GetFileName(path)})"); + Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})"); } } @@ -104,37 +123,43 @@ namespace osu.Game.Database } /// - /// Import a model from an . + /// Import an item from an . /// - /// The beatmap to be imported. + /// The archive to be imported. public TModel Import(ArchiveReader archive) { using (ContextFactory.GetForWrite()) // used to share a context for full import. keep in mind this will block all writes. { - // create a new set info (don't yet add to database) - var model = CreateModel(archive); + // create a new model (don't yet add to database) + var item = CreateModel(archive); - var existing = CheckForExisting(model); + var existing = CheckForExisting(item); if (existing != null) return existing; - model.Files = createFileInfos(archive, Files); + item.Files = createFileInfos(archive, Files); - Populate(model, archive); + Populate(item, archive); // import to store - ModelStore.Add(model); + ModelStore.Add(item); - return model; + return item; } } /// - /// Delete a model from the manager. - /// Is a no-op for already deleted models. + /// Import an item from a . /// - /// The model to delete. - public void Delete(TModel model) + /// The model to be imported. + public void Import(TModel item) => ModelStore.Add(item); + + /// + /// Delete an item from the manager. + /// Is a no-op for already deleted items. + /// + /// The item to delete. + public void Delete(TModel item) { using (var usage = ContextFactory.GetForWrite()) { @@ -143,9 +168,9 @@ namespace osu.Game.Database context.ChangeTracker.AutoDetectChangesEnabled = false; // re-fetch the model on the import context. - var foundModel = ContextFactory.Get().Set().Include(s => s.Files).ThenInclude(f => f.FileInfo).First(s => s.ID == model.ID); + var foundModel = queryModel().Include(s => s.Files).ThenInclude(f => f.FileInfo).First(s => s.ID == item.ID); - if (foundModel.DeletePending || !CheckCanDelete(foundModel)) return; + if (foundModel.DeletePending) return; if (ModelStore.Delete(foundModel)) Files.Dereference(foundModel.Files.Select(f => f.FileInfo).ToArray()); @@ -154,6 +179,59 @@ namespace osu.Game.Database } } + /// + /// Restore all items that were previously deleted. + /// This will post notifications tracking progress. + /// + public void UndeleteAll() + { + var deletedItems = queryModel().Where(m => m.DeletePending).ToList(); + + if (!deletedItems.Any()) return; + + var notification = new ProgressNotification + { + CompletionText = "Restored all deleted items!", + Progress = 0, + State = ProgressNotificationState.Active, + }; + + PostNotification?.Invoke(notification); + + int i = 0; + + foreach (var item in deletedItems) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + notification.Text = $"Restoring ({i} of {deletedItems.Count})"; + notification.Progress = (float)++i / deletedItems.Count; + Undelete(item); + } + + notification.State = ProgressNotificationState.Completed; + } + + /// + /// Restore an item that was previously deleted. Is a no-op if the item is not in a deleted state, or has its protected flag set. + /// + /// The item to restore + public void Undelete(TModel item) + { + using (var usage = ContextFactory.GetForWrite()) + { + usage.Context.ChangeTracker.AutoDetectChangesEnabled = false; + + if (!ModelStore.Undelete(item)) return; + + Files.Reference(item.Files.Select(f => f.FileInfo).ToArray()); + + usage.Context.ChangeTracker.AutoDetectChangesEnabled = true; + } + } + /// /// Create all required s for the provided archive, adding them to the global file store. /// @@ -193,7 +271,7 @@ namespace osu.Game.Database protected virtual TModel CheckForExisting(TModel model) => null; - protected virtual bool CheckCanDelete(TModel model) => true; + private DbSet queryModel() => ContextFactory.Get().Set(); /// /// Creates an from a valid storage path. @@ -203,7 +281,7 @@ namespace osu.Game.Database private ArchiveReader getReaderFrom(string path) { if (ZipFile.IsZipFile(path)) - return new OszArchiveReader(Files.Storage.GetStream(path), Path.GetFileName(path)); + return new ZipArchiveReader(Files.Storage.GetStream(path), Path.GetFileName(path)); return new LegacyFilesystemReader(path); } } diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs new file mode 100644 index 0000000000..c6af1aa475 --- /dev/null +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -0,0 +1,76 @@ +using System; +using osu.Framework.Platform; + +namespace osu.Game.Database +{ + /// + /// A typed store which supports basic addition, deletion and updating for soft-deletable models. + /// + /// The databased model. + public abstract class MutableDatabaseBackedStore : DatabaseBackedStore + where T : class, IHasPrimaryKey, ISoftDelete + { + public event Action ItemAdded; + public event Action ItemRemoved; + + protected MutableDatabaseBackedStore(IDatabaseContextFactory contextFactory, Storage storage = null) + : base(contextFactory, storage) + { + } + + public void Add(T item) + { + using (var usage = ContextFactory.GetForWrite()) + { + var context = usage.Context; + context.Attach(item); + } + + ItemAdded?.Invoke(item); + } + + /// + /// Update a in the database. + /// + /// The item to update. + public void Update(T item) + { + ItemRemoved?.Invoke(item); + + using (var usage = ContextFactory.GetForWrite()) + usage.Context.Update(item); + + ItemAdded?.Invoke(item); + } + + public bool Delete(T item) + { + using (ContextFactory.GetForWrite()) + { + Refresh(ref item); + + if (item.DeletePending) return false; + + item.DeletePending = true; + } + + ItemRemoved?.Invoke(item); + return true; + } + + public bool Undelete(T item) + { + using (ContextFactory.GetForWrite()) + { + Refresh(ref item); + + if (!item.DeletePending) return false; + + item.DeletePending = false; + } + + ItemAdded?.Invoke(item); + return true; + } + } +} diff --git a/osu.Game/Beatmaps/IO/ArchiveReader.cs b/osu.Game/IO/Archives/ArchiveReader.cs similarity index 94% rename from osu.Game/Beatmaps/IO/ArchiveReader.cs rename to osu.Game/IO/Archives/ArchiveReader.cs index 7be03ffb1b..351a6dff39 100644 --- a/osu.Game/Beatmaps/IO/ArchiveReader.cs +++ b/osu.Game/IO/Archives/ArchiveReader.cs @@ -6,7 +6,7 @@ using System.Collections.Generic; using System.IO; using osu.Framework.IO.Stores; -namespace osu.Game.Beatmaps.IO +namespace osu.Game.IO.Archives { public abstract class ArchiveReader : IDisposable, IResourceStore { diff --git a/osu.Game/Beatmaps/IO/LegacyFilesystemReader.cs b/osu.Game/IO/Archives/LegacyFilesystemReader.cs similarity index 93% rename from osu.Game/Beatmaps/IO/LegacyFilesystemReader.cs rename to osu.Game/IO/Archives/LegacyFilesystemReader.cs index e0a54838e0..d6d80783db 100644 --- a/osu.Game/Beatmaps/IO/LegacyFilesystemReader.cs +++ b/osu.Game/IO/Archives/LegacyFilesystemReader.cs @@ -1,12 +1,12 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework.IO.File; using System.Collections.Generic; using System.IO; using System.Linq; +using osu.Framework.IO.File; -namespace osu.Game.Beatmaps.IO +namespace osu.Game.IO.Archives { /// /// Reads an extracted legacy beatmap from disk. diff --git a/osu.Game/Beatmaps/IO/OszArchiveReader.cs b/osu.Game/IO/Archives/ZipArchiveReader.cs similarity index 86% rename from osu.Game/Beatmaps/IO/OszArchiveReader.cs rename to osu.Game/IO/Archives/ZipArchiveReader.cs index fbac5d79f3..a772382b5e 100644 --- a/osu.Game/Beatmaps/IO/OszArchiveReader.cs +++ b/osu.Game/IO/Archives/ZipArchiveReader.cs @@ -6,14 +6,14 @@ using System.IO; using System.Linq; using Ionic.Zip; -namespace osu.Game.Beatmaps.IO +namespace osu.Game.IO.Archives { - public sealed class OszArchiveReader : ArchiveReader + public sealed class ZipArchiveReader : ArchiveReader { private readonly Stream archiveStream; private readonly ZipFile archive; - public OszArchiveReader(Stream archiveStream, string name = null) + public ZipArchiveReader(Stream archiveStream, string name = null) : base(name) { this.archiveStream = archiveStream; diff --git a/osu.Game/IO/IMutableStore.cs b/osu.Game/IO/IMutableStore.cs deleted file mode 100644 index ced1b29316..0000000000 --- a/osu.Game/IO/IMutableStore.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -namespace osu.Game.IO -{ - public interface IMutableStore - { - /// - /// Add an object to the store. - /// - /// The object to add. - void Add(T item); - - bool Delete(T item); - } -} diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 36b6a9964a..3ce0dfee31 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -223,13 +223,13 @@ namespace osu.Game.Overlays.BeatmapSet tabsBg.Colour = colours.Gray3; this.beatmaps = beatmaps; - beatmaps.BeatmapSetAdded += handleBeatmapAdd; + beatmaps.ItemAdded += handleBeatmapAdd; } protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); - if (beatmaps != null) beatmaps.BeatmapSetAdded -= handleBeatmapAdd; + if (beatmaps != null) beatmaps.ItemAdded -= handleBeatmapAdd; } private void handleBeatmapAdd(BeatmapSetInfo beatmap) diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 05b5bba09c..8d8a4aebaa 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -185,7 +185,7 @@ namespace osu.Game.Overlays resultCountsContainer.Colour = colours.Yellow; - beatmaps.BeatmapSetAdded += setAdded; + beatmaps.ItemAdded += setAdded; } private void setAdded(BeatmapSetInfo set) diff --git a/osu.Game/Overlays/Music/PlaylistOverlay.cs b/osu.Game/Overlays/Music/PlaylistOverlay.cs index 2125984785..ac7ec6257b 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.BeatmapSetAdded += list.AddBeatmapSet; - beatmaps.BeatmapSetRemoved += list.RemoveBeatmapSet; + beatmaps.ItemAdded += list.AddBeatmapSet; + beatmaps.ItemRemoved += list.RemoveBeatmapSet; list.BeatmapSets = beatmaps.GetAllUsableBeatmapSets(); diff --git a/osu.Game/Screens/Menu/Intro.cs b/osu.Game/Screens/Menu/Intro.cs index e0467d8f84..ce3c93ebcf 100644 --- a/osu.Game/Screens/Menu/Intro.cs +++ b/osu.Game/Screens/Menu/Intro.cs @@ -10,8 +10,8 @@ using osu.Framework.Screens; using osu.Framework.Graphics; using osu.Framework.MathUtils; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.IO; using osu.Game.Configuration; +using osu.Game.IO.Archives; using osu.Game.Screens.Backgrounds; using OpenTK; using OpenTK.Graphics; @@ -62,7 +62,7 @@ namespace osu.Game.Screens.Menu if (setInfo == null) { // we need to import the default menu background beatmap - setInfo = beatmaps.Import(new OszArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz"), "circles.osz")); + setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream(@"Tracks/circles.osz"), "circles.osz")); setInfo.Protected = true; beatmaps.Update(setInfo); diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 2421a4fdfe..f35768d933 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -197,8 +197,8 @@ namespace osu.Game.Screens.Select if (osu != null) Ruleset.BindTo(osu.Ruleset); - this.beatmaps.BeatmapSetAdded += onBeatmapSetAdded; - this.beatmaps.BeatmapSetRemoved += onBeatmapSetRemoved; + this.beatmaps.ItemAdded += onBeatmapSetAdded; + this.beatmaps.ItemRemoved += onBeatmapSetRemoved; this.beatmaps.BeatmapHidden += onBeatmapHidden; this.beatmaps.BeatmapRestored += onBeatmapRestored; @@ -401,8 +401,8 @@ namespace osu.Game.Screens.Select if (beatmaps != null) { - beatmaps.BeatmapSetAdded -= onBeatmapSetAdded; - beatmaps.BeatmapSetRemoved -= onBeatmapSetRemoved; + beatmaps.ItemAdded -= onBeatmapSetAdded; + beatmaps.ItemRemoved -= onBeatmapSetRemoved; beatmaps.BeatmapHidden -= onBeatmapHidden; beatmaps.BeatmapRestored -= onBeatmapRestored; } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index af2b1dfae5..91aaf9c092 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -274,7 +274,7 @@ - + @@ -282,10 +282,13 @@ + - + + + @@ -378,8 +381,6 @@ - - @@ -394,7 +395,6 @@ - From d3dd31dadb0d8312475bc2fc82c4595340217582 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 13:30:17 +0900 Subject: [PATCH 16/69] Make deletion and purging logic even more global --- .../Visual/TestCasePlaySongSelect.cs | 2 +- osu.Game/Beatmaps/BeatmapManager.cs | 52 +------------- osu.Game/Beatmaps/BeatmapStore.cs | 38 ++++------- osu.Game/Database/ArchiveModelManager.cs | 68 ++++++++++++++++--- osu.Game/Database/DatabaseBackedStore.cs | 2 +- .../Database/MutableDatabaseBackedStore.cs | 35 ++++++++++ osu.Game/IO/FileStore.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- .../Sections/Maintenance/GeneralSettings.cs | 4 +- 9 files changed, 114 insertions(+), 91 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs index 8bb0d152f6..13b2be9fdb 100644 --- a/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs +++ b/osu.Game.Tests/Visual/TestCasePlaySongSelect.cs @@ -75,7 +75,7 @@ namespace osu.Game.Tests.Visual { if (deleteMaps) { - manager.DeleteAll(); + manager.Delete(manager.GetAllUsableBeatmapSets()); game.Beatmap.SetDefault(); } diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 8bc1f72c1f..4a6b6909b9 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -71,8 +71,6 @@ namespace osu.Game.Beatmaps this.rulesets = rulesets; this.api = api; - - beatmaps.Cleanup(); } protected override void Populate(BeatmapSetInfo model, ArchiveReader archive) @@ -102,7 +100,7 @@ namespace osu.Game.Beatmaps if (existingOnlineId != null) { Delete(existingOnlineId); - beatmaps.Cleanup(s => s.ID == existingOnlineId.ID); + beatmaps.PurgeDeletable(s => s.ID == existingOnlineId.ID); } } @@ -193,12 +191,6 @@ namespace osu.Game.Beatmaps /// The object if it exists, or null. public DownloadBeatmapSetRequest GetExistingDownload(BeatmapSetInfo beatmap) => currentDownloads.Find(d => d.BeatmapSet.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID); - /// - /// Update a BeatmapSetInfo with all changes. TODO: This only supports very basic updates currently. - /// - /// The beatmap set to update. - public void Update(BeatmapSetInfo beatmap) => beatmaps.Update(beatmap); - /// /// Delete a beatmap difficulty. /// @@ -239,13 +231,6 @@ namespace osu.Game.Beatmaps /// The first result for the provided query, or null if no results were found. public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.BeatmapSets.AsNoTracking().FirstOrDefault(query); - /// - /// Refresh an existing instance of a from the store. - /// - /// A stale instance. - /// A fresh instance. - public BeatmapSetInfo Refresh(BeatmapSetInfo beatmapSet) => QueryBeatmapSet(s => s.ID == beatmapSet.ID); - /// /// Returns a list of all usable s. /// @@ -294,41 +279,6 @@ namespace osu.Game.Beatmaps await Task.Factory.StartNew(() => Import(stable.GetDirectories("Songs")), TaskCreationOptions.LongRunning); } - /// - /// Delete all beatmaps. - /// This will post notifications tracking progress. - /// - public void DeleteAll() - { - var maps = GetAllUsableBeatmapSets(); - - if (maps.Count == 0) return; - - var notification = new ProgressNotification - { - Progress = 0, - CompletionText = "Deleted all beatmaps!", - State = ProgressNotificationState.Active, - }; - - PostNotification?.Invoke(notification); - - int i = 0; - - foreach (var b in maps) - { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; - - notification.Text = $"Deleting ({i} of {maps.Count})"; - notification.Progress = (float)++i / maps.Count; - Delete(b); - } - - notification.State = ProgressNotificationState.Completed; - } - /// /// Create a SHA-2 hash from the provided archive based on contained beatmap (.osu) file content. /// diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 3e4840f4e1..e695c3bf28 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -2,8 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Linq; -using System.Linq.Expressions; using Microsoft.EntityFrameworkCore; using osu.Game.Database; @@ -63,32 +63,24 @@ namespace osu.Game.Beatmaps return true; } - public override void Cleanup() => Cleanup(_ => true); - - public void Cleanup(Expression> query) + protected override IQueryable AddIncludesForDeletion(IQueryable query) { - using (var usage = ContextFactory.GetForWrite()) - { - var context = usage.Context; + return base.AddIncludesForDeletion(query) + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) + .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) + .Include(s => s.Metadata); + } - var purgeable = context.BeatmapSetInfo.Where(s => s.DeletePending && !s.Protected) - .Where(query) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) - .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Metadata).ToList(); + protected override void Purge(List items, OsuDbContext context) + { + // metadata is M-N so we can't rely on cascades + context.BeatmapMetadata.RemoveRange(items.Select(s => s.Metadata)); + context.BeatmapMetadata.RemoveRange(items.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null))); - if (!purgeable.Any()) return; + // todo: we can probably make cascades work here with a FK in BeatmapDifficulty. just make to make it work correctly. + context.BeatmapDifficulty.RemoveRange(items.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty))); - // metadata is M-N so we can't rely on cascades - context.BeatmapMetadata.RemoveRange(purgeable.Select(s => s.Metadata)); - context.BeatmapMetadata.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.Metadata).Where(m => m != null))); - - // todo: we can probably make cascades work here with a FK in BeatmapDifficulty. just make to make it work correctly. - context.BeatmapDifficulty.RemoveRange(purgeable.SelectMany(s => s.Beatmaps.Select(b => b.BaseDifficulty))); - - // cascades down to beatmaps. - context.BeatmapSetInfo.RemoveRange(purgeable); - } + base.Purge(items, context); } public IQueryable BeatmapSets => ContextFactory.Get().BeatmapSetInfo diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 9c558a6c12..31eab79127 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -62,6 +62,8 @@ namespace osu.Game.Database if (importHost != null) ipc = new ArchiveImportIPCChannel(importHost, this); + + ModelStore.PurgeDeletable(); } /// @@ -154,6 +156,13 @@ namespace osu.Game.Database /// The model to be imported. public void Import(TModel item) => ModelStore.Add(item); + /// + /// Perform an update of the specified item. + /// TODO: Support file changes. + /// + /// The item to update. + public void Update(TModel item) => ModelStore.Update(item); + /// /// Delete an item from the manager. /// Is a no-op for already deleted items. @@ -180,14 +189,48 @@ namespace osu.Game.Database } /// - /// Restore all items that were previously deleted. + /// Delete multiple items. /// This will post notifications tracking progress. /// - public void UndeleteAll() + public void Delete(List items) { - var deletedItems = queryModel().Where(m => m.DeletePending).ToList(); + if (items.Count == 0) return; - if (!deletedItems.Any()) return; + var notification = new ProgressNotification + { + Progress = 0, + CompletionText = "Deleted all beatmaps!", + State = ProgressNotificationState.Active, + }; + + PostNotification?.Invoke(notification); + + int i = 0; + + using (ContextFactory.GetForWrite()) + { + foreach (var b in items) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; + + notification.Text = $"Deleting ({i} of {items.Count})"; + notification.Progress = (float)++i / items.Count; + Delete(b); + } + } + + notification.State = ProgressNotificationState.Completed; + } + + /// + /// Restore multiple items that were previously deleted. + /// This will post notifications tracking progress. + /// + public void Undelete(List items) + { + if (!items.Any()) return; var notification = new ProgressNotification { @@ -200,15 +243,18 @@ namespace osu.Game.Database int i = 0; - foreach (var item in deletedItems) + using (ContextFactory.GetForWrite()) { - if (notification.State == ProgressNotificationState.Cancelled) - // user requested abort - return; + foreach (var item in items) + { + if (notification.State == ProgressNotificationState.Cancelled) + // user requested abort + return; - notification.Text = $"Restoring ({i} of {deletedItems.Count})"; - notification.Progress = (float)++i / deletedItems.Count; - Undelete(item); + notification.Text = $"Restoring ({i} of {items.Count})"; + notification.Progress = (float)++i / items.Count; + Undelete(item); + } } notification.State = ProgressNotificationState.Completed; diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index cf46b66422..a1ed992f03 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -49,7 +49,7 @@ namespace osu.Game.Database /// /// Perform any common clean-up tasks. Should be run when idle, or whenever necessary. /// - public virtual void Cleanup() + public virtual void PurgeDeletable() { } } diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index c6af1aa475..9de6068d10 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -1,4 +1,7 @@ using System; +using System.Collections.Generic; +using System.Linq; +using System.Linq.Expressions; using osu.Framework.Platform; namespace osu.Game.Database @@ -72,5 +75,37 @@ namespace osu.Game.Database ItemAdded?.Invoke(item); return true; } + + protected virtual IQueryable AddIncludesForDeletion(IQueryable query) => query; + + protected virtual void Purge(List items, OsuDbContext context) + { + // cascades down to beatmaps. + context.RemoveRange(items); + } + + /// + /// Purge items in a pending delete state. + /// + /// An optional query limiting the scope of the purge. + public void PurgeDeletable(Expression> query = null) + { + using (var usage = ContextFactory.GetForWrite()) + { + var context = usage.Context; + + var lookup = context.Set().Where(s => s.DeletePending); + + if (query != null) lookup = lookup.Where(query); + + AddIncludesForDeletion(lookup); + + var purgeable = lookup.ToList(); + + if (!purgeable.Any()) return; + + Purge(purgeable, context); + } + } } } diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index ab81ba4851..6f262fd8fa 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -90,7 +90,7 @@ namespace osu.Game.IO } } - public override void Cleanup() + public override void PurgeDeletable() { using (var usage = ContextFactory.GetForWrite()) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 505577416d..ce50f160f7 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -172,7 +172,7 @@ namespace osu.Game API.Register(this); - FileStore.Cleanup(); + FileStore.PurgeDeletable(); } private void runMigrations() diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index 1223310c74..eec99dc886 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance dialogOverlay?.Push(new DeleteAllBeatmapsDialog(() => { deleteButton.Enabled.Value = false; - Task.Run(() => beatmaps.DeleteAll()).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); + Task.Run(() => beatmaps.Delete(beatmaps.GetAllUsableBeatmapSets())).ContinueWith(t => Schedule(() => deleteButton.Enabled.Value = true)); })); } }, @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { undeleteButton.Enabled.Value = false; - Task.Run(() => beatmaps.UndeleteAll()).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + Task.Run(() => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.DeletePending))).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); } }, }; From a0a65abcac810041ac20cc78134f4daf758a7961 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 14:19:16 +0900 Subject: [PATCH 17/69] Crentalise all import logic --- osu.Desktop/OsuGameDesktop.cs | 16 +--------------- osu.Game/Database/ArchiveModelManager.cs | 2 +- ...anImportArchives.cs => ICanAcceptFiles.cs} | 2 +- osu.Game/IPC/ArchiveImportIPCChannel.cs | 4 ++-- osu.Game/OsuGame.cs | 5 ++++- osu.Game/OsuGameBase.cs | 19 ++++++++++++++++++- osu.Game/Rulesets/Scoring/ScoreStore.cs | 18 +++++++++++++++++- osu.Game/osu.Game.csproj | 2 +- 8 files changed, 45 insertions(+), 23 deletions(-) rename osu.Game/Database/{ICanImportArchives.cs => ICanAcceptFiles.cs} (73%) diff --git a/osu.Desktop/OsuGameDesktop.cs b/osu.Desktop/OsuGameDesktop.cs index c563201f0a..45ed66bad2 100644 --- a/osu.Desktop/OsuGameDesktop.cs +++ b/osu.Desktop/OsuGameDesktop.cs @@ -115,21 +115,7 @@ namespace osu.Desktop if (filePaths.Any(f => Path.GetExtension(f) != firstExtension)) return; - switch (firstExtension) - { - case ".osz": - Task.Factory.StartNew(() => BeatmapManager.Import(filePaths), TaskCreationOptions.LongRunning); - return; - case ".osr": - Task.Run(() => - { - var score = ScoreStore.ReadReplayFile(filePaths.First()); - Schedule(() => LoadScore(score)); - }); - return; - } + Task.Factory.StartNew(() => Import(filePaths), TaskCreationOptions.LongRunning); } - - private static readonly string[] allowed_extensions = { @".osz", @".osr" }; } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 31eab79127..1b37e7e76c 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -20,7 +20,7 @@ namespace osu.Game.Database /// /// The model type. /// The associated file join type. - public abstract class ArchiveModelManager : ICanImportArchives + public abstract class ArchiveModelManager : ICanAcceptFiles where TModel : class, IHasFiles, IHasPrimaryKey, ISoftDelete where TFileModel : INamedFileInfo, new() { diff --git a/osu.Game/Database/ICanImportArchives.cs b/osu.Game/Database/ICanAcceptFiles.cs similarity index 73% rename from osu.Game/Database/ICanImportArchives.cs rename to osu.Game/Database/ICanAcceptFiles.cs index 0f863f3044..d09000525d 100644 --- a/osu.Game/Database/ICanImportArchives.cs +++ b/osu.Game/Database/ICanAcceptFiles.cs @@ -1,6 +1,6 @@ namespace osu.Game.Database { - public interface ICanImportArchives + public interface ICanAcceptFiles { void Import(params string[] paths); diff --git a/osu.Game/IPC/ArchiveImportIPCChannel.cs b/osu.Game/IPC/ArchiveImportIPCChannel.cs index 6e9787ca5a..9d7bf17c77 100644 --- a/osu.Game/IPC/ArchiveImportIPCChannel.cs +++ b/osu.Game/IPC/ArchiveImportIPCChannel.cs @@ -12,9 +12,9 @@ namespace osu.Game.IPC { public class ArchiveImportIPCChannel : IpcChannel { - private readonly ICanImportArchives importer; + private readonly ICanAcceptFiles importer; - public ArchiveImportIPCChannel(IIpcHost host, ICanImportArchives importer = null) + public ArchiveImportIPCChannel(IIpcHost host, ICanAcceptFiles importer = null) : base(host) { this.importer = importer; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 624179cfe1..14bc31aecf 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -105,6 +105,8 @@ namespace osu.Game { this.frameworkConfig = frameworkConfig; + ScoreStore.ScoreImported += score => Schedule(() => LoadScore(score)); + if (!Host.IsPrimaryInstance) { Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error); @@ -114,7 +116,8 @@ namespace osu.Game if (args?.Length > 0) { var paths = args.Where(a => !a.StartsWith(@"-")); - Task.Run(() => BeatmapManager.Import(paths.ToArray())); + + Task.Run(() => Import(paths.ToArray())); } dependencies.CacheAs(this); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ce50f160f7..dba0250007 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -2,7 +2,10 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections.Generic; using System.Diagnostics; +using System.IO; +using System.Linq; using System.Reflection; using osu.Framework.Allocation; using osu.Framework.Audio; @@ -30,7 +33,7 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game { - public class OsuGameBase : Framework.Game, IOnlineComponent + public class OsuGameBase : Framework.Game, IOnlineComponent, ICanAcceptFiles { protected OsuConfigManager LocalConfig; @@ -114,6 +117,8 @@ namespace osu.Game dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(new OsuColour()); + fileImporters.Add(BeatmapManager); + //this completely overrides the framework default. will need to change once we make a proper FontStore. dependencies.Cache(Fonts = new FontStore { ScaleAdjust = 100 }); @@ -257,5 +262,17 @@ namespace osu.Game base.Dispose(isDisposing); } + + private readonly List fileImporters = new List(); + + public void Import(params string[] paths) + { + var extension = Path.GetExtension(paths.First()); + + foreach (var importer in fileImporters) + if (importer.HandledExtensions.Contains(extension)) importer.Import(paths); + } + + public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray(); } } diff --git a/osu.Game/Rulesets/Scoring/ScoreStore.cs b/osu.Game/Rulesets/Scoring/ScoreStore.cs index 8bde2747a2..7abee0b04f 100644 --- a/osu.Game/Rulesets/Scoring/ScoreStore.cs +++ b/osu.Game/Rulesets/Scoring/ScoreStore.cs @@ -1,6 +1,7 @@ // 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.IO; using osu.Framework.Platform; @@ -14,7 +15,7 @@ using SharpCompress.Compressors.LZMA; namespace osu.Game.Rulesets.Scoring { - public class ScoreStore : DatabaseBackedStore + public class ScoreStore : DatabaseBackedStore, ICanAcceptFiles { private readonly Storage storage; @@ -23,6 +24,8 @@ namespace osu.Game.Rulesets.Scoring private const string replay_folder = @"replays"; + public event Action ScoreImported; + // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ScoreIPCChannel ipc; @@ -36,6 +39,18 @@ namespace osu.Game.Rulesets.Scoring ipc = new ScoreIPCChannel(importHost, this); } + public string[] HandledExtensions => new[] { ".osr" }; + + public void Import(params string[] paths) + { + foreach (var path in paths) + { + var score = ReadReplayFile(path); + if (score != null) + ScoreImported?.Invoke(score); + } + } + public Score ReadReplayFile(string replayFilename) { Score score; @@ -159,5 +174,6 @@ namespace osu.Game.Rulesets.Scoring return new Replay { Frames = frames }; } + } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 91aaf9c092..bfe7ec1821 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -277,7 +277,7 @@ - + From fe5df663be1c648eb4c0c41398d447141e8eaa04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 15:14:46 +0900 Subject: [PATCH 18/69] Add more xmldoc --- osu.Game/Database/ICanAcceptFiles.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Database/ICanAcceptFiles.cs b/osu.Game/Database/ICanAcceptFiles.cs index d09000525d..40978277b1 100644 --- a/osu.Game/Database/ICanAcceptFiles.cs +++ b/osu.Game/Database/ICanAcceptFiles.cs @@ -1,9 +1,19 @@ namespace osu.Game.Database { + /// + /// A class which can accept files for importing. + /// public interface ICanAcceptFiles { + /// + /// Import the specified paths. + /// + /// The files which should be imported. void Import(params string[] paths); + /// + /// An array of accepted file extensions (in the standard format of ".abc"). + /// string[] HandledExtensions { get; } } } From d07ab1fbea0f25ee28d7e1dfc2e2a08d690ff1bc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 15:14:55 +0900 Subject: [PATCH 19/69] Fix undelete all --- .../Overlays/Settings/Sections/Maintenance/GeneralSettings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs index eec99dc886..d9fedd0225 100644 --- a/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Maintenance/GeneralSettings.cs @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections.Maintenance Action = () => { undeleteButton.Enabled.Value = false; - Task.Run(() => beatmaps.Undelete(beatmaps.QueryBeatmapSet(b => b.DeletePending))).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); + Task.Run(() => beatmaps.Undelete(beatmaps.QueryBeatmapSets(b => b.DeletePending).ToList())).ContinueWith(t => Schedule(() => undeleteButton.Enabled.Value = true)); } }, }; From e51450a0645604d03af8fb1d610e84e85b3634e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 15:24:28 +0900 Subject: [PATCH 20/69] Fix query construction --- osu.Game/Database/MutableDatabaseBackedStore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 9de6068d10..887f568864 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -98,7 +98,7 @@ namespace osu.Game.Database if (query != null) lookup = lookup.Where(query); - AddIncludesForDeletion(lookup); + lookup = AddIncludesForDeletion(lookup); var purgeable = lookup.ToList(); From 671475f3b40981f2105283da8aadec8a86b3952a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 15:51:59 +0900 Subject: [PATCH 21/69] Ensure undeleted items are populated with includes before firing events --- osu.Game/Database/DatabaseBackedStore.cs | 3 +-- osu.Game/Database/MutableDatabaseBackedStore.cs | 6 +++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index a1ed992f03..6109475690 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -1,7 +1,6 @@ // 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 Microsoft.EntityFrameworkCore; using osu.Framework.Platform; @@ -23,7 +22,7 @@ namespace osu.Game.Database /// The object to use as a reference when negotiating a local instance. /// An optional lookup source which will be used to query and populate a freshly retrieved replacement. If not provided, the refreshed object will still be returned but will not have any includes. /// A valid EF-stored type. - protected virtual void Refresh(ref T obj, IEnumerable lookupSource = null) where T : class, IHasPrimaryKey + protected virtual void Refresh(ref T obj, IQueryable lookupSource = null) where T : class, IHasPrimaryKey { using (var usage = ContextFactory.GetForWrite()) { diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 887f568864..01fcfbfe43 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -53,7 +53,6 @@ namespace osu.Game.Database Refresh(ref item); if (item.DeletePending) return false; - item.DeletePending = true; } @@ -65,10 +64,9 @@ namespace osu.Game.Database { using (ContextFactory.GetForWrite()) { - Refresh(ref item); + Refresh(ref item, ConsumableItems); if (!item.DeletePending) return false; - item.DeletePending = false; } @@ -76,6 +74,8 @@ namespace osu.Game.Database return true; } + protected virtual IQueryable AddIncludesForConsumption(IQueryable query) => query; + protected virtual IQueryable AddIncludesForDeletion(IQueryable query) => query; protected virtual void Purge(List items, OsuDbContext context) From 89cf794f980e4ac6137b234e8850764c67cbc477 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 15:52:17 +0900 Subject: [PATCH 22/69] Add a lower level ConsumableItems implementation --- osu.Game/Beatmaps/BeatmapManager.cs | 10 +++--- osu.Game/Beatmaps/BeatmapStore.cs | 34 +++++++++---------- .../Database/MutableDatabaseBackedStore.cs | 5 +++ 3 files changed, 27 insertions(+), 22 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index 4a6b6909b9..1d6d8b6726 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -86,7 +86,7 @@ namespace osu.Game.Beatmaps protected override BeatmapSetInfo CheckForExisting(BeatmapSetInfo model) { // check if this beatmap has already been imported and exit early if so - var existingHashMatch = beatmaps.BeatmapSets.FirstOrDefault(b => b.Hash == model.Hash); + var existingHashMatch = beatmaps.ConsumableItems.FirstOrDefault(b => b.Hash == model.Hash); if (existingHashMatch != null) { Undelete(existingHashMatch); @@ -96,7 +96,7 @@ namespace osu.Game.Beatmaps // check if a set already exists with the same online id if (model.OnlineBeatmapSetID != null) { - var existingOnlineId = beatmaps.BeatmapSets.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID); + var existingOnlineId = beatmaps.ConsumableItems.FirstOrDefault(b => b.OnlineBeatmapSetID == model.OnlineBeatmapSetID); if (existingOnlineId != null) { Delete(existingOnlineId); @@ -229,20 +229,20 @@ namespace osu.Game.Beatmaps /// /// The query. /// The first result for the provided query, or null if no results were found. - public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.BeatmapSets.AsNoTracking().FirstOrDefault(query); + public BeatmapSetInfo QueryBeatmapSet(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().FirstOrDefault(query); /// /// Returns a list of all usable s. /// /// A list of available . - public List GetAllUsableBeatmapSets() => beatmaps.BeatmapSets.Where(s => !s.DeletePending && !s.Protected).ToList(); + public List GetAllUsableBeatmapSets() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected).ToList(); /// /// Perform a lookup query on available s. /// /// The query. /// Results from the provided query. - public IEnumerable QueryBeatmapSets(Expression> query) => beatmaps.BeatmapSets.AsNoTracking().Where(query); + public IEnumerable QueryBeatmapSets(Expression> query) => beatmaps.ConsumableItems.AsNoTracking().Where(query); /// /// Perform a lookup query on available s. diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index e695c3bf28..2e37076aca 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -63,13 +63,19 @@ namespace osu.Game.Beatmaps return true; } - protected override IQueryable AddIncludesForDeletion(IQueryable query) - { - return base.AddIncludesForDeletion(query) + protected override IQueryable AddIncludesForDeletion(IQueryable query) => + base.AddIncludesForDeletion(query) .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) .Include(s => s.Metadata); - } + + protected override IQueryable AddIncludesForConsumption(IQueryable query) => + base.AddIncludesForConsumption(query) + .Include(s => s.Metadata) + .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) + .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) + .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) + .Include(s => s.Files).ThenInclude(f => f.FileInfo); protected override void Purge(List items, OsuDbContext context) { @@ -83,18 +89,12 @@ namespace osu.Game.Beatmaps base.Purge(items, context); } - public IQueryable BeatmapSets => ContextFactory.Get().BeatmapSetInfo - .Include(s => s.Metadata) - .Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset) - .Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty) - .Include(s => s.Beatmaps).ThenInclude(b => b.Metadata) - .Include(s => s.Files).ThenInclude(f => f.FileInfo); - - public IQueryable Beatmaps => ContextFactory.Get().BeatmapInfo - .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) - .Include(b => b.BeatmapSet).ThenInclude(s => s.Files).ThenInclude(f => f.FileInfo) - .Include(b => b.Metadata) - .Include(b => b.Ruleset) - .Include(b => b.BaseDifficulty); + public IQueryable Beatmaps => + ContextFactory.Get().BeatmapInfo + .Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata) + .Include(b => b.BeatmapSet).ThenInclude(s => s.Files).ThenInclude(f => f.FileInfo) + .Include(b => b.Metadata) + .Include(b => b.Ruleset) + .Include(b => b.BaseDifficulty); } } diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 01fcfbfe43..3905942c8c 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -21,6 +21,11 @@ namespace osu.Game.Database { } + /// + /// Access items pre-populated with includes for consumption. + /// + public IQueryable ConsumableItems => AddIncludesForConsumption(ContextFactory.Get().Set()); + public void Add(T item) { using (var usage = ContextFactory.GetForWrite()) From 8c1d581fb34425849a373e25f92072337b724a4b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 16:15:30 +0900 Subject: [PATCH 23/69] Fix hiding beatmaps not refreshing correctly --- osu.Game/Beatmaps/BeatmapStore.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapStore.cs b/osu.Game/Beatmaps/BeatmapStore.cs index 2e37076aca..93ad1badd2 100644 --- a/osu.Game/Beatmaps/BeatmapStore.cs +++ b/osu.Game/Beatmaps/BeatmapStore.cs @@ -34,12 +34,10 @@ namespace osu.Game.Beatmaps Refresh(ref beatmap, Beatmaps); if (beatmap.Hidden) return false; - beatmap.Hidden = true; - - BeatmapHidden?.Invoke(beatmap); } + BeatmapHidden?.Invoke(beatmap); return true; } @@ -55,7 +53,6 @@ namespace osu.Game.Beatmaps Refresh(ref beatmap, Beatmaps); if (!beatmap.Hidden) return false; - beatmap.Hidden = false; } From 1b13be1372c7ab2e7983473c18a35c9904309363 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 16:23:34 +0900 Subject: [PATCH 24/69] Cleanups and xmldoc additions --- osu.Game/Beatmaps/BeatmapSetInfo.cs | 1 - osu.Game/Database/IHasFiles.cs | 13 +++++++++++++ osu.Game/Database/ISoftDelete.cs | 6 ++++++ osu.Game/Database/MutableDatabaseBackedStore.cs | 6 +----- osu.Game/IO/IHasFiles.cs | 9 --------- osu.Game/osu.Game.csproj | 2 +- 6 files changed, 21 insertions(+), 16 deletions(-) create mode 100644 osu.Game/Database/IHasFiles.cs delete mode 100644 osu.Game/IO/IHasFiles.cs diff --git a/osu.Game/Beatmaps/BeatmapSetInfo.cs b/osu.Game/Beatmaps/BeatmapSetInfo.cs index 79983becb0..1736e3fa90 100644 --- a/osu.Game/Beatmaps/BeatmapSetInfo.cs +++ b/osu.Game/Beatmaps/BeatmapSetInfo.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.ComponentModel.DataAnnotations.Schema; using System.Linq; using osu.Game.Database; -using osu.Game.IO; namespace osu.Game.Beatmaps { diff --git a/osu.Game/Database/IHasFiles.cs b/osu.Game/Database/IHasFiles.cs new file mode 100644 index 0000000000..cae8ea66ef --- /dev/null +++ b/osu.Game/Database/IHasFiles.cs @@ -0,0 +1,13 @@ +using System.Collections.Generic; + +namespace osu.Game.Database +{ + /// + /// A model that contains a list of files it is responsible for. + /// + /// The model representing a file. + public interface IHasFiles + { + List Files { get; set; } + } +} diff --git a/osu.Game/Database/ISoftDelete.cs b/osu.Game/Database/ISoftDelete.cs index 19510421c4..c884d7af00 100644 --- a/osu.Game/Database/ISoftDelete.cs +++ b/osu.Game/Database/ISoftDelete.cs @@ -3,8 +3,14 @@ namespace osu.Game.Database { + /// + /// A model that can be deleted from user's view without being instantly lost. + /// public interface ISoftDelete { + /// + /// Whether this model is marked for future deletion. + /// bool DeletePending { get; set; } } } diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 3905942c8c..96bc48fd8a 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -83,11 +83,7 @@ namespace osu.Game.Database protected virtual IQueryable AddIncludesForDeletion(IQueryable query) => query; - protected virtual void Purge(List items, OsuDbContext context) - { - // cascades down to beatmaps. - context.RemoveRange(items); - } + protected virtual void Purge(List items, OsuDbContext context) => context.RemoveRange(items); /// /// Purge items in a pending delete state. diff --git a/osu.Game/IO/IHasFiles.cs b/osu.Game/IO/IHasFiles.cs deleted file mode 100644 index df313b4eae..0000000000 --- a/osu.Game/IO/IHasFiles.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System.Collections.Generic; - -namespace osu.Game.IO -{ - public interface IHasFiles - { - List Files { get; set; } - } -} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index bfe7ec1821..7e440dacf8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -279,6 +279,7 @@ + @@ -289,7 +290,6 @@ - From b9ef32b09bfb137eafaa034f2cd3e5524f496a4d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 16:31:42 +0900 Subject: [PATCH 25/69] Further xmldoc and restoring of Cleanup method --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/Database/DatabaseBackedStore.cs | 2 +- .../Database/MutableDatabaseBackedStore.cs | 34 +++++++++++++++++++ osu.Game/IO/FileStore.cs | 2 +- osu.Game/OsuGameBase.cs | 2 +- 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 1b37e7e76c..20f90a248b 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -63,7 +63,7 @@ namespace osu.Game.Database if (importHost != null) ipc = new ArchiveImportIPCChannel(importHost, this); - ModelStore.PurgeDeletable(); + ModelStore.Cleanup(); } /// diff --git a/osu.Game/Database/DatabaseBackedStore.cs b/osu.Game/Database/DatabaseBackedStore.cs index 6109475690..0fafb77339 100644 --- a/osu.Game/Database/DatabaseBackedStore.cs +++ b/osu.Game/Database/DatabaseBackedStore.cs @@ -48,7 +48,7 @@ namespace osu.Game.Database /// /// Perform any common clean-up tasks. Should be run when idle, or whenever necessary. /// - public virtual void PurgeDeletable() + public virtual void Cleanup() { } } diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 96bc48fd8a..95d3dfc582 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -26,6 +26,10 @@ namespace osu.Game.Database /// public IQueryable ConsumableItems => AddIncludesForConsumption(ContextFactory.Get().Set()); + /// + /// Add a to the database. + /// + /// The item to add. public void Add(T item) { using (var usage = ContextFactory.GetForWrite()) @@ -51,6 +55,10 @@ namespace osu.Game.Database ItemAdded?.Invoke(item); } + /// + /// Delete a from the database. + /// + /// The item to delete. public bool Delete(T item) { using (ContextFactory.GetForWrite()) @@ -65,6 +73,10 @@ namespace osu.Game.Database return true; } + /// + /// Restore a from a deleted state. + /// + /// The item to undelete. public bool Undelete(T item) { using (ContextFactory.GetForWrite()) @@ -79,12 +91,34 @@ namespace osu.Game.Database return true; } + /// + /// Allow implementations to add database-side includes or constraints when querying for consumption of items. + /// + /// The input query. + /// A potentially modified output query. protected virtual IQueryable AddIncludesForConsumption(IQueryable query) => query; + /// + /// Allow implementations to add database-side includes or constraints when deleting items. + /// Included properties could then be subsequently deleted by overriding . + /// + /// The input query. + /// A potentially modified output query. protected virtual IQueryable AddIncludesForDeletion(IQueryable query) => query; + /// + /// Called when removing an item completely from the database. + /// + /// The items to be purged. + /// The write context which can be used to perform subsequent deletions. protected virtual void Purge(List items, OsuDbContext context) => context.RemoveRange(items); + public override void Cleanup() + { + base.Cleanup(); + PurgeDeletable(); + } + /// /// Purge items in a pending delete state. /// diff --git a/osu.Game/IO/FileStore.cs b/osu.Game/IO/FileStore.cs index 6f262fd8fa..ab81ba4851 100644 --- a/osu.Game/IO/FileStore.cs +++ b/osu.Game/IO/FileStore.cs @@ -90,7 +90,7 @@ namespace osu.Game.IO } } - public override void PurgeDeletable() + public override void Cleanup() { using (var usage = ContextFactory.GetForWrite()) { diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index dba0250007..de2a4d0b82 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -177,7 +177,7 @@ namespace osu.Game API.Register(this); - FileStore.PurgeDeletable(); + FileStore.Cleanup(); } private void runMigrations() From fa05822d7dd78ca6fc24dca9843cd762084e4859 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 16:33:33 +0900 Subject: [PATCH 26/69] Add missing licence headers --- osu.Game/Database/ArchiveModelManager.cs | 5 ++++- osu.Game/Database/ICanAcceptFiles.cs | 5 ++++- osu.Game/Database/IHasFiles.cs | 5 ++++- osu.Game/Database/INamedFileInfo.cs | 5 ++++- osu.Game/Database/MutableDatabaseBackedStore.cs | 5 ++++- osu.Game/Online/API/APIDownloadRequest.cs | 7 +++++-- 6 files changed, 25 insertions(+), 7 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 20f90a248b..902a42c172 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -1,4 +1,7 @@ -using System; +// 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.IO; using System.Linq; diff --git a/osu.Game/Database/ICanAcceptFiles.cs b/osu.Game/Database/ICanAcceptFiles.cs index 40978277b1..ab26525619 100644 --- a/osu.Game/Database/ICanAcceptFiles.cs +++ b/osu.Game/Database/ICanAcceptFiles.cs @@ -1,4 +1,7 @@ -namespace osu.Game.Database +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Database { /// /// A class which can accept files for importing. diff --git a/osu.Game/Database/IHasFiles.cs b/osu.Game/Database/IHasFiles.cs index cae8ea66ef..deaf75360c 100644 --- a/osu.Game/Database/IHasFiles.cs +++ b/osu.Game/Database/IHasFiles.cs @@ -1,4 +1,7 @@ -using System.Collections.Generic; +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; namespace osu.Game.Database { diff --git a/osu.Game/Database/INamedFileInfo.cs b/osu.Game/Database/INamedFileInfo.cs index 7922c72974..8de451dd78 100644 --- a/osu.Game/Database/INamedFileInfo.cs +++ b/osu.Game/Database/INamedFileInfo.cs @@ -1,4 +1,7 @@ -using osu.Game.IO; +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.IO; namespace osu.Game.Database { diff --git a/osu.Game/Database/MutableDatabaseBackedStore.cs b/osu.Game/Database/MutableDatabaseBackedStore.cs index 95d3dfc582..4ab55691f2 100644 --- a/osu.Game/Database/MutableDatabaseBackedStore.cs +++ b/osu.Game/Database/MutableDatabaseBackedStore.cs @@ -1,4 +1,7 @@ -using System; +// 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 System.Linq.Expressions; diff --git a/osu.Game/Online/API/APIDownloadRequest.cs b/osu.Game/Online/API/APIDownloadRequest.cs index f1cbd1eb0b..2dff07a847 100644 --- a/osu.Game/Online/API/APIDownloadRequest.cs +++ b/osu.Game/Online/API/APIDownloadRequest.cs @@ -1,4 +1,7 @@ -using osu.Framework.IO.Network; +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.IO.Network; namespace osu.Game.Online.API { @@ -27,4 +30,4 @@ namespace osu.Game.Online.API public new event APISuccessHandler Success; } -} \ No newline at end of file +} From dcbc8c3dcd452487fd3ec75d7690374030c9fd5f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Feb 2018 18:06:33 +0900 Subject: [PATCH 27/69] Remove animated capture boxes for now --- .../Edit/Layers/Selection/CaptureBox.cs | 58 ++----------------- .../Edit/Layers/Selection/SelectionBox.cs | 2 +- .../Edit/Layers/Selection/SelectionLayer.cs | 11 ++-- 3 files changed, 11 insertions(+), 60 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs index 86e96f6d84..35e425c981 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs @@ -15,22 +15,12 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// /// A box which encloses s. /// - public abstract class CaptureBox : VisibilityContainer + public class CaptureBox : VisibilityContainer { - /// - /// Top-left corner of the rectangle that encloses the s. - /// - protected Vector2 FinalPosition { get; private set; } - - /// - /// Size of the rectangle that encloses the s. - /// - protected Vector2 FinalSize { get; private set; } - private readonly IDrawable captureArea; private readonly IReadOnlyList capturedObjects; - protected CaptureBox(IDrawable captureArea, IReadOnlyList capturedObjects) + public CaptureBox(IDrawable captureArea, IReadOnlyList capturedObjects) { this.captureArea = captureArea; this.capturedObjects = capturedObjects; @@ -66,48 +56,12 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection topLeft -= new Vector2(5); bottomRight += new Vector2(5); - FinalSize = bottomRight - topLeft; - FinalPosition = topLeft; + Size = bottomRight - topLeft; + Position = topLeft; } - protected override void PopIn() => this.MoveTo(FinalPosition).ResizeTo(FinalSize).FadeIn(); + protected override void PopIn() => this.FadeIn(); + protected override void PopOut() => this.FadeOut(); } - - /// - /// A which fully encloses the s from the start. - /// - public class InstantCaptureBox : CaptureBox - { - public InstantCaptureBox(IDrawable captureArea, IReadOnlyList capturedObjects) - : base(captureArea, capturedObjects) - { - Origin = Anchor.Centre; - } - - protected override void PopIn() - => this.MoveTo(FinalPosition + FinalSize / 2f).ResizeTo(FinalSize).ScaleTo(1.1f) - .Then() - .ScaleTo(1f, 300, Easing.OutQuint).FadeIn(300, Easing.OutQuint); - - protected override void PopOut() => this.FadeOut(300, Easing.OutQuint); - } - - /// - /// A which moves from an initial position + size to enclose s. - /// - public class DragCaptureBox : CaptureBox - { - public DragCaptureBox(IDrawable captureArea, IReadOnlyList capturedObjects, Vector2 initialPosition, Vector2 initialSize) - : base(captureArea, capturedObjects) - { - Position = initialPosition; - Size = initialSize; - } - - protected override void PopIn() - => this.MoveTo(FinalPosition, 300, Easing.OutQuint).ResizeTo(FinalSize, 300, Easing.OutQuint).FadeIn(300, Easing.OutQuint); - - protected override void PopOut() => this.FadeOut(300, Easing.OutQuint); - } } diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs index 87c6833f01..8907993173 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs @@ -52,6 +52,6 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection Size = bottomRight - topLeft; } - public override void Hide() => this.FadeOut(400, Easing.OutQuint).Expire(); + public override void Hide() => this.FadeOut(250, Easing.OutQuint).Expire(); } } diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index 0fceced040..f73820d534 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnDragEnd(InputState state) { selectionBox.Hide(); - finishSelection(true); + finishSelection(); return true; } @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnClick(InputState state) { selectPoint(state.Mouse.NativeState.Position); - finishSelection(false); + finishSelection(); return true; } @@ -102,7 +102,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection selectedHitObjects.Add(selected); } - private void finishSelection(bool fromDrag) + private void finishSelection() { if (selectedHitObjects.Count == 0) return; @@ -111,10 +111,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection // OnDragEnd and OnClick methods within a single frame, OnMouseDown doesn't help us here captureBox?.Hide(); - if (fromDrag) - AddInternal(captureBox = new DragCaptureBox(this, selectedHitObjects.ToList(), selectionBox.Position, selectionBox.Size)); - else - AddInternal(captureBox = new InstantCaptureBox(this, selectedHitObjects.ToList())); + AddInternal(captureBox = new CaptureBox(this, selectedHitObjects.ToList())); } } } From 4ac0c48ba6229df35ae2850364aaa2ba599bc42e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Feb 2018 19:07:53 +0900 Subject: [PATCH 28/69] Add fadein to SelectionBox --- osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs | 5 +++-- osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs | 4 ++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs index 8907993173..e0e52a41bc 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs @@ -12,7 +12,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// /// A box that represents a drag selection. /// - public class SelectionBox : CompositeDrawable + public class SelectionBox : VisibilityContainer { /// /// Creates a new . @@ -52,6 +52,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection Size = bottomRight - topLeft; } - public override void Hide() => this.FadeOut(250, Easing.OutQuint).Expire(); + 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/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index f73820d534..e7acf05345 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -43,6 +43,8 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnDrag(InputState state) { + selectionBox.Show(); + var dragPosition = state.Mouse.NativeState.Position; var dragStartPosition = state.Mouse.NativeState.PositionMouseDown ?? dragPosition; @@ -57,6 +59,8 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection protected override bool OnDragEnd(InputState state) { selectionBox.Hide(); + selectionBox.Expire(); + finishSelection(); return true; From 95831915565a117f53ac38f3afb0b53ac919d04a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Feb 2018 19:08:03 +0900 Subject: [PATCH 29/69] Dispose SelectionBoxes when they die --- osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs index e0e52a41bc..8eee15d0b2 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs @@ -52,6 +52,8 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection 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); } From ebcdb625702ac2f0711b0551ce2f4d8fe51e15f2 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Feb 2018 19:10:55 +0900 Subject: [PATCH 30/69] Remove now unneeded hide --- osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index e7acf05345..af6a4144fd 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -111,10 +111,6 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection if (selectedHitObjects.Count == 0) return; - // Due to https://github.com/ppy/osu-framework/issues/1382, we may get here through both - // OnDragEnd and OnClick methods within a single frame, OnMouseDown doesn't help us here - captureBox?.Hide(); - AddInternal(captureBox = new CaptureBox(this, selectedHitObjects.ToList())); } } From cbac4de9c2a3207fe40144abfc8cadf95fe1f4bd Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 15 Feb 2018 19:11:29 +0900 Subject: [PATCH 31/69] Expire + dispose CaptureBoxes --- osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs | 3 ++- osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs index 35e425c981..48f6393ccc 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs @@ -60,8 +60,9 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection Position = topLeft; } - protected override void PopIn() => this.FadeIn(); + 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/Rulesets/Edit/Layers/Selection/SelectionLayer.cs index af6a4144fd..bda613f617 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionLayer.cs @@ -81,6 +81,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection { selectedHitObjects.Clear(); captureBox?.Hide(); + captureBox?.Expire(); } /// From ddf49c2e65e6e2af48c7ebfd3d69df4eb26387c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Feb 2018 10:45:15 +0900 Subject: [PATCH 32/69] Fix intro not being replaced by a playable song when entering song select --- osu.Game/Screens/Select/SongSelect.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index f35768d933..de6847d866 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -448,7 +448,7 @@ namespace osu.Game.Screens.Select private void carouselBeatmapsLoaded() { - if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false) + if (!Beatmap.IsDefault && Beatmap.Value.BeatmapSetInfo?.DeletePending == false && Beatmap.Value.BeatmapSetInfo?.Protected == false) { Carousel.SelectBeatmap(Beatmap.Value.BeatmapInfo); } From 21b641b302e8952e23cb6245022c976530d7b028 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Feb 2018 12:07:59 +0900 Subject: [PATCH 33/69] Give storyboards a BeatmapInfo to reduce weird method calls Also removes unnecessary background texture (the actual storyboard background spec wasn't implemented correctly anyway). --- .../Beatmaps/BeatmapManager_WorkingBeatmap.cs | 19 ++++++++++---- osu.Game/Beatmaps/WorkingBeatmap.cs | 2 +- .../Drawables/DrawableStoryboard.cs | 18 ------------- osu.Game/Storyboards/Storyboard.cs | 26 ++++++++----------- 4 files changed, 26 insertions(+), 39 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 14a4028b44..07d05f470e 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -75,23 +75,32 @@ namespace osu.Game.Beatmaps protected override Storyboard GetStoryboard() { + Storyboard storyboard; try { using (var beatmap = new StreamReader(store.GetStream(getPathForFile(BeatmapInfo.Path)))) { Decoder decoder = Decoder.GetDecoder(beatmap); - if (BeatmapSetInfo?.StoryboardFile == null) - return decoder.GetStoryboardDecoder().DecodeStoryboard(beatmap); + // todo: support loading from both set-wide storyboard *and* baetmap specific. - using (var storyboard = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) - return decoder.GetStoryboardDecoder().DecodeStoryboard(beatmap, storyboard); + if (BeatmapSetInfo?.StoryboardFile == null) + storyboard = decoder.GetStoryboardDecoder().DecodeStoryboard(beatmap); + else + { + using (var reader = new StreamReader(store.GetStream(getPathForFile(BeatmapSetInfo.StoryboardFile)))) + storyboard = decoder.GetStoryboardDecoder().DecodeStoryboard(beatmap, reader); + } } } catch { - return new Storyboard(); + storyboard = new Storyboard(); } + + storyboard.BeatmapInfo = BeatmapInfo; + + return storyboard; } } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index c633b94951..8a2a7b01a1 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -57,7 +57,7 @@ namespace osu.Game.Beatmaps protected abstract Texture GetBackground(); protected abstract Track GetTrack(); protected virtual Waveform GetWaveform() => new Waveform(); - protected virtual Storyboard GetStoryboard() => new Storyboard(); + protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo }; public bool BeatmapLoaded => beatmap.IsResultAvailable; public Beatmap Beatmap => beatmap.Value.Result; diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index 2489369493..aaeaaabd55 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -5,7 +5,6 @@ using OpenTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.IO; @@ -15,13 +14,6 @@ namespace osu.Game.Storyboards.Drawables { public Storyboard Storyboard { get; private set; } - private readonly Background background; - public Texture BackgroundTexture - { - get { return background.Texture; } - set { background.Texture = value; } - } - private readonly Container content; protected override Container Content => content; @@ -52,11 +44,6 @@ namespace osu.Game.Storyboards.Drawables Anchor = Anchor.Centre; Origin = Anchor.Centre; - AddInternal(background = new Background - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }); AddInternal(content = new Container { Size = new Vector2(640, 480), @@ -79,10 +66,5 @@ namespace osu.Game.Storyboards.Drawables foreach (var layer in Children) layer.Enabled = passing ? layer.Layer.EnabledWhenPassing : layer.Layer.EnabledWhenFailing; } - - private class Background : Sprite - { - protected override Vector2 DrawScale => Texture != null ? new Vector2(Parent.DrawHeight / Texture.DisplayHeight) : base.DrawScale; - } } } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index e2587debc9..9d4efadc81 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.cs @@ -14,6 +14,8 @@ namespace osu.Game.Storyboards private readonly Dictionary layers = new Dictionary(); public IEnumerable Layers => layers.Values; + public BeatmapInfo BeatmapInfo = new BeatmapInfo(); + public bool HasDrawable => Layers.Any(l => l.Elements.Any(e => e.IsDrawable)); public Storyboard() @@ -36,28 +38,22 @@ namespace osu.Game.Storyboards /// /// Whether the beatmap's background should be hidden while this storyboard is being displayed. /// - public bool ReplacesBackground(BeatmapInfo beatmapInfo) + public bool ReplacesBackground { - var backgroundPath = beatmapInfo.BeatmapSet?.Metadata?.BackgroundFile?.ToLowerInvariant(); - if (backgroundPath == null) - return false; + get + { + var backgroundPath = BeatmapInfo.BeatmapSet?.Metadata?.BackgroundFile?.ToLowerInvariant(); + if (backgroundPath == null) + return false; - return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); + return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); + } } - public float AspectRatio(BeatmapInfo beatmapInfo) - => beatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f; - public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) { var drawable = new DrawableStoryboard(this); - if (working != null) - { - var beatmapInfo = working.Beatmap.BeatmapInfo; - drawable.Width = drawable.Height * AspectRatio(beatmapInfo); - if (!ReplacesBackground(beatmapInfo)) - drawable.BackgroundTexture = working.Background; - } + drawable.Width = drawable.Height * (BeatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f); return drawable; } From 29adedfa96bf0882f4f54b4727069eca8a35cbac Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Feb 2018 11:17:28 +0900 Subject: [PATCH 34/69] Collapse visual settings by default in player --- .../Screens/Play/HUD/PlayerSettingsOverlay.cs | 2 +- .../PlayerSettings/PlayerSettingsGroup.cs | 41 +++++++++++-------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs index e6cf1f7982..5dba10ffc1 100644 --- a/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs +++ b/osu.Game/Screens/Play/HUD/PlayerSettingsOverlay.cs @@ -39,7 +39,7 @@ namespace osu.Game.Screens.Play.HUD //CollectionSettings = new CollectionSettings(), //DiscussionSettings = new DiscussionSettings(), PlaybackSettings = new PlaybackSettings(), - VisualSettings = new VisualSettings() + VisualSettings = new VisualSettings { Expanded = false } } }; diff --git a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs index e8a4bc6b27..95b464154a 100644 --- a/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs +++ b/osu.Game/Screens/Play/PlayerSettings/PlayerSettingsGroup.cs @@ -31,6 +31,28 @@ namespace osu.Game.Screens.Play.PlayerSettings private bool expanded = true; + public bool Expanded + { + get { return expanded; } + set + { + if (expanded == value) return; + expanded = value; + + content.ClearTransforms(); + + if (expanded) + content.AutoSizeAxes = Axes.Y; + else + { + content.AutoSizeAxes = Axes.None; + content.ResizeHeightTo(0, transition_duration, Easing.OutQuint); + } + + button.FadeColour(expanded ? buttonActiveColour : Color4.White, 200, Easing.OutQuint); + } + } + private Color4 buttonActiveColour; protected PlayerSettingsGroup() @@ -82,7 +104,7 @@ namespace osu.Game.Screens.Play.PlayerSettings Position = new Vector2(-15, 0), Icon = FontAwesome.fa_bars, Scale = new Vector2(0.75f), - Action = toggleContentVisibility, + Action = () => Expanded = !Expanded, }, } }, @@ -111,22 +133,5 @@ namespace osu.Game.Screens.Play.PlayerSettings } protected override Container Content => content; - - private void toggleContentVisibility() - { - content.ClearTransforms(); - - expanded = !expanded; - - if (expanded) - content.AutoSizeAxes = Axes.Y; - else - { - content.AutoSizeAxes = Axes.None; - content.ResizeHeightTo(0, transition_duration, Easing.OutQuint); - } - - button.FadeColour(expanded ? buttonActiveColour : Color4.White, 200, Easing.OutQuint); - } } } From 75dcf72520fe3b1a8eb50130e4bc72a1f85f9177 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 16 Feb 2018 13:47:30 +0900 Subject: [PATCH 35/69] Improve testability of API --- osu.Game/Online/API/APIAccess.cs | 5 ++-- osu.Game/Online/API/DummyAPIAccess.cs | 31 ++++++++++++++++++++++++ osu.Game/Online/API/IAPIProvider.cs | 34 +++++++++++++++++++++++++++ osu.Game/OsuGameBase.cs | 1 + osu.Game/osu.Game.csproj | 2 ++ 5 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Online/API/DummyAPIAccess.cs create mode 100644 osu.Game/Online/API/IAPIProvider.cs diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 1d657b8664..90f3999ddd 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -7,7 +7,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Net; using System.Threading; -using osu.Framework; using osu.Framework.Configuration; using osu.Framework.Logging; using osu.Framework.Threading; @@ -16,7 +15,7 @@ using osu.Game.Users; namespace osu.Game.Online.API { - public class APIAccess : IUpdateable + public class APIAccess : IAPIProvider { private readonly OAuth authentication; @@ -34,7 +33,7 @@ namespace osu.Game.Online.API public string Password; - public Bindable LocalUser = new Bindable(createGuestUser()); + public Bindable LocalUser { get; } = new Bindable(createGuestUser()); public string Token { diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs new file mode 100644 index 0000000000..fc0dc0ef8b --- /dev/null +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -0,0 +1,31 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Configuration; +using osu.Game.Users; + +namespace osu.Game.Online.API +{ + public class DummyAPIAccess : IAPIProvider + { + public Bindable LocalUser { get; } = new Bindable(new User + { + Username = @"Dummy", + Id = 1, + }); + + public bool IsLoggedIn => true; + + public void Update() + { + } + + public virtual void Queue(APIRequest request) + { + } + + public void Register(IOnlineComponent component) + { + } + } +} diff --git a/osu.Game/Online/API/IAPIProvider.cs b/osu.Game/Online/API/IAPIProvider.cs new file mode 100644 index 0000000000..b3c8774209 --- /dev/null +++ b/osu.Game/Online/API/IAPIProvider.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework; +using osu.Framework.Configuration; +using osu.Game.Users; + +namespace osu.Game.Online.API +{ + public interface IAPIProvider : IUpdateable + { + /// + /// The local user. + /// + Bindable LocalUser { get; } + + /// + /// Returns whether the local user is logged in. + /// + bool IsLoggedIn { get; } + + /// + /// Queue a new request. + /// + /// The request to perform. + void Queue(APIRequest request); + + /// + /// Register a component to receive state changes. + /// + /// The component to register. + void Register(IOnlineComponent component); + } +} diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index 505577416d..b5d7836f49 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -105,6 +105,7 @@ namespace osu.Game Username = LocalConfig.Get(OsuSetting.Username), Token = LocalConfig.Get(OsuSetting.Token) }); + dependencies.CacheAs(API); dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 02801eb81f..42e54472d1 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -281,6 +281,8 @@ + + From e0faf14a3e6ac549608acc2f304abeeb4e381974 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Feb 2018 12:33:07 +0900 Subject: [PATCH 36/69] Actually consume ReplacesBackground --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 5df88b2b23..4954618ef9 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -387,7 +387,7 @@ namespace osu.Game.Screens.Play .FadeTo(storyboardVisible && opacity > 0 ? 1 : 0, duration, Easing.OutQuint); (Background as BackgroundScreenBeatmap)?.BlurTo(new Vector2((float)blurLevel.Value * 25), duration, Easing.OutQuint); - Background?.FadeTo(!storyboardVisible || beatmap.Background == null ? opacity : 0, duration, Easing.OutQuint); + Background?.FadeTo(beatmap.Background != null && (!storyboardVisible || !beatmap.Storyboard.ReplacesBackground) ? opacity : 0, duration, Easing.OutQuint); } private void fadeOut() From a7915e70415a5c49f13ba5c2a4ccbb8781008dd0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Feb 2018 14:00:48 +0900 Subject: [PATCH 37/69] Fix typo --- osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs index 07d05f470e..a72c1adfcd 100644 --- a/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/BeatmapManager_WorkingBeatmap.cs @@ -82,7 +82,7 @@ namespace osu.Game.Beatmaps { Decoder decoder = Decoder.GetDecoder(beatmap); - // todo: support loading from both set-wide storyboard *and* baetmap specific. + // todo: support loading from both set-wide storyboard *and* beatmap specific. if (BeatmapSetInfo?.StoryboardFile == null) storyboard = decoder.GetStoryboardDecoder().DecodeStoryboard(beatmap); From 8b89735e9e5ee84a7d504c0a73b169484812ba0c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Feb 2018 14:17:41 +0900 Subject: [PATCH 38/69] Improve xmldoc for DatabaseContextFactory.Get --- osu.Game/Database/DatabaseContextFactory.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Database/DatabaseContextFactory.cs b/osu.Game/Database/DatabaseContextFactory.cs index 2068d6bd8a..712ed2d0cc 100644 --- a/osu.Game/Database/DatabaseContextFactory.cs +++ b/osu.Game/Database/DatabaseContextFactory.cs @@ -26,7 +26,8 @@ namespace osu.Game.Database } /// - /// Get a context for read-only usage. + /// Get a context for the current thread for read-only usage. + /// If a is in progress, the existing write-safe context will be returned. /// public OsuDbContext Get() => threadContexts.Value; From 57e61b0b0e49fc6654fbcab291ea81a2841a396c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Feb 2018 14:50:42 +0900 Subject: [PATCH 39/69] Update xmldoc --- osu.Game/Database/ArchiveModelManager.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 902a42c172..a65593ff82 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -304,8 +304,8 @@ namespace osu.Game.Database /// Create a barebones model from the provided archive. /// Actual expensive population should be done in ; this should just prepare for duplicate checking. /// - /// - /// + /// The archive to create the model for. + /// A model populated with minimal information. protected abstract TModel CreateModel(ArchiveReader archive); /// From d70d40e3b8f5d6919b030281df21847c9eceffdb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 19 Feb 2018 15:52:14 +0900 Subject: [PATCH 40/69] Add back forgotten score store to valid importers --- osu.Game/OsuGameBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index de2a4d0b82..8974275da2 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -118,6 +118,7 @@ namespace osu.Game dependencies.Cache(new OsuColour()); fileImporters.Add(BeatmapManager); + fileImporters.Add(ScoreStore); //this completely overrides the framework default. will need to change once we make a proper FontStore. dependencies.Cache(Fonts = new FontStore { ScaleAdjust = 100 }); From ef11ce3dd121a2eabfbffb10c9d655705d087a77 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Feb 2018 17:02:27 +0900 Subject: [PATCH 41/69] Remove Size override from OsuPlayfield --- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 17 ++--------------- osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs | 6 +++++- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 17521f8992..7f8cbce78e 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -27,21 +27,8 @@ namespace osu.Game.Rulesets.Osu.UI public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); - public override Vector2 Size - { - get - { - if (Parent == null) - return Vector2.Zero; - - var parentSize = Parent.DrawSize; - var aspectSize = parentSize.X * 0.75f < parentSize.Y ? new Vector2(parentSize.X, parentSize.X * 0.75f) : new Vector2(parentSize.Y * 4f / 3f, parentSize.Y); - - return new Vector2(aspectSize.X / parentSize.X, aspectSize.Y / parentSize.Y) * base.Size; - } - } - - public OsuPlayfield() : base(BASE_SIZE.X) + public OsuPlayfield() + : base(BASE_SIZE.X) { Anchor = Anchor.Centre; Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index 526348062f..07b59c1ef7 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -50,7 +50,11 @@ namespace osu.Game.Rulesets.Osu.UI protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay); - protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f); + protected override Vector2 GetPlayfieldAspectAdjust() + { + var aspectSize = DrawSize.X * 0.75f < DrawSize.Y ? new Vector2(DrawSize.X, DrawSize.X * 0.75f) : new Vector2(DrawSize.Y * 4f / 3f, DrawSize.Y); + return new Vector2(aspectSize.X / DrawSize.X, aspectSize.Y / DrawSize.Y) * 0.75f; + } protected override CursorContainer CreateCursor() => new GameplayCursor(); } From cd2c9a9de69a7af6d50b6c9c820da7621b0cdb7d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Feb 2018 17:04:18 +0900 Subject: [PATCH 42/69] Adjust xmldoc and rename to GetAspectAdjustedSize --- osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs | 2 +- osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs | 2 +- osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs | 2 +- osu.Game/Rulesets/UI/RulesetContainer.cs | 9 +++++---- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 436d5c1ea6..3c9647117e 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.UI return null; } - protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(1, 0.8f); + protected override Vector2 GetAspectAdjustedSize() => new Vector2(1, 0.8f); protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay, this); diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index 07b59c1ef7..9cb6a13cb2 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -50,7 +50,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay); - protected override Vector2 GetPlayfieldAspectAdjust() + protected override Vector2 GetAspectAdjustedSize() { var aspectSize = DrawSize.X * 0.75f < DrawSize.Y ? new Vector2(DrawSize.X, DrawSize.X * 0.75f) : new Vector2(DrawSize.Y * 4f / 3f, DrawSize.Y); return new Vector2(aspectSize.X / DrawSize.X, aspectSize.Y / DrawSize.Y) * 0.75f; diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index 1b9821d698..8342009e80 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -78,7 +78,7 @@ namespace osu.Game.Rulesets.Taiko.UI } } - protected override Vector2 GetPlayfieldAspectAdjust() + protected override Vector2 GetAspectAdjustedSize() { const float default_relative_height = TaikoPlayfield.DEFAULT_HEIGHT / 768; const float default_aspect = 16f / 9f; diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 231250e858..8f91c3fcf2 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -324,7 +324,7 @@ namespace osu.Game.Rulesets.UI { base.Update(); - Playfield.Size = AspectAdjust ? GetPlayfieldAspectAdjust() : Vector2.One; + Playfield.Size = AspectAdjust ? GetAspectAdjustedSize() : Vector2.One; } /// @@ -335,10 +335,11 @@ namespace osu.Game.Rulesets.UI protected virtual BeatmapProcessor CreateBeatmapProcessor() => new BeatmapProcessor(); /// - /// In some cases we want to apply changes to the relative size of our contained based on custom conditions. + /// Computes the final size of the in relative coordinate space after all + /// aspect and scale adjustments. /// - /// - protected virtual Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f); //a sane default + /// The aspect-adjusted size. + protected virtual Vector2 GetAspectAdjustedSize() => new Vector2(0.75f); // A sane default /// /// Creates a converter to convert Beatmap to a specific mode. From b7be162f28ab16cad692876ed5ebf9442bca2d30 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Mon, 19 Feb 2018 17:05:10 +0900 Subject: [PATCH 43/69] Remove AspectAdjust property (override GetAspectAdjustedSize instead) --- osu.Game/Rulesets/UI/RulesetContainer.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 8f91c3fcf2..f4e700a8eb 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -33,11 +33,6 @@ namespace osu.Game.Rulesets.UI /// public abstract class RulesetContainer : Container { - /// - /// Whether to apply adjustments to the child based on our own size. - /// - public bool AspectAdjust = true; - /// /// The selected variant. /// @@ -324,7 +319,7 @@ namespace osu.Game.Rulesets.UI { base.Update(); - Playfield.Size = AspectAdjust ? GetAspectAdjustedSize() : Vector2.One; + Playfield.Size = GetAspectAdjustedSize(); } /// From 423fe4eba85876df212c17648adb74b02e075d34 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Feb 2018 14:25:42 +0900 Subject: [PATCH 44/69] Combine border radiuses of selection and capture boxes --- .../Edit/Layers/Selection/CaptureBox.cs | 2 +- .../Edit/Layers/Selection/SelectionBox.cs | 29 ++++++------------- 2 files changed, 10 insertions(+), 21 deletions(-) diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs index 48f6393ccc..269dd79bf7 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/CaptureBox.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection this.capturedObjects = capturedObjects; Masking = true; - BorderThickness = 3; + BorderThickness = SelectionBox.BORDER_RADIUS; InternalChild = new Box { diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs index 8eee15d0b2..1c25846ee3 100644 --- a/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs +++ b/osu.Game/Rulesets/Edit/Layers/Selection/SelectionBox.cs @@ -14,32 +14,21 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection /// public class SelectionBox : VisibilityContainer { + public const float BORDER_RADIUS = 2; + /// /// Creates a new . /// public SelectionBox() { - InternalChildren = new Drawable[] + Masking = true; + BorderColour = Color4.White; + BorderThickness = BORDER_RADIUS; + + Child = new Box { - new Container - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(-1), - Child = new Container - { - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderColour = Color4.White, - BorderThickness = 2, - MaskingSmoothness = 1, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f, - AlwaysPresent = true - }, - } - } + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f }; } From 8270e4d758728125c2ebaa24aaccdb57a303cf99 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Feb 2018 20:51:28 +0900 Subject: [PATCH 45/69] Add BorderColour to SliderBody --- .../Objects/Drawables/Pieces/SliderBody.cs | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 89af67ba2a..f5910c8a02 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -51,6 +51,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } + private Color4 borderColour = Color4.White; + /// + /// Used to colour the path border. + /// + public new Color4 BorderColour + { + get { return borderColour; } + set + { + if (borderColour == value) + return; + borderColour = value; + + if (LoadState == LoadState.Ready) + Schedule(reloadTexture); + } + } + public Quad PathDrawQuad => container.ScreenSpaceDrawQuad; private int textureWidth => (int)PathWidth * 2; @@ -130,10 +148,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (progress <= border_portion) { - bytes[i * 4] = 255; - bytes[i * 4 + 1] = 255; - bytes[i * 4 + 2] = 255; - bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * 255); + bytes[i * 4] = (byte)(BorderColour.R * 255); + bytes[i * 4 + 1] = (byte)(BorderColour.G * 255); + bytes[i * 4 + 2] = (byte)(BorderColour.B * 255); + bytes[i * 4 + 3] = (byte)(Math.Min(progress / aa_portion, 1) * (BorderColour.A * 255)); } else { From ee055b8e5c36d0a92fbef03c0e7779410d2a7fe3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Feb 2018 20:52:12 +0900 Subject: [PATCH 46/69] Allow customizing the snake-ability of sliders --- .../Objects/Drawables/DrawableSlider.cs | 9 +++++++++ .../Objects/Drawables/Pieces/SliderBody.cs | 18 +++++++----------- 2 files changed, 16 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 41df7ae4a4..a15c6ccd79 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -7,9 +7,11 @@ using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using System.Collections.Generic; 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.Objects.Types; using osu.Game.Rulesets.Scoring; @@ -87,6 +89,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + [BackgroundDependencyLoader] + private void load(OsuConfigManager config) + { + config.BindWith(OsuSetting.SnakingInSliders, Body.SnakingIn); + config.BindWith(OsuSetting.SnakingOutSliders, Body.SnakingOut); + } + private int currentSpan; public bool Tracking; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index f5910c8a02..31ef1c05e6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.OpenGL.Textures; using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Textures; -using osu.Game.Configuration; using OpenTK; using OpenTK.Graphics.ES30; using OpenTK.Graphics; @@ -30,6 +29,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces set { path.PathWidth = value; } } + public readonly Bindable SnakingIn = new Bindable(); + public readonly Bindable SnakingOut = new Bindable(); + public double? SnakedStart { get; private set; } public double? SnakedEnd { get; private set; } @@ -115,15 +117,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } - private Bindable snakingIn; - private Bindable snakingOut; - [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load() { - snakingIn = config.GetBindable(OsuSetting.SnakingInSliders); - snakingOut = config.GetBindable(OsuSetting.SnakingOutSliders); - reloadTexture(); } @@ -188,18 +184,18 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces public void UpdateProgress(double progress, int span) { double start = 0; - double end = snakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1; + double end = SnakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1; if (span >= slider.SpanCount() - 1) { if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1) { start = 0; - end = snakingOut ? progress : 1; + end = SnakingOut ? progress : 1; } else { - start = snakingOut ? progress : 0; + start = SnakingOut ? progress : 0; } } From 7fd7dc153846d4d8eb549d65f544ee84c35ab473 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Feb 2018 13:50:31 +0900 Subject: [PATCH 47/69] Extract playfield scaling into a separate class And make it more general. --- osu.Game/Rulesets/UI/Playfield.cs | 52 +++-------- osu.Game/Rulesets/UI/ScalableContainer.cs | 86 +++++++++++++++++++ .../UI/Scrolling/ScrollingPlayfield.cs | 11 ++- osu.Game/osu.Game.csproj | 1 + 4 files changed, 105 insertions(+), 45 deletions(-) create mode 100644 osu.Game/Rulesets/UI/ScalableContainer.cs diff --git a/osu.Game/Rulesets/UI/Playfield.cs b/osu.Game/Rulesets/UI/Playfield.cs index a7fed7059b..bbf20c2c26 100644 --- a/osu.Game/Rulesets/UI/Playfield.cs +++ b/osu.Game/Rulesets/UI/Playfield.cs @@ -3,52 +3,37 @@ using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Drawables; -using OpenTK; using osu.Framework.Allocation; namespace osu.Game.Rulesets.UI { - public abstract class Playfield : Container + public abstract class Playfield : ScalableContainer { /// /// The HitObjects contained in this Playfield. /// public HitObjectContainer HitObjects { get; private set; } - public Container ScaledContent; - - protected override Container Content => content; - private readonly Container content; - - private List nestedPlayfields; - /// /// All the s nested inside this playfield. /// public IReadOnlyList NestedPlayfields => nestedPlayfields; + private List nestedPlayfields; /// /// A container for keeping track of DrawableHitObjects. /// - /// Whether we want our internal coordinate system to be scaled to a specified width. - protected Playfield(float? customWidth = null) + /// The width to scale the internal coordinate space to. + /// May be null if scaling based on is desired. If is also null, no scaling will occur. + /// + /// The height to scale the internal coordinate space to. + /// May be null if scaling based on is desired. If is also null, no scaling will occur. + /// + protected Playfield(float? customWidth = null, float? customHeight = null) + : base(customWidth, customHeight) { RelativeSizeAxes = Axes.Both; - - AddInternal(ScaledContent = new ScaledContainer - { - CustomWidth = customWidth, - RelativeSizeAxes = Axes.Both, - Children = new[] - { - content = new Container - { - RelativeSizeAxes = Axes.Both, - } - } - }); } [BackgroundDependencyLoader] @@ -94,22 +79,5 @@ namespace osu.Game.Rulesets.UI /// Creates the container that will be used to contain the s. /// protected virtual HitObjectContainer CreateHitObjectContainer() => new HitObjectContainer(); - - private class ScaledContainer : Container - { - /// - /// A value (in game pixels that we should scale our content to match). - /// - public float? CustomWidth; - - //dividing by the customwidth will effectively scale our content to the required container size. - protected override Vector2 DrawScale => CustomWidth.HasValue ? new Vector2(DrawSize.X / CustomWidth.Value) : base.DrawScale; - - protected override void Update() - { - base.Update(); - RelativeChildSize = new Vector2(DrawScale.X, RelativeChildSize.Y); - } - } } } diff --git a/osu.Game/Rulesets/UI/ScalableContainer.cs b/osu.Game/Rulesets/UI/ScalableContainer.cs new file mode 100644 index 0000000000..e1c1427470 --- /dev/null +++ b/osu.Game/Rulesets/UI/ScalableContainer.cs @@ -0,0 +1,86 @@ +// 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 OpenTK; + +namespace osu.Game.Rulesets.UI +{ + /// + /// A which can have its internal coordinate system scaled to a specific size. + /// + public class ScalableContainer : Container + { + /// + /// The scaled content. + /// + public readonly Container ScaledContent; + + protected override Container Content => content; + private readonly Container content; + + /// + /// A which can have its internal coordinate system scaled to a specific size. + /// + /// The width to scale the internal coordinate space to. + /// May be null if scaling based on is desired. If is also null, no scaling will occur. + /// + /// The height to scale the internal coordinate space to. + /// May be null if scaling based on is desired. If is also null, no scaling will occur. + /// + public ScalableContainer(float? customWidth = null, float? customHeight = null) + { + AddInternal(ScaledContent = new ScaledContainer + { + CustomWidth = customWidth, + CustomHeight = customHeight, + RelativeSizeAxes = Axes.Both, + Child = content = new Container { RelativeSizeAxes = Axes.Both } + }); + } + + public class ScaledContainer : Container + { + /// + /// The value to scale the width of the content to match. + /// If null, is used. + /// + public float? CustomWidth; + + /// + /// The value to scale the height of the content to match. + /// if null, is used. + /// + public float? CustomHeight; + + /// + /// The scale that is required for the size of the content to match and . + /// + private Vector2 sizeScale + { + get + { + if (CustomWidth.HasValue && CustomHeight.HasValue) + return Vector2.Divide(DrawSize, new Vector2(CustomWidth.Value, CustomHeight.Value)); + if (CustomWidth.HasValue) + return new Vector2(DrawSize.X / CustomWidth.Value); + if (CustomHeight.HasValue) + return new Vector2(DrawSize.Y / CustomHeight.Value); + return Vector2.One; + } + } + + /// + /// Scale the content to the required container size by multiplying by . + /// + protected override Vector2 DrawScale => sizeScale * base.DrawScale; + + protected override void Update() + { + base.Update(); + RelativeChildSize = sizeScale; + } + } + } +} diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs index e168f6daec..1c1c8f7f61 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingPlayfield.cs @@ -62,9 +62,14 @@ namespace osu.Game.Rulesets.UI.Scrolling /// Creates a new . /// /// The direction in which s in this container should scroll. - /// Whether we want our internal coordinate system to be scaled to a specified width - protected ScrollingPlayfield(ScrollingDirection direction, float? customWidth = null) - : base(customWidth) + /// The width to scale the internal coordinate space to. + /// May be null if scaling based on is desired. If is also null, no scaling will occur. + /// + /// The height to scale the internal coordinate space to. + /// May be null if scaling based on is desired. If is also null, no scaling will occur. + /// + protected ScrollingPlayfield(ScrollingDirection direction, float? customWidth = null, float? customHeight = null) + : base(customWidth, customHeight) { this.direction = direction; } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 39261fbe57..afeb791029 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -357,6 +357,7 @@ + From 6b8b39abc51bb07062ef22b92fa18ba9cbc3468a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Feb 2018 13:50:55 +0900 Subject: [PATCH 48/69] Rewrite SelectionLayer testcase to construct an entire HitObjectComposer --- .../Visual/TestCaseEditorSelectionLayer.cs | 61 +++++++------------ 1 file changed, 23 insertions(+), 38 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index 50a39e6c33..5e0c0e165c 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -5,15 +5,13 @@ using System; using System.Collections.Generic; using osu.Framework.Allocation; using OpenTK; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Timing; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; 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.Objects; -using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Tests.Beatmaps; namespace osu.Game.Tests.Visual { @@ -27,44 +25,31 @@ namespace osu.Game.Tests.Visual }; [BackgroundDependencyLoader] - private void load() + private void load(OsuGameBase osuGame) { - var playfield = new OsuEditPlayfield(); - - Children = new Drawable[] + osuGame.Beatmap.Value = new TestWorkingBeatmap(new Beatmap { - new Container + HitObjects = new List { - RelativeSizeAxes = Axes.Both, - Clock = new FramedClock(new StopwatchClock()), - Child = playfield + new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }, + new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }, + new Slider + { + ControlPoints = new List + { + new Vector2(128, 256), + new Vector2(344, 256), + }, + Distance = 400, + Position = new Vector2(128, 256), + Velocity = 1, + TickDistance = 100, + Scale = 0.5f, + } }, - new SelectionLayer(playfield) - }; + }); - var hitCircle1 = new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }; - var hitCircle2 = new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }; - var slider = new Slider - { - ControlPoints = new List - { - new Vector2(128, 256), - new Vector2(344, 256), - }, - Distance = 400, - Position = new Vector2(128, 256), - Velocity = 1, - TickDistance = 100, - Scale = 0.5f, - }; - - hitCircle1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - hitCircle2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - - playfield.Add(new DrawableHitCircle(hitCircle1)); - playfield.Add(new DrawableHitCircle(hitCircle2)); - playfield.Add(new DrawableSlider(slider)); + Child = new OsuHitObjectComposer(new OsuRuleset()); } } } From 4934ef742941b864efddf5d00d55ecf5ca71a135 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Feb 2018 14:01:33 +0900 Subject: [PATCH 49/69] Add playfield aspect ratio + scaling modifications to composer layers --- .../Edit/OsuHitObjectComposer.cs | 4 ++ osu.Game/Rulesets/Edit/HitObjectComposer.cs | 50 +++++++++++++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index 6652a5fde2..ae19706da3 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -2,10 +2,12 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.Collections.Generic; +using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; namespace osu.Game.Rulesets.Osu.Edit @@ -25,5 +27,7 @@ namespace osu.Game.Rulesets.Osu.Edit new HitObjectCompositionTool(), new HitObjectCompositionTool() }; + + protected override ScalableContainer CreateLayerContainer() => new ScalableContainer(OsuPlayfield.BASE_SIZE.X) { RelativeSizeAxes = Axes.Both }; } } diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 7f22b3764c..62669150aa 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Linq; using OpenTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -25,6 +26,9 @@ namespace osu.Game.Rulesets.Edit protected ICompositionTool CurrentTool { get; private set; } + private RulesetContainer rulesetContainer; + private readonly Container[] layerContainers = new Container[2]; + protected HitObjectComposer(Ruleset ruleset) { this.ruleset = ruleset; @@ -35,7 +39,6 @@ namespace osu.Game.Rulesets.Edit [BackgroundDependencyLoader] private void load(OsuGameBase osuGame) { - RulesetContainer rulesetContainer; try { rulesetContainer = CreateRulesetContainer(ruleset, osuGame.Beatmap.Value); @@ -46,6 +49,20 @@ namespace osu.Game.Rulesets.Edit return; } + layerContainers[0] = CreateLayerContainer(); + layerContainers[0].Child = new Container + { + Name = "Border", + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderColour = Color4.White, + BorderThickness = 2, + Child = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0, AlwaysPresent = true } + }; + + layerContainers[1] = CreateLayerContainer(); + layerContainers[1].Child = new SelectionLayer(rulesetContainer.Playfield); + RadioButtonCollection toolboxCollection; InternalChild = new GridContainer { @@ -66,20 +83,13 @@ namespace osu.Game.Rulesets.Edit }, new Container { + Name = "Content", RelativeSizeAxes = Axes.Both, - Masking = true, - BorderColour = Color4.White, - BorderThickness = 2, Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - AlwaysPresent = true, - }, + layerContainers[0], rulesetContainer, - new SelectionLayer(rulesetContainer.Playfield) + layerContainers[1] } } }, @@ -102,10 +112,28 @@ namespace osu.Game.Rulesets.Edit toolboxCollection.Items[0].Select(); } + protected override void UpdateAfterChildren() + { + base.UpdateAfterChildren(); + + layerContainers.ForEach(l => + { + l.Anchor = rulesetContainer.Playfield.Anchor; + l.Origin = rulesetContainer.Playfield.Origin; + l.Position = rulesetContainer.Playfield.Position; + l.Size = rulesetContainer.Playfield.Size; + }); + } + private void setCompositionTool(ICompositionTool tool) => CurrentTool = tool; protected virtual RulesetContainer CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap) => ruleset.CreateRulesetContainerWith(beatmap, true); protected abstract IReadOnlyList CompositionTools { get; } + + /// + /// Creates a which provides a layer above or below the . + /// + protected virtual ScalableContainer CreateLayerContainer() => new ScalableContainer(); } } From 9a9f53ddfd76731d76462bdcc287190749667a8e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 20 Feb 2018 14:13:52 +0900 Subject: [PATCH 50/69] Remove 0.75 scale from osu! playfield in the editor --- osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs index 56efc25fa5..dd65cd470d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs @@ -5,6 +5,7 @@ using osu.Framework.Graphics.Cursor; using osu.Game.Beatmaps; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; +using OpenTK; namespace osu.Game.Rulesets.Osu.Edit { @@ -17,6 +18,12 @@ namespace osu.Game.Rulesets.Osu.Edit protected override Playfield CreatePlayfield() => new OsuEditPlayfield(); + protected override Vector2 GetAspectAdjustedSize() + { + var aspectSize = DrawSize.X * 0.75f < DrawSize.Y ? new Vector2(DrawSize.X, DrawSize.X * 0.75f) : new Vector2(DrawSize.Y * 4f / 3f, DrawSize.Y); + return new Vector2(aspectSize.X / DrawSize.X, aspectSize.Y / DrawSize.Y); + } + protected override CursorContainer CreateCursor() => null; } } From cb1ed2fb892062538ad2195f49226a851c4a53ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Feb 2018 16:14:39 +0900 Subject: [PATCH 51/69] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 458ebc2d46..f6fa5b80ed 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 458ebc2d4626c74bb8059cd28b44eb7adba74fbb +Subproject commit f6fa5b80ed06f84c8fd25a2576eea8d51565785c From 31edcfa35c107c19111bbbf05ef4bf7ece0a1b6a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 21 Feb 2018 16:12:54 +0900 Subject: [PATCH 52/69] Attempt to fix beatmap carousel test failures As seen here: https://ci.appveyor.com/project/peppy/osu/build/master-7260#L1164 Potentially was continuing operation too early. --- osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs index 4a65d12977..901d24e531 100644 --- a/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs +++ b/osu.Game.Tests/Visual/TestCaseBeatmapCarousel.cs @@ -60,7 +60,9 @@ namespace osu.Game.Tests.Visual AddStep("Load Beatmaps", () => { carousel.BeatmapSets = beatmapSets; }); - AddUntilStep(() => carousel.BeatmapSets.Any(), "Wait for load"); + bool changed = false; + carousel.BeatmapSetsChanged = () => changed = true; + AddUntilStep(() => changed, "Wait for load"); testTraversal(); testFiltering(); From f877b642da8a65310a52c0f98bac198a0bdda9ff Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Feb 2018 17:10:18 +0900 Subject: [PATCH 53/69] Clean up and document better what "progress" means --- .../Objects/Drawables/DrawableSlider.cs | 12 ++---------- .../Objects/Drawables/Pieces/SliderBall.cs | 4 ++-- .../Objects/Drawables/Pieces/SliderBody.cs | 9 ++++++--- osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs | 6 +++++- 4 files changed, 15 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 41df7ae4a4..14650235c3 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.Graphics.Containers; using osu.Game.Rulesets.Osu.Judgements; using osu.Framework.Graphics.Primitives; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables @@ -87,7 +86,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - private int currentSpan; public bool Tracking; protected override void Update() @@ -96,19 +94,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Tracking = Ball.Tracking; - double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); - - int span = slider.SpanAt(progress); - progress = slider.ProgressAt(progress); - - if (span > currentSpan) - currentSpan = span; + double completionProgress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. if (!HeadCircle.IsHit) HeadCircle.Position = slider.Curve.PositionAt(progress); - foreach (var c in components.OfType()) c.UpdateProgress(progress, span); + foreach (var c in components.OfType()) c.UpdateProgress(completionProgress); foreach (var c in components.OfType()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); foreach (var t in components.OfType()) t.Tracking = Ball.Tracking; } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs index 2fda299389..61db10b694 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBall.cs @@ -139,9 +139,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces } } - public void UpdateProgress(double progress, int span) + public void UpdateProgress(double completionProgress) { - Position = slider.Curve.PositionAt(progress); + Position = slider.StackedPositionAt(completionProgress); } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 89af67ba2a..fd63a3d954 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -167,8 +167,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return true; } - public void UpdateProgress(double progress, int span) + public void UpdateProgress(double completionProgress) { + var span = slider.SpanAt(completionProgress); + var spanProgress = slider.ProgressAt(completionProgress); + double start = 0; double end = snakingIn ? MathHelper.Clamp((Time.Current - (slider.StartTime - slider.TimePreempt)) / slider.TimeFadein, 0, 1) : 1; @@ -177,11 +180,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces if (Math.Min(span, slider.SpanCount() - 1) % 2 == 1) { start = 0; - end = snakingOut ? progress : 1; + end = snakingOut ? spanProgress : 1; } else { - start = snakingOut ? progress : 0; + start = snakingOut ? spanProgress : 0; } } diff --git a/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs b/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs index 54f783b664..a0566eaf17 100644 --- a/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs +++ b/osu.Game.Rulesets.Osu/Objects/ISliderProgress.cs @@ -5,6 +5,10 @@ namespace osu.Game.Rulesets.Osu.Objects { public interface ISliderProgress { - void UpdateProgress(double progress, int span); + /// + /// Updates the progress of this element along the slider. + /// + /// Amount of the slider completed. + void UpdateProgress(double completionProgress); } } From f012cce6682568331c8b34f3a6b12c38722f7175 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Feb 2018 17:33:22 +0900 Subject: [PATCH 54/69] Rewrite some xmldocs to make methods easier to understand --- osu.Game/Rulesets/Objects/Types/IHasCurve.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs index 7f03854ea9..c03bdb240e 100644 --- a/osu.Game/Rulesets/Objects/Types/IHasCurve.cs +++ b/osu.Game/Rulesets/Objects/Types/IHasCurve.cs @@ -30,21 +30,19 @@ namespace osu.Game.Rulesets.Objects.Types public static class HasCurveExtensions { /// - /// Computes the position on the curve at a given progress, accounting for repeat logic. - /// - /// Ranges from [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. - /// + /// Computes the position on the curve relative to how much of the has been completed. /// /// The curve. - /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. + /// [0, 1] where 0 is the start time of the and 1 is the end time of the . + /// The position on the curve. public static Vector2 PositionAt(this IHasCurve obj, double progress) => obj.Curve.PositionAt(obj.ProgressAt(progress)); /// - /// Finds the progress along the curve, accounting for repeat logic. + /// Computes the progress along the curve relative to how much of the has been completed. /// /// The curve. - /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. + /// [0, 1] where 0 is the start time of the and 1 is the end time of the . /// [0, 1] where 0 is the beginning of the curve and 1 is the end of the curve. public static double ProgressAt(this IHasCurve obj, double progress) { From f903e6d241035330d7c7cc1ffaa3df207695738f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Feb 2018 17:46:45 +0900 Subject: [PATCH 55/69] Fix stacking not working with sliders Fixes #2093. --- .../Objects/Drawables/DrawableRepeatPoint.cs | 7 ++++--- .../Objects/Drawables/DrawableSlider.cs | 6 +++--- osu.Game.Rulesets.Osu/Objects/Slider.cs | 12 ------------ osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs | 10 ++++++++-- 4 files changed, 15 insertions(+), 20 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs index 79a4714e33..db704b0553 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableRepeatPoint.cs @@ -78,7 +78,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables bool isRepeatAtEnd = repeatPoint.RepeatIndex % 2 == 0; List curve = drawableSlider.Body.CurrentCurve; - Position = isRepeatAtEnd ? end : start; + var positionOnCurve = isRepeatAtEnd ? end : start; + Position = positionOnCurve + drawableSlider.HitObject.StackOffset; if (curve.Count < 2) return; @@ -89,10 +90,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables // find the next vector2 in the curve which is not equal to our current position to infer a rotation. for (int i = searchStart; i >= 0 && i < curve.Count; i += direction) { - if (curve[i] == Position) + if (curve[i] == positionOnCurve) continue; - Rotation = MathHelper.RadiansToDegrees((float)Math.Atan2(curve[i].Y - Position.Y, curve[i].X - Position.X)); + Rotation = MathHelper.RadiansToDegrees((float)Math.Atan2(curve[i].Y - positionOnCurve.Y, curve[i].X - positionOnCurve.X)); break; } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 14650235c3..86b9706d9c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -65,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { var drawableTick = new DrawableSliderTick(tick) { - Position = tick.Position + Position = tick.StackedPosition }; ticks.Add(drawableTick); @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) { - Position = repeatPoint.Position + Position = repeatPoint.StackedPosition }; repeatPoints.Add(drawableRepeatPoint); @@ -98,7 +98,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. if (!HeadCircle.IsHit) - HeadCircle.Position = slider.Curve.PositionAt(progress); + HeadCircle.Position = slider.StackedPositionAt(completionProgress); foreach (var c in components.OfType()) c.UpdateProgress(completionProgress); foreach (var c in components.OfType()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 5dd3d7aa89..ce6c88a340 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -66,18 +66,6 @@ namespace osu.Game.Rulesets.Osu.Objects /// public double SpanDuration => Duration / this.SpanCount(); - private int stackHeight; - - public override int StackHeight - { - get { return stackHeight; } - set - { - stackHeight = value; - Curve.Offset = StackOffset; - } - } - public double Velocity; public double TickDistance; diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs index 55fa37882d..90a0a450a7 100644 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs @@ -88,10 +88,15 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("Catmull Slider", () => testCatmull()); AddStep("Catmull Slider 1 Repeat", () => testCatmull(1)); AddStep("Catmull Slider 2 Repeats", () => testCatmull(2)); + + AddStep("Big Single, Large StackOffset", () => testSimpleBigLargeStackOffset()); + AddStep("Big 1 Repeat, Large StackOffset", () => testSimpleBigLargeStackOffset(1)); } private void testSimpleBig(int repeats = 0) => createSlider(2, repeats: repeats); + private void testSimpleBigLargeStackOffset(int repeats = 0) => createSlider(2, repeats: repeats, stackHeight: 10); + private void testSimpleMedium(int repeats = 0) => createSlider(5, repeats: repeats); private void testSimpleSmall(int repeats = 0) => createSlider(7, repeats: repeats); @@ -104,7 +109,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void testShortHighSpeed(int repeats = 0) => createSlider(distance: 100, repeats: repeats, speedMultiplier: 15); - private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2) + private void createSlider(float circleSize = 2, float distance = 400, int repeats = 0, double speedMultiplier = 2, int stackHeight = 0) { var slider = new Slider { @@ -118,7 +123,8 @@ namespace osu.Game.Rulesets.Osu.Tests }, Distance = distance, RepeatCount = repeats, - RepeatSamples = createEmptySamples(repeats) + RepeatSamples = createEmptySamples(repeats), + StackHeight = stackHeight }; addSlider(slider, circleSize, speedMultiplier); From 0d60a65c942a2e224188e5fe05620d621e3b7ac6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Feb 2018 17:51:34 +0900 Subject: [PATCH 56/69] Fix OsuAutoGenerator not considering stacking --- osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs index a22ac6aed1..274f7bff62 100644 --- a/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs +++ b/osu.Game.Rulesets.Osu/Replays/OsuAutoGenerator.cs @@ -315,11 +315,11 @@ namespace osu.Game.Rulesets.Osu.Replays for (double j = FrameDelay; j < s.Duration; j += FrameDelay) { - Vector2 pos = s.PositionAt(j / s.Duration); + Vector2 pos = s.StackedPositionAt(j / s.Duration); AddFrameToReplay(new ReplayFrame(h.StartTime + j, pos.X, pos.Y, button)); } - AddFrameToReplay(new ReplayFrame(s.EndTime, s.EndPosition.X, s.EndPosition.Y, button)); + AddFrameToReplay(new ReplayFrame(s.EndTime, s.StackedEndPosition.X, s.StackedEndPosition.Y, button)); } // We only want to let go of our button if we are at the end of the current replay. Otherwise something is still going on after us so we need to keep the button pressed! From 9cd9c83f2cc2c5947d7cc8aeb80e4111c2a26480 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Feb 2018 18:00:46 +0900 Subject: [PATCH 57/69] Allow changing accentcolour/bordercolour post-load --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 31ef1c05e6..96afc35ad6 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return; accentColour = value; - if (LoadState == LoadState.Ready) + if (LoadState >= LoadState.Ready) Schedule(reloadTexture); } } @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces return; borderColour = value; - if (LoadState == LoadState.Ready) + if (LoadState >= LoadState.Ready) Schedule(reloadTexture); } } From 08e52e8c153afd0e2cf73fd39e4afb756e850ed3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Feb 2018 18:02:52 +0900 Subject: [PATCH 58/69] Remove unnecessary schedules --- osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs index 96afc35ad6..0098ddc20b 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Pieces/SliderBody.cs @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces accentColour = value; if (LoadState >= LoadState.Ready) - Schedule(reloadTexture); + reloadTexture(); } } @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces borderColour = value; if (LoadState >= LoadState.Ready) - Schedule(reloadTexture); + reloadTexture(); } } From 6757be200723d143dbb8cf574c08408c3326ed52 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Feb 2018 20:15:42 +0900 Subject: [PATCH 59/69] Fix incorrect RelativeChildSize in Catch --- osu.Game/Rulesets/UI/ScalableContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/ScalableContainer.cs b/osu.Game/Rulesets/UI/ScalableContainer.cs index e1c1427470..43ed770f77 100644 --- a/osu.Game/Rulesets/UI/ScalableContainer.cs +++ b/osu.Game/Rulesets/UI/ScalableContainer.cs @@ -79,7 +79,7 @@ namespace osu.Game.Rulesets.UI protected override void Update() { base.Update(); - RelativeChildSize = sizeScale; + RelativeChildSize = new Vector2(CustomWidth.HasValue ? sizeScale.X : RelativeChildSize.X, CustomHeight.HasValue ? sizeScale.Y : RelativeChildSize.Y); } } } From 5faec5c2f1b9e8cac6263124b59c60699136ea00 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 21 Feb 2018 19:52:36 +0900 Subject: [PATCH 60/69] Add a separate property to control playfield area --- .../UI/ManiaRulesetContainer.cs | 2 +- .../Edit/OsuEditRulesetContainer.cs | 6 +----- osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs | 2 +- .../UI/TaikoRulesetContainer.cs | 2 ++ osu.Game/Rulesets/UI/RulesetContainer.cs | 14 ++++++++++---- 5 files changed, 15 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs index 3c9647117e..732d5f4109 100644 --- a/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs +++ b/osu.Game.Rulesets.Mania/UI/ManiaRulesetContainer.cs @@ -101,7 +101,7 @@ namespace osu.Game.Rulesets.Mania.UI return null; } - protected override Vector2 GetAspectAdjustedSize() => new Vector2(1, 0.8f); + protected override Vector2 PlayfieldArea => new Vector2(1, 0.8f); protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay, this); diff --git a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs index dd65cd470d..a8d895bc1d 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuEditRulesetContainer.cs @@ -18,11 +18,7 @@ namespace osu.Game.Rulesets.Osu.Edit protected override Playfield CreatePlayfield() => new OsuEditPlayfield(); - protected override Vector2 GetAspectAdjustedSize() - { - var aspectSize = DrawSize.X * 0.75f < DrawSize.Y ? new Vector2(DrawSize.X, DrawSize.X * 0.75f) : new Vector2(DrawSize.Y * 4f / 3f, DrawSize.Y); - return new Vector2(aspectSize.X / DrawSize.X, aspectSize.Y / DrawSize.Y); - } + protected override Vector2 PlayfieldArea => Vector2.One; protected override CursorContainer CreateCursor() => null; } diff --git a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs index 9cb6a13cb2..2af381dd71 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuRulesetContainer.cs @@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Osu.UI protected override Vector2 GetAspectAdjustedSize() { var aspectSize = DrawSize.X * 0.75f < DrawSize.Y ? new Vector2(DrawSize.X, DrawSize.X * 0.75f) : new Vector2(DrawSize.Y * 4f / 3f, DrawSize.Y); - return new Vector2(aspectSize.X / DrawSize.X, aspectSize.Y / DrawSize.Y) * 0.75f; + return new Vector2(aspectSize.X / DrawSize.X, aspectSize.Y / DrawSize.Y); } protected override CursorContainer CreateCursor() => new GameplayCursor(); diff --git a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs index 8342009e80..fd31f738ee 100644 --- a/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs +++ b/osu.Game.Rulesets.Taiko/UI/TaikoRulesetContainer.cs @@ -88,6 +88,8 @@ namespace osu.Game.Rulesets.Taiko.UI return new Vector2(1, default_relative_height * aspectAdjust); } + protected override Vector2 PlayfieldArea => Vector2.One; + public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this); protected override BeatmapConverter CreateBeatmapConverter() => new TaikoBeatmapConverter(IsForCurrentRuleset); diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index f4e700a8eb..05cb0f741b 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -319,7 +319,7 @@ namespace osu.Game.Rulesets.UI { base.Update(); - Playfield.Size = GetAspectAdjustedSize(); + Playfield.Size = GetAspectAdjustedSize() * PlayfieldArea; } /// @@ -330,11 +330,17 @@ namespace osu.Game.Rulesets.UI protected virtual BeatmapProcessor CreateBeatmapProcessor() => new BeatmapProcessor(); /// - /// Computes the final size of the in relative coordinate space after all - /// aspect and scale adjustments. + /// Computes the size of the in relative coordinate space after aspect adjustments. /// /// The aspect-adjusted size. - protected virtual Vector2 GetAspectAdjustedSize() => new Vector2(0.75f); // A sane default + protected virtual Vector2 GetAspectAdjustedSize() => Vector2.One; + + /// + /// The area of this that is available for the to use. + /// Must be specified in relative coordinate space to this . + /// This affects the final size of the but does not affect the 's scale. + /// + protected virtual Vector2 PlayfieldArea => new Vector2(0.75f); // A sane default /// /// Creates a converter to convert Beatmap to a specific mode. From 7c942eb592d5e06b54e31828ae8cb0291318bcfd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Feb 2018 13:42:31 +0900 Subject: [PATCH 61/69] Tidy up layer container logic --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 41 +++++++++++---------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 62669150aa..1246127257 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using System.Linq; using OpenTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; @@ -27,12 +26,11 @@ namespace osu.Game.Rulesets.Edit protected ICompositionTool CurrentTool { get; private set; } private RulesetContainer rulesetContainer; - private readonly Container[] layerContainers = new Container[2]; + private readonly List layerContainers = new List(); protected HitObjectComposer(Ruleset ruleset) { this.ruleset = ruleset; - RelativeSizeAxes = Axes.Both; } @@ -42,6 +40,9 @@ namespace osu.Game.Rulesets.Edit try { rulesetContainer = CreateRulesetContainer(ruleset, osuGame.Beatmap.Value); + + // TODO: should probably be done at a RulesetContainer level to share logic with Player. + rulesetContainer.Clock = new InterpolatingFramedClock((IAdjustableClock)osuGame.Beatmap.Value.Track ?? new StopwatchClock()); } catch (Exception e) { @@ -49,19 +50,13 @@ namespace osu.Game.Rulesets.Edit return; } - layerContainers[0] = CreateLayerContainer(); - layerContainers[0].Child = new Container + ScalableContainer createLayerContainerWithContent(Drawable content) { - Name = "Border", - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderColour = Color4.White, - BorderThickness = 2, - Child = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0, AlwaysPresent = true } - }; - - layerContainers[1] = CreateLayerContainer(); - layerContainers[1].Child = new SelectionLayer(rulesetContainer.Playfield); + var container = CreateLayerContainer(); + container.Child = content; + layerContainers.Add(container); + return container; + } RadioButtonCollection toolboxCollection; InternalChild = new GridContainer @@ -87,9 +82,17 @@ namespace osu.Game.Rulesets.Edit RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - layerContainers[0], + createLayerContainerWithContent(new Container + { + Name = "Border", + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderColour = Color4.White, + BorderThickness = 2, + Child = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0, AlwaysPresent = true } + }), rulesetContainer, - layerContainers[1] + createLayerContainerWithContent(new SelectionLayer(rulesetContainer.Playfield)) } } }, @@ -100,8 +103,6 @@ namespace osu.Game.Rulesets.Edit } }; - rulesetContainer.Clock = new InterpolatingFramedClock((IAdjustableClock)osuGame.Beatmap.Value.Track ?? new StopwatchClock()); - toolboxCollection.Items = new[] { new RadioButton("Select", () => setCompositionTool(null)) } .Concat( @@ -134,6 +135,6 @@ namespace osu.Game.Rulesets.Edit /// /// Creates a which provides a layer above or below the . /// - protected virtual ScalableContainer CreateLayerContainer() => new ScalableContainer(); + protected virtual ScalableContainer CreateLayerContainer() => new ScalableContainer { RelativeSizeAxes = Axes.Both }; } } From 3d4bed462b55f792ebfd8a66b4c5b00581334568 Mon Sep 17 00:00:00 2001 From: Joseph Madamba <35318437+Joehuu@users.noreply.github.com> Date: Wed, 21 Feb 2018 20:54:47 -0800 Subject: [PATCH 62/69] Reword sign in text --- .../Overlays/Settings/Sections/General/LoginSettings.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs index d959da52f3..a5d068adbd 100644 --- a/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/General/LoginSettings.cs @@ -208,7 +208,7 @@ namespace osu.Game.Overlays.Settings.Sections.General { username = new OsuTextBox { - PlaceholderText = "Username", + PlaceholderText = "Email address", RelativeSizeAxes = Axes.X, Text = api?.Username ?? string.Empty, TabbableContentContainer = this @@ -222,12 +222,12 @@ namespace osu.Game.Overlays.Settings.Sections.General }, new SettingsCheckbox { - LabelText = "Remember username", + LabelText = "Remember email address", Bindable = config.GetBindable(OsuSetting.SaveUsername), }, new SettingsCheckbox { - LabelText = "Stay logged in", + LabelText = "Stay signed in", Bindable = config.GetBindable(OsuSetting.SavePassword), }, new SettingsButton @@ -237,7 +237,7 @@ namespace osu.Game.Overlays.Settings.Sections.General }, new SettingsButton { - Text = "Register new account", + Text = "Register", //Action = registerLink } }; From f2b3d9a0e89ad1bdbfba21e025f1bbd7f5ea5570 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Feb 2018 14:17:35 +0900 Subject: [PATCH 63/69] Change some missed instances --- osu.Game/Overlays/ChatOverlay.cs | 2 +- osu.Game/Screens/Select/Leaderboards/Leaderboard.cs | 2 +- osu.Game/Tests/Visual/TestCasePerformancePoints.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index beb2b3b746..7c6e563c5b 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -477,7 +477,7 @@ namespace osu.Game.Overlays if (!api.IsLoggedIn) { - target.AddNewMessages(new ErrorMessage("Please login to participate in chat!")); + target.AddNewMessages(new ErrorMessage("Please sign in to participate in chat!")); return; } diff --git a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs index 6be6523175..273cceeeda 100644 --- a/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/Leaderboard.cs @@ -133,7 +133,7 @@ namespace osu.Game.Screens.Select.Leaderboards replacePlaceholder(new MessagePlaceholder(@"No records yet!")); break; case PlaceholderState.NotLoggedIn: - replacePlaceholder(new MessagePlaceholder(@"Please login to view online leaderboards!")); + replacePlaceholder(new MessagePlaceholder(@"Please sign in to view online leaderboards!")); break; case PlaceholderState.NotSupporter: replacePlaceholder(new MessagePlaceholder(@"Please invest in a supporter tag to view this leaderboard!")); diff --git a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs index c531edb893..5b32433467 100644 --- a/osu.Game/Tests/Visual/TestCasePerformancePoints.cs +++ b/osu.Game/Tests/Visual/TestCasePerformancePoints.cs @@ -230,7 +230,7 @@ namespace osu.Game.Tests.Visual { Anchor = Anchor.TopCentre, Origin = Anchor.TopCentre, - Text = "Please login to see online scores", + Text = "Please sign in to see online scores", }; } From c070c695229a0b8ec13b2f36743178edec0b5ceb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Feb 2018 15:01:53 +0900 Subject: [PATCH 64/69] Update framework --- osu-framework | 2 +- osu.Game/Overlays/Direct/PlayButton.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu-framework b/osu-framework index f6fa5b80ed..16a4bef775 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit f6fa5b80ed06f84c8fd25a2576eea8d51565785c +Subproject commit 16a4bef775a49166f38faa6e952d83d8823fe3e0 diff --git a/osu.Game/Overlays/Direct/PlayButton.cs b/osu.Game/Overlays/Direct/PlayButton.cs index 1d67bc2d90..0fb988ead7 100644 --- a/osu.Game/Overlays/Direct/PlayButton.cs +++ b/osu.Game/Overlays/Direct/PlayButton.cs @@ -22,6 +22,7 @@ namespace osu.Game.Overlays.Direct public Track Preview { get; private set; } private BeatmapSetInfo beatmapSet; + public BeatmapSetInfo BeatmapSet { get { return beatmapSet; } @@ -199,8 +200,7 @@ namespace osu.Game.Overlays.Direct // add back the user's music volume setting (since we are no longer in the global TrackManager's hierarchy). config.BindWith(FrameworkSetting.VolumeMusic, trackManager.Volume); - if (!string.IsNullOrEmpty(preview)) - Preview = trackManager.Get(preview); + Preview = trackManager.Get(preview); } protected override void Dispose(bool isDisposing) From 4c14b32783ada771f7e7e75dec301688bf98713b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 15 Feb 2018 13:45:39 +0900 Subject: [PATCH 65/69] Add basic skin database model layout and importing --- osu.Game/Database/OsuDbContext.cs | 2 ++ osu.Game/OsuGame.cs | 2 ++ osu.Game/OsuGameBase.cs | 6 +++++ osu.Game/Skinning/SkinFileInfo.cs | 25 ++++++++++++++++++++ osu.Game/Skinning/SkinInfo.cs | 25 ++++++++++++++++++++ osu.Game/Skinning/SkinManager.cs | 39 +++++++++++++++++++++++++++++++ osu.Game/Skinning/SkinStore.cs | 22 +++++++++++++++++ osu.Game/osu.Game.csproj | 4 ++++ 8 files changed, 125 insertions(+) create mode 100644 osu.Game/Skinning/SkinFileInfo.cs create mode 100644 osu.Game/Skinning/SkinInfo.cs create mode 100644 osu.Game/Skinning/SkinManager.cs create mode 100644 osu.Game/Skinning/SkinStore.cs diff --git a/osu.Game/Database/OsuDbContext.cs b/osu.Game/Database/OsuDbContext.cs index e83b30595e..a4b0c30478 100644 --- a/osu.Game/Database/OsuDbContext.cs +++ b/osu.Game/Database/OsuDbContext.cs @@ -13,6 +13,7 @@ using osu.Game.IO; using osu.Game.Rulesets; using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding; using LogLevel = Microsoft.Extensions.Logging.LogLevel; +using osu.Game.Skinning; namespace osu.Game.Database { @@ -26,6 +27,7 @@ namespace osu.Game.Database public DbSet DatabasedSetting { get; set; } public DbSet FileInfo { get; set; } public DbSet RulesetInfo { get; set; } + public DbSet SkinInfo { get; set; } private readonly string connectionString; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 14bc31aecf..15ee62c5e5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -187,7 +187,9 @@ namespace osu.Game CursorOverrideContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; // hook up notifications to components. + SkinManager.PostNotification = n => notifications?.Post(n); BeatmapManager.PostNotification = n => notifications?.Post(n); + BeatmapManager.GetStableStorage = GetStorageForStableInstall; AddRange(new Drawable[] diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index b70055cc00..94ed696e49 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -30,6 +30,7 @@ using osu.Game.Input.Bindings; using osu.Game.IO; using osu.Game.Rulesets; using osu.Game.Rulesets.Scoring; +using osu.Game.Skinning; namespace osu.Game { @@ -39,6 +40,8 @@ namespace osu.Game protected BeatmapManager BeatmapManager; + protected SkinManager SkinManager; + protected RulesetStore RulesetStore; protected FileStore FileStore; @@ -103,6 +106,8 @@ namespace osu.Game runMigrations(); + dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host)); + dependencies.Cache(API = new APIAccess { Username = LocalConfig.Get(OsuSetting.Username), @@ -120,6 +125,7 @@ namespace osu.Game fileImporters.Add(BeatmapManager); fileImporters.Add(ScoreStore); + fileImporters.Add(SkinManager); //this completely overrides the framework default. will need to change once we make a proper FontStore. dependencies.Cache(Fonts = new FontStore { ScaleAdjust = 100 }); diff --git a/osu.Game/Skinning/SkinFileInfo.cs b/osu.Game/Skinning/SkinFileInfo.cs new file mode 100644 index 0000000000..e8caf8f44a --- /dev/null +++ b/osu.Game/Skinning/SkinFileInfo.cs @@ -0,0 +1,25 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; +using osu.Game.Database; +using osu.Game.IO; + +namespace osu.Game.Skinning +{ + public class SkinFileInfo : INamedFileInfo + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int ID { get; set; } + + public int SkinInfoID { get; set; } + + public int FileInfoID { get; set; } + + public FileInfo FileInfo { get; set; } + + [Required] + public string Filename { get; set; } + } +} diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs new file mode 100644 index 0000000000..ee9f63bec9 --- /dev/null +++ b/osu.Game/Skinning/SkinInfo.cs @@ -0,0 +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.ComponentModel.DataAnnotations.Schema; +using osu.Game.Database; + +namespace osu.Game.Skinning +{ + public class SkinInfo : IHasFiles, IHasPrimaryKey, ISoftDelete + { + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public int ID { get; set; } + + public string Name { get; set; } + + public string Creator { get; set; } + + public List Files { get; set; } + + public bool DeletePending { get; set; } + + public static SkinInfo Default { get; } = new SkinInfo { Name = "osu!lazer", Creator = "team osu!" }; + } +} diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs new file mode 100644 index 0000000000..ac3347e1d0 --- /dev/null +++ b/osu.Game/Skinning/SkinManager.cs @@ -0,0 +1,39 @@ +// 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.Configuration; +using osu.Framework.Platform; +using osu.Game.Database; +using osu.Game.IO.Archives; + +namespace osu.Game.Skinning +{ + public class SkinManager : ArchiveModelManager + { + public readonly Bindable CurrentSkinInfo = new Bindable(SkinInfo.Default) { Default = SkinInfo.Default }; + + public override string[] HandledExtensions => new[] { ".osk" }; + + /// + /// Returns a list of all usable s. + /// + /// A list of available . + public List GetAllUsableSkins() + { + var userSkins = ModelStore.ConsumableItems.Where(s => !s.DeletePending).ToList(); + userSkins.Insert(0, SkinInfo.Default); + return userSkins; + } + + protected override SkinInfo CreateModel(ArchiveReader archive) => new SkinInfo { Name = archive.Name }; + + private SkinStore store; + + public SkinManager(Storage storage, DatabaseContextFactory contextFactory, IIpcHost importHost) + : base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost) + { + } + } +} diff --git a/osu.Game/Skinning/SkinStore.cs b/osu.Game/Skinning/SkinStore.cs new file mode 100644 index 0000000000..ffd9873901 --- /dev/null +++ b/osu.Game/Skinning/SkinStore.cs @@ -0,0 +1,22 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using Microsoft.EntityFrameworkCore; +using osu.Framework.Platform; +using osu.Game.Database; + +namespace osu.Game.Skinning +{ + public class SkinStore : MutableDatabaseBackedStore + { + public SkinStore(DatabaseContextFactory contextFactory, Storage storage = null) + : base(contextFactory, storage) + { + } + + protected override IQueryable AddIncludesForConsumption(IQueryable query) => + base.AddIncludesForConsumption(query) + .Include(s => s.Files).ThenInclude(f => f.FileInfo); + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index afeb791029..4b1dad28db 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -850,6 +850,10 @@ + + + + From 2351b6ab269d7d4623dd9817ecbaf6b4571c691b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Feb 2018 15:44:13 +0900 Subject: [PATCH 66/69] Add migration --- .../20180219060912_AddSkins.Designer.cs | 379 ++++++++++++++++++ .../Migrations/20180219060912_AddSkins.cs | 73 ++++ .../Migrations/OsuDbContextModelSnapshot.cs | 50 +++ osu.Game/osu.Game.csproj | 4 + 4 files changed, 506 insertions(+) create mode 100644 osu.Game/Migrations/20180219060912_AddSkins.Designer.cs create mode 100644 osu.Game/Migrations/20180219060912_AddSkins.cs diff --git a/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs new file mode 100644 index 0000000000..83b8d6cf8a --- /dev/null +++ b/osu.Game/Migrations/20180219060912_AddSkins.Designer.cs @@ -0,0 +1,379 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage; +using osu.Game.Database; +using System; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180219060912_AddSkins")] + partial class AddSkins + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "2.0.0-rtm-26452"); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("ApproachRate"); + + b.Property("CircleSize"); + + b.Property("DrainRate"); + + b.Property("OverallDifficulty"); + + b.Property("SliderMultiplier"); + + b.Property("SliderTickRate"); + + b.HasKey("ID"); + + b.ToTable("BeatmapDifficulty"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("AudioLeadIn"); + + b.Property("BaseDifficultyID"); + + b.Property("BeatDivisor"); + + b.Property("BeatmapSetInfoID"); + + b.Property("Countdown"); + + b.Property("DistanceSpacing"); + + b.Property("GridSize"); + + b.Property("Hash"); + + b.Property("Hidden"); + + b.Property("LetterboxInBreaks"); + + b.Property("MD5Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapID"); + + b.Property("Path"); + + b.Property("RulesetID"); + + b.Property("SpecialStyle"); + + b.Property("StackLeniency"); + + b.Property("StarDifficulty"); + + b.Property("StoredBookmarks"); + + b.Property("TimelineZoom"); + + b.Property("Version"); + + b.Property("WidescreenStoryboard"); + + b.HasKey("ID"); + + b.HasIndex("BaseDifficultyID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MD5Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapID") + .IsUnique(); + + b.HasIndex("RulesetID"); + + b.ToTable("BeatmapInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Artist"); + + b.Property("ArtistUnicode"); + + b.Property("AudioFile"); + + b.Property("AuthorString") + .HasColumnName("Author"); + + b.Property("BackgroundFile"); + + b.Property("PreviewTime"); + + b.Property("Source"); + + b.Property("Tags"); + + b.Property("Title"); + + b.Property("TitleUnicode"); + + b.HasKey("ID"); + + b.ToTable("BeatmapMetadata"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("BeatmapSetInfoID"); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.HasKey("ID"); + + b.HasIndex("BeatmapSetInfoID"); + + b.HasIndex("FileInfoID"); + + b.ToTable("BeatmapSetFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("DeletePending"); + + b.Property("Hash"); + + b.Property("MetadataID"); + + b.Property("OnlineBeatmapSetID"); + + b.Property("Protected"); + + b.HasKey("ID"); + + b.HasIndex("DeletePending"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("MetadataID"); + + b.HasIndex("OnlineBeatmapSetID") + .IsUnique(); + + b.ToTable("BeatmapSetInfo"); + }); + + modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntKey") + .HasColumnName("Key"); + + b.Property("RulesetID"); + + b.Property("StringValue") + .HasColumnName("Value"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("Settings"); + }); + + modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("IntAction") + .HasColumnName("Action"); + + b.Property("KeysString") + .HasColumnName("Keys"); + + b.Property("RulesetID"); + + b.Property("Variant"); + + b.HasKey("ID"); + + b.HasIndex("IntAction"); + + b.HasIndex("RulesetID", "Variant"); + + b.ToTable("KeyBinding"); + }); + + modelBuilder.Entity("osu.Game.IO.FileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Hash"); + + b.Property("ReferenceCount"); + + b.HasKey("ID"); + + b.HasIndex("Hash") + .IsUnique(); + + b.HasIndex("ReferenceCount"); + + b.ToTable("FileInfo"); + }); + + modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Available"); + + b.Property("InstantiationInfo"); + + b.Property("Name"); + + b.Property("ShortName"); + + b.HasKey("ID"); + + b.HasIndex("Available"); + + b.HasIndex("ShortName") + .IsUnique(); + + b.ToTable("RulesetInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") + .WithMany() + .HasForeignKey("BaseDifficultyID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet") + .WithMany("Beatmaps") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("Beatmaps") + .HasForeignKey("MetadataID"); + + b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset") + .WithMany() + .HasForeignKey("RulesetID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo") + .WithMany("Files") + .HasForeignKey("BeatmapSetInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b => + { + b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata") + .WithMany("BeatmapSets") + .HasForeignKey("MetadataID"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/osu.Game/Migrations/20180219060912_AddSkins.cs b/osu.Game/Migrations/20180219060912_AddSkins.cs new file mode 100644 index 0000000000..741fcf4079 --- /dev/null +++ b/osu.Game/Migrations/20180219060912_AddSkins.cs @@ -0,0 +1,73 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; + +namespace osu.Game.Migrations +{ + public partial class AddSkins : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "SkinInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Creator = table.Column(type: "TEXT", nullable: true), + DeletePending = table.Column(type: "INTEGER", nullable: false), + Name = table.Column(type: "TEXT", nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_SkinInfo", x => x.ID); + }); + + migrationBuilder.CreateTable( + name: "SkinFileInfo", + columns: table => new + { + ID = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + FileInfoID = table.Column(type: "INTEGER", nullable: false), + Filename = table.Column(type: "TEXT", nullable: false), + SkinInfoID = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_SkinFileInfo", x => x.ID); + table.ForeignKey( + name: "FK_SkinFileInfo_FileInfo_FileInfoID", + column: x => x.FileInfoID, + principalTable: "FileInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + table.ForeignKey( + name: "FK_SkinFileInfo_SkinInfo_SkinInfoID", + column: x => x.SkinInfoID, + principalTable: "SkinInfo", + principalColumn: "ID", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_SkinFileInfo_FileInfoID", + table: "SkinFileInfo", + column: "FileInfoID"); + + migrationBuilder.CreateIndex( + name: "IX_SkinFileInfo_SkinInfoID", + table: "SkinFileInfo", + column: "SkinInfoID"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "SkinFileInfo"); + + migrationBuilder.DropTable( + name: "SkinInfo"); + } + } +} diff --git a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs index 157125102f..1627627790 100644 --- a/osu.Game/Migrations/OsuDbContextModelSnapshot.cs +++ b/osu.Game/Migrations/OsuDbContextModelSnapshot.cs @@ -281,6 +281,43 @@ namespace osu.Game.Migrations b.ToTable("RulesetInfo"); }); + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("FileInfoID"); + + b.Property("Filename") + .IsRequired(); + + b.Property("SkinInfoID"); + + b.HasKey("ID"); + + b.HasIndex("FileInfoID"); + + b.HasIndex("SkinInfoID"); + + b.ToTable("SkinFileInfo"); + }); + + modelBuilder.Entity("osu.Game.Skinning.SkinInfo", b => + { + b.Property("ID") + .ValueGeneratedOnAdd(); + + b.Property("Creator"); + + b.Property("DeletePending"); + + b.Property("Name"); + + b.HasKey("ID"); + + b.ToTable("SkinInfo"); + }); + modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b => { b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty") @@ -322,6 +359,19 @@ namespace osu.Game.Migrations .WithMany("BeatmapSets") .HasForeignKey("MetadataID"); }); + + modelBuilder.Entity("osu.Game.Skinning.SkinFileInfo", b => + { + b.HasOne("osu.Game.IO.FileInfo", "FileInfo") + .WithMany() + .HasForeignKey("FileInfoID") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("osu.Game.Skinning.SkinInfo") + .WithMany("Files") + .HasForeignKey("SkinInfoID") + .OnDelete(DeleteBehavior.Cascade); + }); #pragma warning restore 612, 618 } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4b1dad28db..829addc360 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -322,6 +322,10 @@ 20171209034410_AddRulesetInfoShortName.cs + + + 20180219060912_AddSkins.cs + From 659cf629b6d62e614bc5bc87eefcaf6075b78182 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Feb 2018 15:44:25 +0900 Subject: [PATCH 67/69] Add skin seleciton dropdown to settings --- .../Overlays/Settings/Sections/SkinSection.cs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index f6915896d7..1cd1cd0f7f 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -1,26 +1,33 @@ // 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.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; +using osu.Game.Skinning; using OpenTK; namespace osu.Game.Overlays.Settings.Sections { public class SkinSection : SettingsSection { + private SettingsDropdown skinDropdown; + public override string Header => "Skin"; + public override FontAwesome Icon => FontAwesome.fa_paint_brush; [BackgroundDependencyLoader] - private void load(OsuConfigManager config) + private void load(OsuConfigManager config, SkinManager skins) { FlowContent.Spacing = new Vector2(0, 5); Children = new Drawable[] { + skinDropdown = new SettingsDropdown(), new SettingsSlider { LabelText = "Menu cursor size", @@ -39,6 +46,14 @@ namespace osu.Game.Overlays.Settings.Sections Bindable = config.GetBindable(OsuSetting.AutoCursorSize) }, }; + + void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.Name, s)); + skins.ItemAdded += _ => reloadSkins(); + skins.ItemRemoved += _ => reloadSkins(); + + reloadSkins(); + + skinDropdown.Bindable = skins.CurrentSkinInfo; } private class SizeSlider : OsuSliderBar From 402d71a8d9677b08f61130b5c0b269925f3cc772 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 22 Feb 2018 16:29:05 +0900 Subject: [PATCH 68/69] Add user skin setting storage --- osu.Game/Configuration/OsuConfigManager.cs | 5 ++++- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/OsuGame.cs | 11 +++++++++++ osu.Game/Overlays/Settings/Sections/SkinSection.cs | 8 ++++---- osu.Game/Skinning/SkinInfo.cs | 5 ++++- 5 files changed, 24 insertions(+), 7 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index c33dd91330..3d927ef67c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -14,6 +14,8 @@ namespace osu.Game.Configuration { // UI/selection defaults Set(OsuSetting.Ruleset, 0, 0, int.MaxValue); + Set(OsuSetting.Skin, 0, 0, int.MaxValue); + Set(OsuSetting.BeatmapDetailTab, BeatmapDetailTab.Details); Set(OsuSetting.ShowConvertedBeatmaps, true); @@ -122,6 +124,7 @@ namespace osu.Game.Configuration ChatDisplayHeight, Version, ShowConvertedBeatmaps, - SpeedChangeVisualisation + SpeedChangeVisualisation, + Skin } } diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index a65593ff82..854bee99a5 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -48,7 +48,7 @@ namespace osu.Game.Database protected readonly IDatabaseContextFactory ContextFactory; - protected readonly MutableDatabaseBackedStore ModelStore; + public readonly MutableDatabaseBackedStore ModelStore; // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ArchiveImportIPCChannel ipc; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 15ee62c5e5..17a72d3c87 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -30,6 +30,7 @@ using osu.Game.Rulesets; using osu.Game.Screens.Play; using osu.Game.Input.Bindings; using osu.Game.Rulesets.Mods; +using osu.Game.Skinning; using OpenTK.Graphics; namespace osu.Game @@ -79,6 +80,8 @@ namespace osu.Game private Bindable configRuleset; public Bindable Ruleset = new Bindable(); + private Bindable configSkin; + private readonly string[] args; private SettingsOverlay settings; @@ -122,10 +125,18 @@ namespace osu.Game dependencies.CacheAs(this); + // bind config int to database RulesetInfo configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; + // bind config int to database SkinInfo + configSkin = LocalConfig.GetBindable(OsuSetting.Skin); + + SkinManager.CurrentSkinInfo.ValueChanged += s => configSkin.Value = s.ID; + configSkin.ValueChanged += id => SkinManager.CurrentSkinInfo.Value = SkinManager.ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == id) ?? SkinInfo.Default; + configSkin.TriggerChange(); + LocalConfig.BindWith(OsuSetting.VolumeInactive, inactiveVolumeAdjust); } diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 1cd1cd0f7f..bc0b8b4aaa 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -15,7 +15,7 @@ namespace osu.Game.Overlays.Settings.Sections { public class SkinSection : SettingsSection { - private SettingsDropdown skinDropdown; + private SettingsDropdown skinDropdown; public override string Header => "Skin"; @@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Settings.Sections FlowContent.Spacing = new Vector2(0, 5); Children = new Drawable[] { - skinDropdown = new SettingsDropdown(), + skinDropdown = new SettingsDropdown(), new SettingsSlider { LabelText = "Menu cursor size", @@ -47,13 +47,13 @@ namespace osu.Game.Overlays.Settings.Sections }, }; - void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.Name, s)); + void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair(s.Name, s.ID)); skins.ItemAdded += _ => reloadSkins(); skins.ItemRemoved += _ => reloadSkins(); reloadSkins(); - skinDropdown.Bindable = skins.CurrentSkinInfo; + skinDropdown.Bindable = config.GetBindable(OsuSetting.Skin); } private class SizeSlider : OsuSliderBar diff --git a/osu.Game/Skinning/SkinInfo.cs b/osu.Game/Skinning/SkinInfo.cs index ee9f63bec9..45c8b97f63 100644 --- a/osu.Game/Skinning/SkinInfo.cs +++ b/osu.Game/Skinning/SkinInfo.cs @@ -1,13 +1,14 @@ // 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.ComponentModel.DataAnnotations.Schema; using osu.Game.Database; namespace osu.Game.Skinning { - public class SkinInfo : IHasFiles, IHasPrimaryKey, ISoftDelete + public class SkinInfo : IHasFiles, IEquatable, IHasPrimaryKey, ISoftDelete { [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public int ID { get; set; } @@ -21,5 +22,7 @@ namespace osu.Game.Skinning public bool DeletePending { get; set; } public static SkinInfo Default { get; } = new SkinInfo { Name = "osu!lazer", Creator = "team osu!" }; + + public bool Equals(SkinInfo other) => other != null && ID == other.ID; } } From e9c583438761b00e2b0332c4198a53afe45df946 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 23 Feb 2018 13:22:33 +0900 Subject: [PATCH 69/69] Add query method for now --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/OsuGame.cs | 2 +- osu.Game/Skinning/SkinManager.cs | 10 ++++++++++ 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 854bee99a5..a65593ff82 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -48,7 +48,7 @@ namespace osu.Game.Database protected readonly IDatabaseContextFactory ContextFactory; - public readonly MutableDatabaseBackedStore ModelStore; + protected readonly MutableDatabaseBackedStore ModelStore; // ReSharper disable once NotAccessedField.Local (we should keep a reference to this so it is not finalised) private ArchiveImportIPCChannel ipc; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 17a72d3c87..95eb88c5c8 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -134,7 +134,7 @@ namespace osu.Game configSkin = LocalConfig.GetBindable(OsuSetting.Skin); SkinManager.CurrentSkinInfo.ValueChanged += s => configSkin.Value = s.ID; - configSkin.ValueChanged += id => SkinManager.CurrentSkinInfo.Value = SkinManager.ModelStore.ConsumableItems.FirstOrDefault(s => s.ID == id) ?? SkinInfo.Default; + configSkin.ValueChanged += id => SkinManager.CurrentSkinInfo.Value = SkinManager.Query(s => s.ID == id) ?? SkinInfo.Default; configSkin.TriggerChange(); LocalConfig.BindWith(OsuSetting.VolumeInactive, inactiveVolumeAdjust); diff --git a/osu.Game/Skinning/SkinManager.cs b/osu.Game/Skinning/SkinManager.cs index ac3347e1d0..0031968b2b 100644 --- a/osu.Game/Skinning/SkinManager.cs +++ b/osu.Game/Skinning/SkinManager.cs @@ -1,8 +1,11 @@ // 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 System.Linq.Expressions; +using Microsoft.EntityFrameworkCore; using osu.Framework.Configuration; using osu.Framework.Platform; using osu.Game.Database; @@ -35,5 +38,12 @@ namespace osu.Game.Skinning : base(storage, contextFactory, new SkinStore(contextFactory, storage), importHost) { } + + /// + /// Perform a lookup query on available s. + /// + /// The query. + /// The first result for the provided query, or null if no results were found. + public SkinInfo Query(Expression> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query); } }