From ad2f556133ca365fa1a441ddaf1c35690947b8da Mon Sep 17 00:00:00 2001 From: smoogipoo <smoogipoo@smgi.me> Date: Tue, 20 Feb 2018 18:01:45 +0900 Subject: [PATCH] Add hitobject overlays to selected hitobjects --- .../Selection/OsuHitObjectOverlayLayer.cs | 26 ++++++++ .../Selection/Overlays/HitCircleOverlay.cs | 33 +++++++++++ .../Selection/Overlays/SliderCircleOverlay.cs | 45 ++++++++++++++ .../Selection/Overlays/SliderOverlay.cs | 55 +++++++++++++++++ .../Edit/OsuHitObjectComposer.cs | 4 ++ .../Objects/Drawables/DrawableSlider.cs | 27 ++++++--- .../osu.Game.Rulesets.Osu.csproj | 4 ++ .../Visual/TestCaseEditorSelectionLayer.cs | 13 +++- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 59 +++++++++++++------ .../Edit/Layers/Selection/HitObjectOverlay.cs | 20 +++++++ .../Layers/Selection/HitObjectOverlayLayer.cs | 53 +++++++++++++++++ osu.Game/osu.Game.csproj | 2 + 12 files changed, 315 insertions(+), 26 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Edit/Layers/Selection/OsuHitObjectOverlayLayer.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleOverlay.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleOverlay.cs create mode 100644 osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderOverlay.cs create mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlay.cs create mode 100644 osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlayLayer.cs diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/OsuHitObjectOverlayLayer.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/OsuHitObjectOverlayLayer.cs new file mode 100644 index 0000000000..e0d1b34ca5 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/OsuHitObjectOverlayLayer.cs @@ -0,0 +1,26 @@ +// 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); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleOverlay.cs new file mode 100644 index 0000000000..4e64783840 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/HitCircleOverlay.cs @@ -0,0 +1,33 @@ +// 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.Allocation; +using osu.Game.Graphics; +using osu.Game.Rulesets.Edit.Layers.Selection; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; + +namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays +{ + public class HitCircleOverlay : HitObjectOverlay + { + public HitCircleOverlay(DrawableHitCircle hitCircle) + : base(hitCircle) + { + Origin = Anchor.Centre; + + Position = hitCircle.Position; + Size = hitCircle.Size; + Scale = hitCircle.Scale; + + AddInternal(new RingPiece()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleOverlay.cs new file mode 100644 index 0000000000..0d60f62a2f --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderCircleOverlay.cs @@ -0,0 +1,45 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Rulesets.Edit.Layers.Selection; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using OpenTK; + +namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays +{ + public class SliderCircleOverlay : HitObjectOverlay + { + public SliderCircleOverlay(DrawableHitCircle sliderHead, DrawableSlider slider) + : this(sliderHead, sliderHead.Position, slider) + { + } + + public SliderCircleOverlay(DrawableSliderTail sliderTail, DrawableSlider slider) + : this(sliderTail, ((Slider)slider.HitObject).Curve.PositionAt(1) + slider.HitObject.StackOffset, slider) + { + } + + private SliderCircleOverlay(DrawableOsuHitObject hitObject, Vector2 position, DrawableSlider slider) + : base(hitObject) + { + Origin = Anchor.Centre; + + Position = position; + Size = slider.HeadCircle.Size; + Scale = slider.HeadCircle.Scale; + + AddInternal(new RingPiece()); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderOverlay.cs b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderOverlay.cs new file mode 100644 index 0000000000..0a9b5638ea --- /dev/null +++ b/osu.Game.Rulesets.Osu/Edit/Layers/Selection/Overlays/SliderOverlay.cs @@ -0,0 +1,55 @@ +// 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.Allocation; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Rulesets.Edit.Layers.Selection; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; +using OpenTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays +{ + public class SliderOverlay : HitObjectOverlay + { + private readonly SliderBody body; + + private readonly DrawableSlider hitObject; + + public SliderOverlay(DrawableSlider slider) + : base(slider) + { + hitObject = slider; + + var obj = (Slider)slider.HitObject; + + InternalChildren = new Drawable[] + { + body = new SliderBody(obj) + { + AccentColour = Color4.Transparent, + Position = obj.StackedPosition, + PathWidth = obj.Scale * 64 + }, + new SliderCircleOverlay(slider.HeadCircle, slider), + new SliderCircleOverlay(slider.TailCircle, slider), + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + body.BorderColour = colours.Yellow; + } + + protected override void Update() + { + base.Update(); + + hitObject.GetCurrentProgress(out int span, out double progress); + body.UpdateProgress(progress, span); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index ae19706da3..70d49a6b4f 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -5,7 +5,9 @@ using System.Collections.Generic; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Edit.Layers.Selection; using osu.Game.Rulesets.Edit.Tools; +using osu.Game.Rulesets.Osu.Edit.Layers.Selection; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; @@ -29,5 +31,7 @@ namespace osu.Game.Rulesets.Osu.Edit }; protected override ScalableContainer CreateLayerContainer() => new ScalableContainer(OsuPlayfield.BASE_SIZE.X) { RelativeSizeAxes = Axes.Both }; + + protected override HitObjectOverlayLayer CreateHitObjectOverlayLayer() => new OsuHitObjectOverlayLayer(); } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 41df7ae4a4..b3f2f1850c 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables private readonly List<Drawable> components = new List<Drawable>(); public readonly DrawableHitCircle HeadCircle; + public readonly DrawableSliderTail TailCircle; + public readonly SliderBody Body; public readonly SliderBall Ball; @@ -29,7 +31,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { slider = s; - DrawableSliderTail tail; Container<DrawableSliderTick> ticks; Container<DrawableRepeatPoint> repeatPoints; @@ -51,7 +52,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Alpha = 0 }, HeadCircle = new DrawableHitCircle(s.HeadCircle), - tail = new DrawableSliderTail(s.TailCircle) + TailCircle = new DrawableSliderTail(s.TailCircle) }; components.Add(Body); @@ -59,8 +60,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AddNested(HeadCircle); - AddNested(tail); - components.Add(tail); + AddNested(TailCircle); + components.Add(TailCircle); foreach (var tick in s.NestedHitObjects.OfType<SliderTick>()) { @@ -96,10 +97,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables Tracking = Ball.Tracking; - double progress = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); - - int span = slider.SpanAt(progress); - progress = slider.ProgressAt(progress); + GetCurrentProgress(out int span, out double progress); if (span > currentSpan) currentSpan = span; @@ -155,6 +153,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } + /// <summary> + /// Finds the progress along the slider at the current time. + /// </summary> + /// <param name="span">The current span.</param> + /// <param name="progress">The current progress in the current span.</param> + public void GetCurrentProgress(out int span, out double progress) + { + double offset = MathHelper.Clamp((Time.Current - slider.StartTime) / slider.Duration, 0, 1); + + span = slider.SpanAt(offset); + progress = slider.ProgressAt(offset); + } + public Drawable ProxiedLayer => HeadCircle.ApproachCircle; public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Body.ReceiveMouseInputAt(screenSpacePos); diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 7838fb7707..53923e36ba 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -64,6 +64,10 @@ <ItemGroup> <Compile Include="Beatmaps\OsuBeatmapConverter.cs" /> <Compile Include="Beatmaps\OsuBeatmapProcessor.cs" /> + <Compile Include="Edit\Layers\Selection\OsuHitObjectOverlayLayer.cs" /> + <Compile Include="Edit\Layers\Selection\Overlays\HitCircleOverlay.cs" /> + <Compile Include="Edit\Layers\Selection\Overlays\SliderCircleOverlay.cs" /> + <Compile Include="Edit\Layers\Selection\Overlays\SliderOverlay.cs" /> <Compile Include="Edit\OsuEditPlayfield.cs" /> <Compile Include="Edit\OsuEditRulesetContainer.cs" /> <Compile Include="Edit\OsuHitObjectComposer.cs" /> diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index 5e0c0e165c..0db03b08a7 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -6,10 +6,13 @@ using System.Collections.Generic; using osu.Framework.Allocation; using OpenTK; using osu.Game.Beatmaps; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Layers.Selection; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu.Edit; +using osu.Game.Rulesets.Osu.Edit.Layers.Selection; +using osu.Game.Rulesets.Osu.Edit.Layers.Selection.Overlays; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Tests.Beatmaps; @@ -21,7 +24,15 @@ namespace osu.Game.Tests.Visual { typeof(SelectionBox), typeof(SelectionLayer), - typeof(CaptureBox) + typeof(CaptureBox), + typeof(HitObjectComposer), + typeof(OsuHitObjectComposer), + typeof(HitObjectOverlayLayer), + typeof(OsuHitObjectOverlayLayer), + typeof(HitObjectOverlay), + typeof(HitCircleOverlay), + typeof(SliderOverlay), + typeof(SliderCircleOverlay) }; [BackgroundDependencyLoader] diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 62669150aa..67d4e8cc92 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -27,7 +27,7 @@ namespace osu.Game.Rulesets.Edit protected ICompositionTool CurrentTool { get; private set; } private RulesetContainer rulesetContainer; - private readonly Container[] layerContainers = new Container[2]; + private readonly ScalableContainer[] layerContainers = new ScalableContainer[2]; protected HitObjectComposer(Ruleset ruleset) { @@ -49,20 +49,6 @@ namespace osu.Game.Rulesets.Edit return; } - layerContainers[0] = CreateLayerContainer(); - layerContainers[0].Child = new Container - { - Name = "Border", - RelativeSizeAxes = Axes.Both, - Masking = true, - BorderColour = Color4.White, - BorderThickness = 2, - Child = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0, AlwaysPresent = true } - }; - - layerContainers[1] = CreateLayerContainer(); - layerContainers[1].Child = new SelectionLayer(rulesetContainer.Playfield); - RadioButtonCollection toolboxCollection; InternalChild = new GridContainer { @@ -87,9 +73,9 @@ namespace osu.Game.Rulesets.Edit RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - layerContainers[0], + createBottomLayer(), rulesetContainer, - layerContainers[1] + createTopLayer() } } }, @@ -112,6 +98,40 @@ namespace osu.Game.Rulesets.Edit toolboxCollection.Items[0].Select(); } + private ScalableContainer createBottomLayer() + { + layerContainers[0] = CreateLayerContainer(); + layerContainers[0].Child = new Container + { + Name = "Border", + RelativeSizeAxes = Axes.Both, + Masking = true, + BorderColour = Color4.White, + BorderThickness = 2, + Child = new Box { RelativeSizeAxes = Axes.Both, Alpha = 0, AlwaysPresent = true } + }; + + return layerContainers[0]; + } + + private ScalableContainer createTopLayer() + { + var overlayLayer = CreateHitObjectOverlayLayer(); + var selectionLayer = new SelectionLayer(rulesetContainer.Playfield); + + selectionLayer.ObjectSelected += overlayLayer.AddOverlay; + selectionLayer.ObjectDeselected += overlayLayer.RemoveOverlay; + + layerContainers[1] = CreateLayerContainer(); + layerContainers[1].Children = new Drawable[] + { + overlayLayer, + selectionLayer, + }; + + return layerContainers[1]; + } + protected override void UpdateAfterChildren() { base.UpdateAfterChildren(); @@ -135,5 +155,10 @@ namespace osu.Game.Rulesets.Edit /// Creates a <see cref="ScalableContainer"/> which provides a layer above or below the <see cref="Playfield"/>. /// </summary> protected virtual ScalableContainer CreateLayerContainer() => new ScalableContainer(); + + /// <summary> + /// Creates the <see cref="HitObjectOverlayLayer"/> which overlays selected <see cref="DrawableHitObject"/>s. + /// </summary> + protected virtual HitObjectOverlayLayer CreateHitObjectOverlayLayer() => new HitObjectOverlayLayer(); } } diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlay.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlay.cs new file mode 100644 index 0000000000..e18627ea5d --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlay.cs @@ -0,0 +1,20 @@ +// 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 : CompositeDrawable + { + // 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; + } + } +} diff --git a/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlayLayer.cs b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlayLayer.cs new file mode 100644 index 0000000000..0b6e63d1fe --- /dev/null +++ b/osu.Game/Rulesets/Edit/Layers/Selection/HitObjectOverlayLayer.cs @@ -0,0 +1,53 @@ +// 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.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Drawables; + +namespace osu.Game.Rulesets.Edit.Layers.Selection +{ + public class HitObjectOverlayLayer : CompositeDrawable + { + private readonly Dictionary<DrawableHitObject, HitObjectOverlay> existingOverlays = new Dictionary<DrawableHitObject, HitObjectOverlay>(); + + public HitObjectOverlayLayer() + { + RelativeSizeAxes = Axes.Both; + } + + /// <summary> + /// Adds an overlay for a <see cref="DrawableHitObject"/> which adds movement support. + /// </summary> + /// <param name="hitObject">The <see cref="DrawableHitObject"/> to create an overlay for.</param> + public void AddOverlay(DrawableHitObject hitObject) + { + var overlay = CreateOverlayFor(hitObject); + if (overlay == null) + return; + + existingOverlays[hitObject] = overlay; + AddInternal(overlay); + } + + /// <summary> + /// Removes the overlay for a <see cref="DrawableHitObject"/>. + /// </summary> + /// <param name="hitObject">The <see cref="DrawableHitObject"/> to remove the overlay for.</param> + public void RemoveOverlay(DrawableHitObject hitObject) + { + if (!existingOverlays.TryGetValue(hitObject, out var existing)) + return; + + existing.Hide(); + existing.Expire(); + } + + /// <summary> + /// Creates a <see cref="HitObjectOverlay"/> for a specific <see cref="DrawableHitObject"/>. + /// </summary> + /// <param name="hitObject">The <see cref="DrawableHitObject"/> to create the overlay for.</param> + protected virtual HitObjectOverlay CreateOverlayFor(DrawableHitObject hitObject) => null; + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5a827e155b..e4ddea49e8 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -347,6 +347,8 @@ <Compile Include="Rulesets\Configuration\IRulesetConfigManager.cs" /> <Compile Include="Rulesets\Configuration\RulesetConfigManager.cs" /> <Compile Include="Rulesets\Edit\Layers\Selection\CaptureBox.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\IApplicableMod.cs" /> <Compile Include="Rulesets\Mods\IApplicableToBeatmapConverter.cs" />