diff --git a/osu.Game.Rulesets.Catch/Edit/CatchBeatSnapGrid.cs b/osu.Game.Rulesets.Catch/Edit/CatchBeatSnapGrid.cs index 6862696b3a..40bd08455f 100644 --- a/osu.Game.Rulesets.Catch/Edit/CatchBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Catch/Edit/CatchBeatSnapGrid.cs @@ -1,180 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; -using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Caching; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Catch.UI; using osu.Game.Rulesets.Edit; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI.Scrolling; -using osu.Game.Screens.Edit; -using osuTK.Graphics; +using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Catch.Edit { - /// - /// A grid which displays coloured beat divisor lines in proximity to the selection or placement cursor. - /// - /// - /// This class heavily borrows from osu!mania's implementation (ManiaBeatSnapGrid). - /// If further changes are to be made, they should also be applied there. - /// If the scale of the changes are large enough, abstracting may be a good path. - /// - public partial class CatchBeatSnapGrid : Component + public partial class CatchBeatSnapGrid : BeatSnapGrid { - private const double visible_range = 750; - - /// - /// The range of time values of the current selection. - /// - public (double start, double end)? SelectionTimeRange + protected override IEnumerable GetTargetContainers(HitObjectComposer composer) => new[] { - set - { - if (value == selectionTimeRange) - return; - - selectionTimeRange = value; - lineCache.Invalidate(); - } - } - - [Resolved] - private EditorBeatmap beatmap { get; set; } = null!; - - [Resolved] - private OsuColour colours { get; set; } = null!; - - [Resolved] - private BindableBeatDivisor beatDivisor { get; set; } = null!; - - private readonly Cached lineCache = new Cached(); - - private (double start, double end)? selectionTimeRange; - - private ScrollingHitObjectContainer lineContainer = null!; - - [BackgroundDependencyLoader] - private void load(HitObjectComposer composer) - { - lineContainer = new ScrollingHitObjectContainer(); - - ((CatchPlayfield)composer.Playfield).UnderlayElements.Add(lineContainer); - - beatDivisor.BindValueChanged(_ => createLines(), true); - } - - protected override void Update() - { - base.Update(); - - if (!lineCache.IsValid) - { - lineCache.Validate(); - createLines(); - } - } - - private readonly Stack availableLines = new Stack(); - - private void createLines() - { - foreach (var line in lineContainer.Objects.OfType()) - availableLines.Push(line); - - lineContainer.Clear(); - - if (selectionTimeRange == null) - return; - - var range = selectionTimeRange.Value; - - var timingPoint = beatmap.ControlPointInfo.TimingPointAt(range.start - visible_range); - - double time = timingPoint.Time; - int beat = 0; - - // progress time until in the visible range. - while (time < range.start - visible_range) - { - time += timingPoint.BeatLength / beatDivisor.Value; - beat++; - } - - while (time < range.end + visible_range) - { - var nextTimingPoint = beatmap.ControlPointInfo.TimingPointAt(time); - - // switch to the next timing point if we have reached it. - if (nextTimingPoint.Time > timingPoint.Time) - { - beat = 0; - time = nextTimingPoint.Time; - timingPoint = nextTimingPoint; - } - - Color4 colour = BindableBeatDivisor.GetColourFor( - BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value), colours); - - if (!availableLines.TryPop(out var line)) - line = new DrawableGridLine(); - - line.HitObject.StartTime = time; - line.Colour = colour; - - lineContainer.Add(line); - - beat++; - time += timingPoint.BeatLength / beatDivisor.Value; - } - - // required to update ScrollingHitObjectContainer's cache. - lineContainer.UpdateSubTree(); - - foreach (var line in lineContainer.Objects.OfType()) - { - time = line.HitObject.StartTime; - - if (time >= range.start && time <= range.end) - line.Alpha = 1; - else - { - double timeSeparation = time < range.start ? range.start - time : time - range.end; - line.Alpha = (float)Math.Max(0, 1 - timeSeparation / visible_range); - } - } - } - - private partial class DrawableGridLine : DrawableHitObject - { - public DrawableGridLine() - : base(new HitObject()) - { - RelativeSizeAxes = Axes.X; - Height = 2; - - AddInternal(new Box { RelativeSizeAxes = Axes.Both }); - } - - [BackgroundDependencyLoader] - private void load() - { - Origin = Anchor.BottomLeft; - Anchor = Anchor.BottomLeft; - } - - protected override void UpdateInitialTransforms() - { - // don't perform any fading – we are handling that ourselves. - LifetimeEnd = HitObject.StartTime + visible_range; - } - } + ((CatchPlayfield)composer.Playfield).UnderlayElements + }; } } diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs index 4d45e16588..61c730912f 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaBeatSnapGrid.cs @@ -1,206 +1,23 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.Linq; -using osu.Framework.Allocation; -using osu.Framework.Bindables; -using osu.Framework.Caching; -using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Pooling; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Mania.UI; -using osu.Game.Rulesets.Objects; -using osu.Game.Rulesets.Objects.Drawables; -using osu.Game.Rulesets.UI.Scrolling; -using osu.Game.Screens.Edit; -using osuTK.Graphics; +using osu.Game.Screens.Edit.Compose.Components; namespace osu.Game.Rulesets.Mania.Edit { - /// - /// A grid which displays coloured beat divisor lines in proximity to the selection or placement cursor. - /// - public partial class ManiaBeatSnapGrid : CompositeComponent + public partial class ManiaBeatSnapGrid : BeatSnapGrid { - private const double visible_range = 750; - - /// - /// The range of time values of the current selection. - /// - public (double start, double end)? SelectionTimeRange + protected override IEnumerable GetTargetContainers(HitObjectComposer composer) { - set - { - if (value == selectionTimeRange) - return; - - selectionTimeRange = value; - lineCache.Invalidate(); - } - } - - [Resolved] - private EditorBeatmap beatmap { get; set; } = null!; - - [Resolved] - private OsuColour colours { get; set; } = null!; - - [Resolved] - private BindableBeatDivisor beatDivisor { get; set; } = null!; - - private readonly List grids = new List(); - - private readonly DrawablePool linesPool = new DrawablePool(50); - - private readonly Cached lineCache = new Cached(); - - private (double start, double end)? selectionTimeRange; - - [BackgroundDependencyLoader] - private void load(HitObjectComposer composer) - { - AddInternal(linesPool); - - foreach (var stage in ((ManiaPlayfield)composer.Playfield).Stages) - { - foreach (var column in stage.Columns) - { - var lineContainer = new ScrollingHitObjectContainer(); - - grids.Add(lineContainer); - column.UnderlayElements.Add(lineContainer); - } - } - - beatDivisor.BindValueChanged(_ => createLines(), true); - } - - protected override void Update() - { - base.Update(); - - if (!lineCache.IsValid) - { - lineCache.Validate(); - createLines(); - } - } - - private void createLines() - { - foreach (var grid in grids) - grid.Clear(); - - if (selectionTimeRange == null) - return; - - var range = selectionTimeRange.Value; - - var timingPoint = beatmap.ControlPointInfo.TimingPointAt(range.start - visible_range); - - double time = timingPoint.Time; - int beat = 0; - - // progress time until in the visible range. - while (time < range.start - visible_range) - { - time += timingPoint.BeatLength / beatDivisor.Value; - beat++; - } - - while (time < range.end + visible_range) - { - var nextTimingPoint = beatmap.ControlPointInfo.TimingPointAt(time); - - // switch to the next timing point if we have reached it. - if (nextTimingPoint.Time > timingPoint.Time) - { - beat = 0; - time = nextTimingPoint.Time; - timingPoint = nextTimingPoint; - } - - Color4 colour = BindableBeatDivisor.GetColourFor( - BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value), colours); - - foreach (var grid in grids) - { - var line = linesPool.Get(); - - line.Apply(new HitObject - { - StartTime = time - }); - - line.Colour = colour; - - grid.Add(line); - } - - beat++; - time += timingPoint.BeatLength / beatDivisor.Value; - } - - foreach (var grid in grids) - { - // required to update ScrollingHitObjectContainer's cache. - grid.UpdateSubTree(); - - foreach (var line in grid.Objects.OfType()) - { - time = line.HitObject.StartTime; - - if (time >= range.start && time <= range.end) - line.Alpha = 1; - else - { - double timeSeparation = time < range.start ? range.start - time : time - range.end; - line.Alpha = (float)Math.Max(0, 1 - timeSeparation / visible_range); - } - } - } - } - - private partial class DrawableGridLine : DrawableHitObject - { - [Resolved] - private IScrollingInfo scrollingInfo { get; set; } = null!; - - private readonly IBindable direction = new Bindable(); - - public DrawableGridLine() - : base(new HitObject()) - { - RelativeSizeAxes = Axes.X; - Height = 2; - - AddInternal(new Box { RelativeSizeAxes = Axes.Both }); - } - - [BackgroundDependencyLoader] - private void load() - { - direction.BindTo(scrollingInfo.Direction); - direction.BindValueChanged(onDirectionChanged, true); - } - - private void onDirectionChanged(ValueChangedEvent direction) - { - Origin = Anchor = direction.NewValue == ScrollingDirection.Up - ? Anchor.TopLeft - : Anchor.BottomLeft; - } - - protected override void UpdateInitialTransforms() - { - // don't perform any fading – we are handling that ourselves. - LifetimeEnd = HitObject.StartTime + visible_range; - } + return ((ManiaPlayfield)composer.Playfield) + .Stages + .SelectMany(stage => stage.Columns) + .Select(column => column.UnderlayElements); } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs new file mode 100644 index 0000000000..c7adaa5649 --- /dev/null +++ b/osu.Game/Screens/Edit/Compose/Components/BeatSnapGrid.cs @@ -0,0 +1,203 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Caching; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Pooling; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.UI.Scrolling; +using osuTK.Graphics; + +namespace osu.Game.Screens.Edit.Compose.Components +{ + /// + /// A grid which displays coloured beat divisor lines in proximity to the selection or placement cursor. + /// + public abstract partial class BeatSnapGrid : CompositeComponent + { + private const double visible_range = 750; + + /// + /// The range of time values of the current selection. + /// + public (double start, double end)? SelectionTimeRange + { + set + { + if (value == selectionTimeRange) + return; + + selectionTimeRange = value; + lineCache.Invalidate(); + } + } + + [Resolved] + private EditorBeatmap beatmap { get; set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private BindableBeatDivisor beatDivisor { get; set; } = null!; + + private readonly List grids = new List(); + + private readonly DrawablePool linesPool = new DrawablePool(50); + + private readonly Cached lineCache = new Cached(); + + private (double start, double end)? selectionTimeRange; + + [BackgroundDependencyLoader] + private void load(HitObjectComposer composer) + { + AddInternal(linesPool); + + foreach (var target in GetTargetContainers(composer)) + { + var lineContainer = new ScrollingHitObjectContainer(); + + grids.Add(lineContainer); + target.Add(lineContainer); + } + + beatDivisor.BindValueChanged(_ => createLines(), true); + } + + protected abstract IEnumerable GetTargetContainers(HitObjectComposer composer); + + protected override void Update() + { + base.Update(); + + if (!lineCache.IsValid) + { + lineCache.Validate(); + createLines(); + } + } + + private void createLines() + { + foreach (var grid in grids) + grid.Clear(); + + if (selectionTimeRange == null) + return; + + var range = selectionTimeRange.Value; + + var timingPoint = beatmap.ControlPointInfo.TimingPointAt(range.start - visible_range); + + double time = timingPoint.Time; + int beat = 0; + + // progress time until in the visible range. + while (time < range.start - visible_range) + { + time += timingPoint.BeatLength / beatDivisor.Value; + beat++; + } + + while (time < range.end + visible_range) + { + var nextTimingPoint = beatmap.ControlPointInfo.TimingPointAt(time); + + // switch to the next timing point if we have reached it. + if (nextTimingPoint.Time > timingPoint.Time) + { + beat = 0; + time = nextTimingPoint.Time; + timingPoint = nextTimingPoint; + } + + Color4 colour = BindableBeatDivisor.GetColourFor( + BindableBeatDivisor.GetDivisorForBeatIndex(beat, beatDivisor.Value), colours); + + foreach (var grid in grids) + { + var line = linesPool.Get(); + + line.Apply(new HitObject + { + StartTime = time + }); + + line.Colour = colour; + + grid.Add(line); + } + + beat++; + time += timingPoint.BeatLength / beatDivisor.Value; + } + + foreach (var grid in grids) + { + // required to update ScrollingHitObjectContainer's cache. + grid.UpdateSubTree(); + + foreach (var line in grid.Objects.OfType()) + { + time = line.HitObject.StartTime; + + if (time >= range.start && time <= range.end) + line.Alpha = 1; + else + { + double timeSeparation = time < range.start ? range.start - time : time - range.end; + line.Alpha = (float)Math.Max(0, 1 - timeSeparation / visible_range); + } + } + } + } + + private partial class DrawableGridLine : DrawableHitObject + { + [Resolved] + private IScrollingInfo scrollingInfo { get; set; } = null!; + + private readonly IBindable direction = new Bindable(); + + public DrawableGridLine() + : base(new HitObject()) + { + RelativeSizeAxes = Axes.X; + Height = 2; + + AddInternal(new Box { RelativeSizeAxes = Axes.Both }); + } + + [BackgroundDependencyLoader] + private void load() + { + direction.BindTo(scrollingInfo.Direction); + direction.BindValueChanged(onDirectionChanged, true); + } + + private void onDirectionChanged(ValueChangedEvent direction) + { + Origin = Anchor = direction.NewValue == ScrollingDirection.Up + ? Anchor.TopLeft + : Anchor.BottomLeft; + } + + protected override void UpdateInitialTransforms() + { + // don't perform any fading – we are handling that ourselves. + LifetimeEnd = HitObject.StartTime + visible_range; + } + } + } +}