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

Merge branch 'master' into mod-fixes

This commit is contained in:
Dean Herbert 2018-03-16 11:27:39 +09:00 committed by GitHub
commit 7ed84465fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
30 changed files with 410 additions and 305 deletions

View File

@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
yield break; yield break;
} }
var objects = IsForCurrentRuleset ? generateSpecific(original) : generateConverted(original); var objects = IsForCurrentRuleset ? generateSpecific(original, beatmap) : generateConverted(original, beatmap);
if (objects == null) if (objects == null)
yield break; yield break;
@ -110,10 +110,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// Method that generates hit objects for osu!mania specific beatmaps. /// Method that generates hit objects for osu!mania specific beatmaps.
/// </summary> /// </summary>
/// <param name="original">The original hit object.</param> /// <param name="original">The original hit object.</param>
/// <param name="originalBeatmap">The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.</param>
/// <returns>The hit objects generated.</returns> /// <returns>The hit objects generated.</returns>
private IEnumerable<ManiaHitObject> generateSpecific(HitObject original) private IEnumerable<ManiaHitObject> generateSpecific(HitObject original, Beatmap originalBeatmap)
{ {
var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern); var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap);
Pattern newPattern = generator.Generate(); Pattern newPattern = generator.Generate();
lastPattern = newPattern; lastPattern = newPattern;
@ -125,26 +126,25 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// Method that generates hit objects for non-osu!mania beatmaps. /// Method that generates hit objects for non-osu!mania beatmaps.
/// </summary> /// </summary>
/// <param name="original">The original hit object.</param> /// <param name="original">The original hit object.</param>
/// <param name="originalBeatmap">The original beatmap. This is used to look-up any values dependent on a fully-loaded beatmap.</param>
/// <returns>The hit objects generated.</returns> /// <returns>The hit objects generated.</returns>
private IEnumerable<ManiaHitObject> generateConverted(HitObject original) private IEnumerable<ManiaHitObject> generateConverted(HitObject original, Beatmap originalBeatmap)
{ {
var endTimeData = original as IHasEndTime; var endTimeData = original as IHasEndTime;
var distanceData = original as IHasDistance; var distanceData = original as IHasDistance;
var positionData = original as IHasPosition; var positionData = original as IHasPosition;
// Following lines currently commented out to appease resharper
Patterns.PatternGenerator conversion = null; Patterns.PatternGenerator conversion = null;
if (distanceData != null) if (distanceData != null)
conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern); conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern, originalBeatmap);
else if (endTimeData != null) else if (endTimeData != null)
conversion = new EndTimeObjectPatternGenerator(random, original, beatmap); conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, originalBeatmap);
else if (positionData != null) else if (positionData != null)
{ {
computeDensity(original.StartTime); computeDensity(original.StartTime);
conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair); conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair, originalBeatmap);
recordNote(original.StartTime, positionData.Position); recordNote(original.StartTime, positionData.Position);
} }
@ -153,10 +153,9 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
return null; return null;
Pattern newPattern = conversion.Generate(); Pattern newPattern = conversion.Generate();
lastPattern = newPattern;
var stairPatternGenerator = conversion as HitObjectPatternGenerator; lastPattern = conversion is EndTimeObjectPatternGenerator ? lastPattern : newPattern;
lastStair = stairPatternGenerator?.StairType ?? lastStair; lastStair = (conversion as HitObjectPatternGenerator)?.StairType ?? lastStair;
return newPattern.HitObjects; return newPattern.HitObjects;
} }
@ -166,8 +165,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// </summary> /// </summary>
private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator
{ {
public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern) public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern) : base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{ {
} }

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
@ -29,11 +30,11 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private PatternType convertType; private PatternType convertType;
public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern) public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern) : base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{ {
convertType = PatternType.None; convertType = PatternType.None;
if (Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode) if (!Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode)
convertType = PatternType.LowProbability; convertType = PatternType.LowProbability;
var distanceData = hitObject as IHasDistance; var distanceData = hitObject as IHasDistance;
@ -305,19 +306,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
p4 = 0; p4 = 0;
break; break;
case 3: case 3:
p2 = Math.Max(p2, 0.1); p2 = Math.Min(p2, 0.1);
p3 = 0; p3 = 0;
p4 = 0; p4 = 0;
break; break;
case 4: case 4:
p2 = Math.Max(p2, 0.3); p2 = Math.Min(p2, 0.3);
p3 = Math.Max(p3, 0.04); p3 = Math.Min(p3, 0.04);
p4 = 0; p4 = 0;
break; break;
case 5: case 5:
p2 = Math.Max(p2, 0.34); p2 = Math.Min(p2, 0.34);
p3 = Math.Max(p3, 0.1); p3 = Math.Min(p3, 0.1);
p4 = Math.Max(p4, 0.03); p4 = Math.Min(p4, 0.03);
break; break;
} }
@ -396,17 +397,19 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
// Create the hold note // Create the hold note
addToPattern(pattern, holdColumn, startTime, endTime); addToPattern(pattern, holdColumn, startTime, endTime);
int noteCount = 1; int nextColumn = Random.Next(RandomStart, TotalColumns);
int noteCount;
if (ConversionDifficulty > 6.5) if (ConversionDifficulty > 6.5)
noteCount = GetRandomNoteCount(0.63, 0); noteCount = GetRandomNoteCount(0.63, 0);
else if (ConversionDifficulty > 4) else if (ConversionDifficulty > 4)
noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0.12 : 0.45, 0); noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0.12 : 0.45, 0);
else if (ConversionDifficulty > 2.5) else if (ConversionDifficulty > 2.5)
noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0 : 0.24, 0); noteCount = GetRandomNoteCount(TotalColumns < 6 ? 0 : 0.24, 0);
else
noteCount = 0;
noteCount = Math.Min(TotalColumns - 1, noteCount); noteCount = Math.Min(TotalColumns - 1, noteCount);
bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP); bool ignoreHead = !sampleInfoListAt(startTime).Any(s => s.Name == SampleInfo.HIT_WHISTLE || s.Name == SampleInfo.HIT_FINISH || s.Name == SampleInfo.HIT_CLAP);
int nextColumn = Random.Next(RandomStart, TotalColumns);
var rowPattern = new Pattern(); var rowPattern = new Pattern();
for (int i = 0; i <= spanCount; i++) for (int i = 0; i <= spanCount; i++)

View File

@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Objects.Types;
using System.Linq; using System.Linq;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
@ -15,8 +16,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{ {
private readonly double endTime; private readonly double endTime;
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap) public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Beatmap originalBeatmap)
: base(random, hitObject, beatmap, new Pattern()) : base(random, hitObject, beatmap, new Pattern(), originalBeatmap)
{ {
var endtimeData = HitObject as IHasEndTime; var endtimeData = HitObject as IHasEndTime;

View File

@ -5,6 +5,7 @@ using System;
using System.Linq; using System.Linq;
using OpenTK; using OpenTK;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Mania.MathUtils; using osu.Game.Rulesets.Mania.MathUtils;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
@ -19,8 +20,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private readonly PatternType convertType; private readonly PatternType convertType;
public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair) public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair, Beatmap originalBeatmap)
: base(random, hitObject, beatmap, previousPattern) : base(random, hitObject, beatmap, previousPattern, originalBeatmap)
{ {
if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime)); if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime));
if (density < 0) throw new ArgumentOutOfRangeException(nameof(density)); if (density < 0) throw new ArgumentOutOfRangeException(nameof(density));
@ -308,20 +309,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
p5 = 0; p5 = 0;
break; break;
case 3: case 3:
p2 = Math.Max(p2, 0.1); p2 = Math.Min(p2, 0.1);
p3 = 0; p3 = 0;
p4 = 0; p4 = 0;
p5 = 0; p5 = 0;
break; break;
case 4: case 4:
p2 = Math.Max(p2, 0.23); p2 = Math.Min(p2, 0.23);
p3 = Math.Max(p3, 0.04); p3 = Math.Min(p3, 0.04);
p4 = 0; p4 = 0;
p5 = 0; p5 = 0;
break; break;
case 5: case 5:
p3 = Math.Max(p3, 0.15); p3 = Math.Min(p3, 0.15);
p4 = Math.Max(p4, 0.03); p4 = Math.Min(p4, 0.03);
p5 = 0; p5 = 0;
break; break;
} }
@ -355,23 +356,23 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
p3 = 0; p3 = 0;
break; break;
case 3: case 3:
centreProbability = Math.Max(centreProbability, 0.03); centreProbability = Math.Min(centreProbability, 0.03);
p2 = Math.Max(p2, 0.1); p2 = 0;
p3 = 0; p3 = 0;
break; break;
case 4: case 4:
centreProbability = 0; centreProbability = 0;
p2 = Math.Max(p2 * 2, 0.2); p2 = Math.Min(p2 * 2, 0.2);
p3 = 0; p3 = 0;
break; break;
case 5: case 5:
centreProbability = Math.Max(centreProbability, 0.03); centreProbability = Math.Min(centreProbability, 0.03);
p3 = 0; p3 = 0;
break; break;
case 6: case 6:
centreProbability = 0; centreProbability = 0;
p2 = Math.Max(p2 * 2, 0.5); p2 = Math.Min(p2 * 2, 0.5);
p3 = Math.Max(p3 * 2, 0.15); p3 = Math.Min(p3 * 2, 0.15);
break; break;
} }

View File

@ -25,14 +25,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary> /// </summary>
protected readonly FastRandom Random; protected readonly FastRandom Random;
protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern) /// <summary>
/// The beatmap which <see cref="HitObject"/> is being converted from.
/// </summary>
protected readonly Beatmap OriginalBeatmap;
protected PatternGenerator(FastRandom random, HitObject hitObject, ManiaBeatmap beatmap, Pattern previousPattern, Beatmap originalBeatmap)
: base(hitObject, beatmap, previousPattern) : base(hitObject, beatmap, previousPattern)
{ {
if (random == null) throw new ArgumentNullException(nameof(random)); if (random == null) throw new ArgumentNullException(nameof(random));
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap)); if (originalBeatmap == null) throw new ArgumentNullException(nameof(originalBeatmap));
if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern));
Random = random; Random = random;
OriginalBeatmap = originalBeatmap;
RandomStart = TotalColumns == 8 ? 1 : 0; RandomStart = TotalColumns == 8 ? 1 : 0;
} }
@ -94,17 +100,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
if (conversionDifficulty != null) if (conversionDifficulty != null)
return conversionDifficulty.Value; return conversionDifficulty.Value;
HitObject lastObject = Beatmap.HitObjects.LastOrDefault(); HitObject lastObject = OriginalBeatmap.HitObjects.LastOrDefault();
HitObject firstObject = Beatmap.HitObjects.FirstOrDefault(); HitObject firstObject = OriginalBeatmap.HitObjects.FirstOrDefault();
double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0); double drainTime = (lastObject?.StartTime ?? 0) - (firstObject?.StartTime ?? 0);
drainTime -= Beatmap.TotalBreakTime; drainTime -= OriginalBeatmap.TotalBreakTime;
if (drainTime == 0) if (drainTime == 0)
drainTime = 10000; drainTime = 10000000;
BeatmapDifficulty difficulty = Beatmap.BeatmapInfo.BaseDifficulty; // We need this in seconds
conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + Beatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15; drainTime /= 1000;
BeatmapDifficulty difficulty = OriginalBeatmap.BeatmapInfo.BaseDifficulty;
conversionDifficulty = ((difficulty.DrainRate + MathHelper.Clamp(difficulty.ApproachRate, 4, 7)) / 1.5 + OriginalBeatmap.HitObjects.Count / drainTime * 9f) / 38f * 5f / 1.15;
conversionDifficulty = Math.Min(conversionDifficulty.Value, 12); conversionDifficulty = Math.Min(conversionDifficulty.Value, 12);
return conversionDifficulty.Value; return conversionDifficulty.Value;

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Tests
private bool isForCurrentRuleset; private bool isForCurrentRuleset;
[NonParallelizable] [NonParallelizable]
[TestCase("basic", false), Ignore("See: https://github.com/ppy/osu/issues/2150")] [TestCase("basic", false)]
public void Test(string name, bool isForCurrentRuleset) public void Test(string name, bool isForCurrentRuleset)
{ {
this.isForCurrentRuleset = isForCurrentRuleset; this.isForCurrentRuleset = isForCurrentRuleset;

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" />