1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 12:33:01 +08:00

Merge pull request #2195 from smoogipoo/editor-hitobject-movement

Implement an interface for moving hitobjects in the editor
This commit is contained in:
Dean Herbert 2018-03-14 15:55:15 +09:00 committed by GitHub
commit edd053fcd2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 347 additions and 255 deletions

View File

@ -1,26 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Edit.Layers.Selection;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection
{
public class OsuHitObjectOverlayLayer : HitObjectOverlayLayer
{
protected override HitObjectOverlay CreateOverlayFor(DrawableHitObject hitObject)
{
switch (hitObject)
{
case DrawableHitCircle circle:
return new HitCircleOverlay(circle);
case DrawableSlider slider:
return new SliderOverlay(slider);
}
return base.CreateOverlayFor(hitObject);
}
}
}

View File

@ -4,15 +4,15 @@
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Edit.Layers.Selection; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
{ {
public class HitCircleOverlay : HitObjectOverlay public class HitCircleMask : HitObjectMask
{ {
public HitCircleOverlay(DrawableHitCircle hitCircle) public HitCircleMask(DrawableHitCircle hitCircle)
: base(hitCircle) : base(hitCircle)
{ {
Origin = Anchor.Centre; Origin = Anchor.Centre;
@ -22,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
Scale = hitCircle.Scale; Scale = hitCircle.Scale;
AddInternal(new RingPiece()); AddInternal(new RingPiece());
hitCircle.HitObject.PositionChanged += _ => Position = hitCircle.Position;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -4,7 +4,7 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Edit.Layers.Selection; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
@ -12,21 +12,21 @@ using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
{ {
public class SliderCircleOverlay : HitObjectOverlay public class SliderCircleMask : HitObjectMask
{ {
public SliderCircleOverlay(DrawableHitCircle sliderHead, DrawableSlider slider) public SliderCircleMask(DrawableHitCircle sliderHead, DrawableSlider slider)
: this(sliderHead, Vector2.Zero, slider) : this(sliderHead, Vector2.Zero, slider)
{ {
} }
public SliderCircleOverlay(DrawableSliderTail sliderTail, DrawableSlider slider) public SliderCircleMask(DrawableSliderTail sliderTail, DrawableSlider slider)
: this(sliderTail, ((Slider)slider.HitObject).Curve.PositionAt(1), slider) : this(sliderTail, ((Slider)slider.HitObject).Curve.PositionAt(1), slider)
{ {
} }
private readonly DrawableOsuHitObject hitObject; private readonly DrawableOsuHitObject hitObject;
private SliderCircleOverlay(DrawableOsuHitObject hitObject, Vector2 position, DrawableSlider slider) private SliderCircleMask(DrawableOsuHitObject hitObject, Vector2 position, DrawableSlider slider)
: base(hitObject) : base(hitObject)
{ {
this.hitObject = hitObject; this.hitObject = hitObject;

View File

@ -4,36 +4,41 @@
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Edit.Layers.Selection; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
{ {
public class SliderOverlay : HitObjectOverlay public class SliderMask : HitObjectMask
{ {
private readonly SliderBody body; private readonly SliderBody body;
private readonly DrawableSlider slider; private readonly DrawableSlider slider;
public SliderOverlay(DrawableSlider slider) public SliderMask(DrawableSlider slider)
: base(slider) : base(slider)
{ {
this.slider = slider; this.slider = slider;
var obj = (Slider)slider.HitObject; Position = slider.Position;
var sliderObject = (Slider)slider.HitObject;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
body = new SliderBody(obj) body = new SliderBody(sliderObject)
{ {
AccentColour = Color4.Transparent, AccentColour = Color4.Transparent,
PathWidth = obj.Scale * 64 PathWidth = sliderObject.Scale * 64
}, },
new SliderCircleOverlay(slider.HeadCircle, slider), new SliderCircleMask(slider.HeadCircle, slider),
new SliderCircleOverlay(slider.TailCircle, slider), new SliderCircleMask(slider.TailCircle, slider),
}; };
sliderObject.PositionChanged += _ => Position = slider.Position;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -46,12 +51,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays
{ {
base.Update(); base.Update();
Position = slider.Position;
Size = slider.Size; Size = slider.Size;
OriginPosition = slider.OriginPosition; OriginPosition = slider.OriginPosition;
// Need to cause one update // Need to cause one update
body.UpdateProgress(0); body.UpdateProgress(0);
} }
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => body.ReceiveMouseInputAt(screenSpacePos);
} }
} }

View File

@ -5,10 +5,11 @@ using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Layers.Selection;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Osu.Edit.Layers.Selection; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -32,6 +33,17 @@ namespace osu.Game.Rulesets.Osu.Edit
protected override ScalableContainer CreateLayerContainer() => new ScalableContainer(OsuPlayfield.BASE_SIZE.X) { RelativeSizeAxes = Axes.Both }; protected override ScalableContainer CreateLayerContainer() => new ScalableContainer(OsuPlayfield.BASE_SIZE.X) { RelativeSizeAxes = Axes.Both };
protected override HitObjectOverlayLayer CreateHitObjectOverlayLayer() => new OsuHitObjectOverlayLayer(); public override HitObjectMask CreateMaskFor(DrawableHitObject hitObject)
{
switch (hitObject)
{
case DrawableHitCircle circle:
return new HitCircleMask(circle);
case DrawableSlider slider:
return new SliderMask(slider);
}
return base.CreateMaskFor(hitObject);
}
} }
} }

View File

@ -66,6 +66,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
//may not be so correct //may not be so correct
Size = circle.DrawSize; Size = circle.DrawSize;
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
} }
protected override void CheckForJudgements(bool userTriggered, double timeOffset) protected override void CheckForJudgements(bool userTriggered, double timeOffset)

View File

@ -83,6 +83,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
components.Add(drawableRepeatPoint); components.Add(drawableRepeatPoint);
AddNested(drawableRepeatPoint); AddNested(drawableRepeatPoint);
} }
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -1,23 +1,41 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using OpenTK; using OpenTK;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit.Types;
namespace osu.Game.Rulesets.Osu.Objects namespace osu.Game.Rulesets.Osu.Objects
{ {
public abstract class OsuHitObject : HitObject, IHasCombo, IHasPosition public abstract class OsuHitObject : HitObject, IHasCombo, IHasEditablePosition
{ {
public const double OBJECT_RADIUS = 64; public const double OBJECT_RADIUS = 64;
public event Action<Vector2> PositionChanged;
public double TimePreempt = 600; public double TimePreempt = 600;
public double TimeFadein = 400; public double TimeFadein = 400;
public Vector2 Position { get; set; } private Vector2 position;
public Vector2 Position
{
get => position;
set
{
if (position == value)
return;
position = value;
PositionChanged?.Invoke(value);
}
}
public float X => Position.X; public float X => Position.X;
public float Y => Position.Y; public float Y => Position.Y;
@ -48,5 +66,7 @@ namespace osu.Game.Rulesets.Osu.Objects
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2; Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
} }
public virtual void OffsetPosition(Vector2 offset) => Position += offset;
} }
} }

View File

@ -95,7 +95,7 @@ namespace osu.Game.Rulesets.Osu.Objects
private void createSliderEnds() private void createSliderEnds()
{ {
HeadCircle = new HitCircle HeadCircle = new SliderCircle(this)
{ {
StartTime = StartTime, StartTime = StartTime,
Position = Position, Position = Position,
@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Objects
SampleControlPoint = SampleControlPoint SampleControlPoint = SampleControlPoint
}; };
TailCircle = new HitCircle TailCircle = new SliderCircle(this)
{ {
StartTime = EndTime, StartTime = EndTime,
Position = EndPosition, Position = EndPosition,

View File

@ -0,0 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects
{
public class SliderCircle : HitCircle
{
private readonly Slider slider;
public SliderCircle(Slider slider)
{
this.slider = slider;
}
public override void OffsetPosition(Vector2 offset) => slider.OffsetPosition(offset);
}
}

View File

@ -64,10 +64,9 @@
<ItemGroup> <ItemGroup>
<Compile Include="Beatmaps\OsuBeatmapConverter.cs" /> <Compile Include="Beatmaps\OsuBeatmapConverter.cs" />
<Compile Include="Beatmaps\OsuBeatmapProcessor.cs" /> <Compile Include="Beatmaps\OsuBeatmapProcessor.cs" />
<Compile Include="Edit\Layers\Selection\OsuHitObjectOverlayLayer.cs" /> <Compile Include="Edit\Layers\Selection\Overlays\HitCircleMask.cs" />
<Compile Include="Edit\Layers\Selection\Overlays\HitCircleOverlay.cs" /> <Compile Include="Edit\Layers\Selection\Overlays\SliderCircleMask.cs" />
<Compile Include="Edit\Layers\Selection\Overlays\SliderCircleOverlay.cs" /> <Compile Include="Edit\Layers\Selection\Overlays\SliderMask.cs" />
<Compile Include="Edit\Layers\Selection\Overlays\SliderOverlay.cs" />
<Compile Include="Edit\OsuEditPlayfield.cs" /> <Compile Include="Edit\OsuEditPlayfield.cs" />
<Compile Include="Edit\OsuEditRulesetContainer.cs" /> <Compile Include="Edit\OsuEditRulesetContainer.cs" />
<Compile Include="Edit\OsuHitObjectComposer.cs" /> <Compile Include="Edit\OsuHitObjectComposer.cs" />
@ -118,6 +117,7 @@
<Compile Include="Objects\Drawables\Pieces\SliderBody.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBody.cs" />
<Compile Include="Objects\ISliderProgress.cs" /> <Compile Include="Objects\ISliderProgress.cs" />
<Compile Include="Objects\RepeatPoint.cs" /> <Compile Include="Objects\RepeatPoint.cs" />
<Compile Include="Objects\SliderCircle.cs" />
<Compile Include="Objects\SliderTick.cs" /> <Compile Include="Objects\SliderTick.cs" />
<Compile Include="OsuDifficulty\OsuDifficultyCalculator.cs" /> <Compile Include="OsuDifficulty\OsuDifficultyCalculator.cs" />
<Compile Include="OsuDifficulty\Preprocessing\OsuDifficultyBeatmap.cs" /> <Compile Include="OsuDifficulty\Preprocessing\OsuDifficultyBeatmap.cs" />

View File

@ -8,13 +8,12 @@ using osu.Framework.Allocation;
using OpenTK; using OpenTK;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Layers.Selection;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Edit.Layers.Selection;
using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays; using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Screens.Compose.Layers;
using osu.Game.Tests.Beatmaps; using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
@ -24,17 +23,15 @@ namespace osu.Game.Tests.Visual
{ {
public override IReadOnlyList<Type> RequiredTypes => new[] public override IReadOnlyList<Type> RequiredTypes => new[]
{ {
typeof(SelectionBox),
typeof(SelectionLayer), typeof(SelectionLayer),
typeof(CaptureBox), typeof(SelectionBox),
typeof(HitObjectComposer), typeof(HitObjectComposer),
typeof(OsuHitObjectComposer), typeof(OsuHitObjectComposer),
typeof(HitObjectOverlayLayer), typeof(HitObjectMaskLayer),
typeof(OsuHitObjectOverlayLayer), typeof(HitObjectMask),
typeof(HitObjectOverlay), typeof(HitCircleMask),
typeof(HitCircleOverlay), typeof(SliderMask),
typeof(SliderOverlay), typeof(SliderCircleMask)
typeof(SliderCircleOverlay)
}; };
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -10,10 +10,10 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Layers;
using osu.Game.Rulesets.Edit.Layers.Selection;
using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Screens.Compose.Layers;
using osu.Game.Screens.Edit.Screens.Compose.RadioButtons; using osu.Game.Screens.Edit.Screens.Compose.RadioButtons;
namespace osu.Game.Rulesets.Edit namespace osu.Game.Rulesets.Edit
@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Edit
return; return;
} }
HitObjectOverlayLayer hitObjectOverlayLayer = CreateHitObjectOverlayLayer(); HitObjectMaskLayer hitObjectMaskLayer = new HitObjectMaskLayer(this);
SelectionLayer selectionLayer = new SelectionLayer(rulesetContainer.Playfield); SelectionLayer selectionLayer = new SelectionLayer(rulesetContainer.Playfield);
var layerBelowRuleset = new BorderLayer var layerBelowRuleset = new BorderLayer
@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Edit
layerAboveRuleset.Children = new Drawable[] layerAboveRuleset.Children = new Drawable[]
{ {
selectionLayer, // Below object overlays for input selectionLayer, // Below object overlays for input
hitObjectOverlayLayer, hitObjectMaskLayer,
selectionLayer.CreateProxy() // Proxy above object overlays for selections selectionLayer.CreateProxy() // Proxy above object overlays for selections
}; };
@ -106,8 +106,10 @@ namespace osu.Game.Rulesets.Edit
} }
}; };
selectionLayer.ObjectSelected += hitObjectOverlayLayer.AddOverlay; selectionLayer.ObjectSelected += hitObjectMaskLayer.AddOverlay;
selectionLayer.ObjectDeselected += hitObjectOverlayLayer.RemoveOverlay; selectionLayer.ObjectDeselected += hitObjectMaskLayer.RemoveOverlay;
selectionLayer.SelectionCleared += hitObjectMaskLayer.RemoveSelectionOverlay;
selectionLayer.SelectionFinished += hitObjectMaskLayer.AddSelectionOverlay;
toolboxCollection.Items = toolboxCollection.Items =
new[] { new RadioButton("Select", () => setCompositionTool(null)) } new[] { new RadioButton("Select", () => setCompositionTool(null)) }
@ -138,14 +140,22 @@ namespace osu.Game.Rulesets.Edit
protected abstract IReadOnlyList<ICompositionTool> CompositionTools { get; } protected abstract IReadOnlyList<ICompositionTool> CompositionTools { get; }
/// <summary>
/// Creates a <see cref="HitObjectMask"/> for a specific <see cref="DrawableHitObject"/>.
/// </summary>
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to create the overlay for.</param>
public virtual HitObjectMask CreateMaskFor(DrawableHitObject hitObject) => null;
/// <summary>
/// Creates a <see cref="SelectionBox"/> which outlines <see cref="DrawableHitObject"/>s
/// and handles all hitobject movement/pattern adjustments.
/// </summary>
/// <param name="overlays">The <see cref="DrawableHitObject"/> overlays.</param>
public virtual SelectionBox CreateSelectionOverlay(IReadOnlyList<HitObjectMask> overlays) => new SelectionBox(overlays);
/// <summary> /// <summary>
/// Creates a <see cref="ScalableContainer"/> which provides a layer above or below the <see cref="Playfield"/>. /// Creates a <see cref="ScalableContainer"/> which provides a layer above or below the <see cref="Playfield"/>.
/// </summary> /// </summary>
protected virtual ScalableContainer CreateLayerContainer() => new ScalableContainer { RelativeSizeAxes = Axes.Both }; protected virtual ScalableContainer CreateLayerContainer() => new ScalableContainer { RelativeSizeAxes = Axes.Both };
/// <summary>
/// Creates the <see cref="HitObjectOverlayLayer"/> which overlays selected <see cref="DrawableHitObject"/>s.
/// </summary>
protected virtual HitObjectOverlayLayer CreateHitObjectOverlayLayer() => new HitObjectOverlayLayer();
} }
} }

View File

@ -0,0 +1,21 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Edit
{
/// <summary>
/// A mask placed above a <see cref="DrawableHitObject"/> adding editing functionality.
/// </summary>
public class HitObjectMask : Container
{
public readonly DrawableHitObject HitObject;
public HitObjectMask(DrawableHitObject hitObject)
{
HitObject = hitObject;
}
}
}

View File

@ -1,68 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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
{
/// <summary>
/// A box which encloses <see cref="DrawableHitObject"/>s.
/// </summary>
public class CaptureBox : VisibilityContainer
{
private readonly IDrawable captureArea;
private readonly IReadOnlyList<DrawableHitObject> capturedObjects;
public CaptureBox(IDrawable captureArea, IReadOnlyList<DrawableHitObject> capturedObjects)
{
this.captureArea = captureArea;
this.capturedObjects = capturedObjects;
Masking = true;
BorderThickness = SelectionBox.BORDER_RADIUS;
InternalChild = new Box
{
RelativeSizeAxes = Axes.Both,
AlwaysPresent = true,
Alpha = 0
};
State = Visibility.Visible;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BorderColour = colours.Yellow;
// Move the rectangle to cover the hitobjects
var topLeft = new Vector2(float.MaxValue, float.MaxValue);
var bottomRight = new Vector2(float.MinValue, float.MinValue);
foreach (var obj in capturedObjects)
{
topLeft = Vector2.ComponentMin(topLeft, captureArea.ToLocalSpace(obj.SelectionQuad.TopLeft));
bottomRight = Vector2.ComponentMax(bottomRight, captureArea.ToLocalSpace(obj.SelectionQuad.BottomRight));
}
topLeft -= new Vector2(5);
bottomRight += new Vector2(5);
Size = bottomRight - topLeft;
Position = topLeft;
}
public override bool DisposeOnDeathRemoval => true;
protected override void PopIn() => this.FadeIn();
protected override void PopOut() => this.FadeOut();
}
}

View File

@ -1,25 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Edit.Layers.Selection
{
public class HitObjectOverlay : OverlayContainer
{
// ReSharper disable once NotAccessedField.Local
// This will be used later to handle drag movement, etc
private readonly DrawableHitObject hitObject;
public HitObjectOverlay(DrawableHitObject hitObject)
{
this.hitObject = hitObject;
State = Visibility.Visible;
}
protected override void PopIn() => Alpha = 1;
protected override void PopOut() => Alpha = 0;
}
}

View File

@ -1,49 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Edit.Layers.Selection
{
/// <summary>
/// A box that represents a drag selection.
/// </summary>
public class SelectionBox : VisibilityContainer
{
public const float BORDER_RADIUS = 2;
/// <summary>
/// Creates a new <see cref="SelectionBox"/>.
/// </summary>
public SelectionBox()
{
Masking = true;
BorderColour = Color4.White;
BorderThickness = BORDER_RADIUS;
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.1f
};
}
public void SetDragRectangle(RectangleF rectangle)
{
var topLeft = Parent.ToLocalSpace(rectangle.TopLeft);
var bottomRight = Parent.ToLocalSpace(rectangle.BottomRight);
Position = topLeft;
Size = bottomRight - topLeft;
}
public override bool DisposeOnDeathRemoval => true;
protected override void PopIn() => this.FadeIn(250, Easing.OutQuint);
protected override void PopOut() => this.FadeOut(250, Easing.OutQuint);
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
namespace osu.Game.Rulesets.Edit.Types
{
public interface IHasEditablePosition : IHasPosition
{
void OffsetPosition(Vector2 offset);
}
}

View File

@ -6,7 +6,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Rulesets.Edit.Layers namespace osu.Game.Screens.Edit.Screens.Compose.Layers
{ {
public class BorderLayer : Container public class BorderLayer : Container
{ {

View File

@ -1,20 +1,25 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using System.Linq;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Edit.Layers.Selection namespace osu.Game.Screens.Edit.Screens.Compose.Layers
{ {
public class HitObjectOverlayLayer : CompositeDrawable public class HitObjectMaskLayer : CompositeDrawable
{ {
private readonly Dictionary<DrawableHitObject, HitObjectOverlay> existingOverlays = new Dictionary<DrawableHitObject, HitObjectOverlay>(); private readonly HitObjectComposer composer;
private readonly Container<HitObjectMask> overlayContainer;
public HitObjectOverlayLayer() public HitObjectMaskLayer(HitObjectComposer composer)
{ {
this.composer = composer;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
InternalChild = overlayContainer = new Container<HitObjectMask> { RelativeSizeAxes = Axes.Both };
} }
/// <summary> /// <summary>
@ -23,12 +28,11 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to create an overlay for.</param> /// <param name="hitObject">The <see cref="DrawableHitObject"/> to create an overlay for.</param>
public void AddOverlay(DrawableHitObject hitObject) public void AddOverlay(DrawableHitObject hitObject)
{ {
var overlay = CreateOverlayFor(hitObject); var overlay = composer.CreateMaskFor(hitObject);
if (overlay == null) if (overlay == null)
return; return;
existingOverlays[hitObject] = overlay; overlayContainer.Add(overlay);
AddInternal(overlay);
} }
/// <summary> /// <summary>
@ -37,17 +41,22 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to remove the overlay for.</param> /// <param name="hitObject">The <see cref="DrawableHitObject"/> to remove the overlay for.</param>
public void RemoveOverlay(DrawableHitObject hitObject) public void RemoveOverlay(DrawableHitObject hitObject)
{ {
if (!existingOverlays.TryGetValue(hitObject, out var existing)) var existing = overlayContainer.FirstOrDefault(h => h.HitObject == hitObject);
if (existing == null)
return; return;
existing.Hide(); existing.Hide();
existing.Expire(); existing.Expire();
} }
/// <summary> private SelectionBox currentSelectionBox;
/// Creates a <see cref="HitObjectOverlay"/> for a specific <see cref="DrawableHitObject"/>.
/// </summary> public void AddSelectionOverlay() => AddInternal(currentSelectionBox = composer.CreateSelectionOverlay(overlayContainer));
/// <param name="hitObject">The <see cref="DrawableHitObject"/> to create the overlay for.</param>
protected virtual HitObjectOverlay CreateOverlayFor(DrawableHitObject hitObject) => null; public void RemoveSelectionOverlay()
{
currentSelectionBox?.Hide();
currentSelectionBox?.Expire();
}
} }
} }

View File

@ -0,0 +1,102 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Types;
using osu.Game.Rulesets.Objects.Drawables;
using OpenTK;
namespace osu.Game.Screens.Edit.Screens.Compose.Layers
{
/// <summary>
/// A box which surrounds <see cref="DrawableHitObject"/>s and provides interactive handles, context menus etc.
/// </summary>
public class SelectionBox : VisibilityContainer
{
private readonly IReadOnlyList<HitObjectMask> overlays;
public const float BORDER_RADIUS = 2;
public SelectionBox(IReadOnlyList<HitObjectMask> overlays)
{
this.overlays = overlays;
Masking = true;
BorderThickness = BORDER_RADIUS;
InternalChild = new Box
{
RelativeSizeAxes = Axes.Both,
AlwaysPresent = true,
Alpha = 0
};
State = Visibility.Visible;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BorderColour = colours.Yellow;
}
protected override void Update()
{
base.Update();
// Todo: We might need to optimise this
// Move the rectangle to cover the hitobjects
var topLeft = new Vector2(float.MaxValue, float.MaxValue);
var bottomRight = new Vector2(float.MinValue, float.MinValue);
foreach (var obj in overlays)
{
topLeft = Vector2.ComponentMin(topLeft, Parent.ToLocalSpace(obj.HitObject.SelectionQuad.TopLeft));
bottomRight = Vector2.ComponentMax(bottomRight, Parent.ToLocalSpace(obj.HitObject.SelectionQuad.BottomRight));
}
topLeft -= new Vector2(5);
bottomRight += new Vector2(5);
Size = bottomRight - topLeft;
Position = topLeft;
}
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => overlays.Any(o => o.ReceiveMouseInputAt(screenSpacePos));
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => true;
protected override bool OnDragStart(InputState state) => true;
protected override bool OnDrag(InputState state)
{
// Todo: Various forms of snapping
foreach (var hitObject in overlays.Select(o => o.HitObject.HitObject))
{
switch (hitObject)
{
case IHasEditablePosition editablePosition:
editablePosition.OffsetPosition(state.Mouse.Delta);
break;
}
}
return true;
}
protected override bool OnDragEnd(InputState state) => true;
public override bool DisposeOnDeathRemoval => true;
protected override void PopIn() => this.FadeIn();
protected override void PopOut() => this.FadeOut();
}
}

View File

@ -8,12 +8,14 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using OpenTK; using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Edit.Layers.Selection namespace osu.Game.Screens.Edit.Screens.Compose.Layers
{ {
public class SelectionLayer : CompositeDrawable public class SelectionLayer : CompositeDrawable
{ {
@ -27,6 +29,16 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection
/// </summary> /// </summary>
public event Action<DrawableHitObject> ObjectDeselected; public event Action<DrawableHitObject> ObjectDeselected;
/// <summary>
/// Invoked when the selection has been cleared.
/// </summary>
public event Action SelectionCleared;
/// <summary>
/// Invoked when the user has finished selecting all <see cref="DrawableHitObject"/>s.
/// </summary>
public event Action SelectionFinished;
private readonly Playfield playfield; private readonly Playfield playfield;
public SelectionLayer(Playfield playfield) public SelectionLayer(Playfield playfield)
@ -36,8 +48,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
} }
private SelectionBox selectionBox; private DragBox dragBox;
private CaptureBox captureBox;
private readonly HashSet<DrawableHitObject> selectedHitObjects = new HashSet<DrawableHitObject>(); private readonly HashSet<DrawableHitObject> selectedHitObjects = new HashSet<DrawableHitObject>();
@ -49,20 +60,20 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection
protected override bool OnDragStart(InputState state) protected override bool OnDragStart(InputState state)
{ {
AddInternal(selectionBox = new SelectionBox()); AddInternal(dragBox = new DragBox());
return true; return true;
} }
protected override bool OnDrag(InputState state) protected override bool OnDrag(InputState state)
{ {
selectionBox.Show(); dragBox.Show();
var dragPosition = state.Mouse.NativeState.Position; var dragPosition = state.Mouse.NativeState.Position;
var dragStartPosition = state.Mouse.NativeState.PositionMouseDown ?? dragPosition; var dragStartPosition = state.Mouse.NativeState.PositionMouseDown ?? dragPosition;
var screenSpaceDragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y); var screenSpaceDragQuad = new Quad(dragStartPosition.X, dragStartPosition.Y, dragPosition.X - dragStartPosition.X, dragPosition.Y - dragStartPosition.Y);
selectionBox.SetDragRectangle(screenSpaceDragQuad.AABBFloat); dragBox.SetDragRectangle(screenSpaceDragQuad.AABBFloat);
selectQuad(screenSpaceDragQuad); selectQuad(screenSpaceDragQuad);
return true; return true;
@ -70,8 +81,8 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection
protected override bool OnDragEnd(InputState state) protected override bool OnDragEnd(InputState state)
{ {
selectionBox.Hide(); dragBox.Hide();
selectionBox.Expire(); dragBox.Expire();
finishSelection(); finishSelection();
@ -95,7 +106,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection
if (!select(hitObject)) if (!select(hitObject))
return; return;
clearCapture(); clearSelection();
finishSelection(); finishSelection();
} }
@ -122,7 +133,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection
if (!deselect(hitObject)) if (!deselect(hitObject))
return; return;
clearCapture(); clearSelection();
finishSelection(); finishSelection();
} }
@ -148,7 +159,7 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection
selectedHitObjects.ForEach(h => ObjectDeselected?.Invoke(h)); selectedHitObjects.ForEach(h => ObjectDeselected?.Invoke(h));
selectedHitObjects.Clear(); selectedHitObjects.Clear();
clearCapture(); clearSelection();
} }
/// <summary> /// <summary>
@ -180,18 +191,49 @@ namespace osu.Game.Rulesets.Edit.Layers.Selection
select(target); select(target);
} }
private void clearCapture() private void clearSelection() => SelectionCleared?.Invoke();
{
captureBox?.Hide();
captureBox?.Expire();
}
private void finishSelection() private void finishSelection()
{ {
if (selectedHitObjects.Count == 0) if (selectedHitObjects.Count == 0)
return; return;
SelectionFinished?.Invoke();
}
AddInternal(captureBox = new CaptureBox(this, selectedHitObjects.ToList())); /// <summary>
/// A box that represents a drag selection.
/// </summary>
private class DragBox : VisibilityContainer
{
/// <summary>
/// Creates a new <see cref="DragBox"/>.
/// </summary>
public DragBox()
{
Masking = true;
BorderColour = Color4.White;
BorderThickness = SelectionBox.BORDER_RADIUS;
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.1f
};
}
public void SetDragRectangle(RectangleF rectangle)
{
var topLeft = Parent.ToLocalSpace(rectangle.TopLeft);
var bottomRight = Parent.ToLocalSpace(rectangle.BottomRight);
Position = topLeft;
Size = bottomRight - topLeft;
}
public override bool DisposeOnDeathRemoval => true;
protected override void PopIn() => this.FadeIn(250, Easing.OutQuint);
protected override void PopOut() => this.FadeOut(250, Easing.OutQuint);
} }
} }
} }

View File

@ -8,6 +8,9 @@ using osu.Game.Beatmaps;
namespace osu.Game.Screens.Edit.Screens namespace osu.Game.Screens.Edit.Screens
{ {
/// <summary>
/// TODO: eventually make this inherit Screen and add a local scren stack inside the Editor.
/// </summary>
public class EditorScreen : Container public class EditorScreen : Container
{ {
public readonly Bindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>(); public readonly Bindable<WorkingBeatmap> Beatmap = new Bindable<WorkingBeatmap>();

View File

@ -362,10 +362,8 @@
<Compile Include="Overlays\Volume\VolumeMeter.cs" /> <Compile Include="Overlays\Volume\VolumeMeter.cs" />
<Compile Include="Rulesets\Configuration\IRulesetConfigManager.cs" /> <Compile Include="Rulesets\Configuration\IRulesetConfigManager.cs" />
<Compile Include="Rulesets\Configuration\RulesetConfigManager.cs" /> <Compile Include="Rulesets\Configuration\RulesetConfigManager.cs" />
<Compile Include="Rulesets\Edit\Layers\BorderLayer.cs" /> <Compile Include="Rulesets\Edit\HitObjectMask.cs" />
<Compile Include="Rulesets\Edit\Layers\Selection\CaptureBox.cs" /> <Compile Include="Rulesets\Edit\Types\IHasEditablePosition.cs" />
<Compile Include="Rulesets\Edit\Layers\Selection\HitObjectOverlay.cs" />
<Compile Include="Rulesets\Edit\Layers\Selection\HitObjectOverlayLayer.cs" />
<Compile Include="Rulesets\Mods\IApplicableFailOverride.cs" /> <Compile Include="Rulesets\Mods\IApplicableFailOverride.cs" />
<Compile Include="Rulesets\Mods\IApplicableMod.cs" /> <Compile Include="Rulesets\Mods\IApplicableMod.cs" />
<Compile Include="Rulesets\Mods\IApplicableToBeatmapConverter.cs" /> <Compile Include="Rulesets\Mods\IApplicableToBeatmapConverter.cs" />
@ -380,6 +378,10 @@
<Compile Include="Rulesets\Replays\Types\IConvertibleReplayFrame.cs" /> <Compile Include="Rulesets\Replays\Types\IConvertibleReplayFrame.cs" />
<Compile Include="Rulesets\Scoring\Legacy\LegacyScoreParser.cs" /> <Compile Include="Rulesets\Scoring\Legacy\LegacyScoreParser.cs" />
<Compile Include="Rulesets\UI\JudgementContainer.cs" /> <Compile Include="Rulesets\UI\JudgementContainer.cs" />
<Compile Include="Screens\Edit\Screens\Compose\Layers\BorderLayer.cs" />
<Compile Include="Screens\Edit\Screens\Compose\Layers\HitObjectMaskLayer.cs" />
<Compile Include="Screens\Edit\Screens\Compose\Layers\SelectionBox.cs" />
<Compile Include="Screens\Edit\Screens\Compose\Layers\SelectionLayer.cs" />
<Compile Include="Screens\Play\ScreenWithBeatmapBackground.cs" /> <Compile Include="Screens\Play\ScreenWithBeatmapBackground.cs" />
<Compile Include="Screens\Play\PlayerSettings\VisualSettings.cs" /> <Compile Include="Screens\Play\PlayerSettings\VisualSettings.cs" />
<Compile Include="Rulesets\Objects\CatmullApproximator.cs" /> <Compile Include="Rulesets\Objects\CatmullApproximator.cs" />
@ -394,8 +396,6 @@
<Compile Include="Rulesets\UI\Scrolling\ScrollingRulesetContainer.cs" /> <Compile Include="Rulesets\UI\Scrolling\ScrollingRulesetContainer.cs" />
<Compile Include="Screens\Select\ImportFromStablePopup.cs" /> <Compile Include="Screens\Select\ImportFromStablePopup.cs" />
<Compile Include="Overlays\Settings\SettingsButton.cs" /> <Compile Include="Overlays\Settings\SettingsButton.cs" />
<Compile Include="Rulesets\Edit\Layers\Selection\SelectionBox.cs" />
<Compile Include="Rulesets\Edit\Layers\Selection\SelectionLayer.cs" />
<Compile Include="Screens\Edit\Components\BottomBarContainer.cs" /> <Compile Include="Screens\Edit\Components\BottomBarContainer.cs" />
<Compile Include="Screens\Edit\Components\PlaybackControl.cs" /> <Compile Include="Screens\Edit\Components\PlaybackControl.cs" />
<Compile Include="Screens\Edit\Components\TimeInfoContainer.cs" /> <Compile Include="Screens\Edit\Components\TimeInfoContainer.cs" />