Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Collapse sample point indicators into dots if they cannot be displayed in full #29896

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
2 changes: 1 addition & 1 deletion osu.Game.Rulesets.Mania/Edit/ManiaHitObjectComposer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ protected override void Update()
base.Update();

if (screenWithTimeline?.TimelineArea.Timeline != null)
drawableRuleset.TimelineTimeRange = EditorClock.TrackLength / screenWithTimeline.TimelineArea.Timeline.CurrentZoom / 2;
drawableRuleset.TimelineTimeRange = EditorClock.TrackLength / screenWithTimeline.TimelineArea.Timeline.CurrentZoom.Value / 2;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ public partial class HitObjectPointPiece : CircularContainer
{
protected OsuSpriteText Label { get; private set; }

protected Container LabelContainer { get; private set; }

[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Expand All @@ -26,7 +28,7 @@ private void load(OsuColour colours)

InternalChildren = new Drawable[]
{
new Container
LabelContainer = new Container
{
AutoSizeAxes = Axes.X,
Height = 16,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Audio;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
Expand All @@ -40,6 +41,9 @@ public partial class SamplePointPiece : HitObjectPointPiece, IHasPopover
[Resolved]
private Editor? editor { get; set; }

[Resolved]
private TimelineBlueprintContainer? timelineBlueprintContainer { get; set; }

public SamplePointPiece(HitObject hitObject)
{
HitObject = hitObject;
Expand All @@ -53,15 +57,45 @@ public SamplePointPiece(HitObject hitObject)
protected virtual double GetTime() => HitObject is IHasRepeats r ? HitObject.StartTime + r.Duration / r.SpanCount() / 2 : HitObject.StartTime;

[BackgroundDependencyLoader]
private void load()
private void load(OsuConfigManager config)
{
HitObject.DefaultsApplied += _ => updateText();
Label.AllowMultiline = false;
LabelContainer.AutoSizeAxes = Axes.None;
updateText();

if (editor != null)
editor.ShowSampleEditPopoverRequested += onShowSampleEditPopoverRequested;
}

private readonly Bindable<bool> contracted = new Bindable<bool>();

protected override void LoadComplete()
{
base.LoadComplete();

if (timelineBlueprintContainer != null)
contracted.BindTo(timelineBlueprintContainer.SamplePointContracted);

contracted.BindValueChanged(v =>
{
if (v.NewValue)
{
Label.FadeOut(200, Easing.OutQuint);
LabelContainer.ResizeTo(new Vector2(12), 200, Easing.OutQuint);
LabelContainer.CornerRadius = 6;
}
else
{
Label.FadeIn(200, Easing.OutQuint);
LabelContainer.ResizeTo(new Vector2(Label.Width, 16), 200, Easing.OutQuint);
LabelContainer.CornerRadius = 8;
}
}, true);

FinishTransforms();
}

protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
Expand All @@ -87,6 +121,9 @@ protected override bool OnClick(ClickEvent e)
private void updateText()
{
Label.Text = $"{abbreviateBank(GetBankValue(GetSamples()))} {GetVolumeValue(GetSamples())}";

if (!contracted.Value)
LabelContainer.ResizeWidthTo(Label.Width, 200, Easing.OutQuint);
}

private static string? abbreviateBank(string? bank)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,22 @@
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
using osuTK;
using osuTK.Graphics;

namespace osu.Game.Screens.Edit.Compose.Components.Timeline
{
[Cached]
internal partial class TimelineBlueprintContainer : EditorBlueprintContainer
{
[Resolved(CanBeNull = true)]
private Timeline timeline { get; set; }

[Resolved(CanBeNull = true)]
private EditorClock editorClock { get; set; }

private Bindable<HitObject> placement;
private SelectionBlueprint<HitObject> placementBlueprint;

Expand Down Expand Up @@ -118,9 +123,53 @@ protected override void Update()

base.Update();

updateSamplePointContractedState();
updateStacking();
}

public Bindable<bool> SamplePointContracted = new Bindable<bool>();

private void updateSamplePointContractedState()
{
const double minimum_gap = 28;

if (timeline == null || editorClock == null)
return;

// Find the smallest time gap between any two sample point pieces
double smallestTimeGap = double.PositiveInfinity;
double lastTime = double.PositiveInfinity;

// The blueprints are ordered in reverse chronological order
foreach (var selectionBlueprint in SelectionBlueprints)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can't be done. While SelectionBlueprints will only contain the visible blueprints in the general case... it will also contain more blueprints if they are selected. In fact if you select every object you may see these contract for no reason:

2024-10-21.12-00-51.mp4

I imagine this will also make selecting all objects even slower than it usually is, although crude profiling shows maybe not very much:

1729505224

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're very right. I forgot the Ctrl+A loads every single selection blueprint. I'll find some way to get only the visible blueprints.

The performance impact is probably small because it only does a simple operation per each blueprint.

{
var hitObject = selectionBlueprint.Item;

// Only check the hit objects which are visible in the timeline
// SelectionBlueprints can contain hit objects which are not visible in the timeline due to selection keeping them alive
if (hitObject.StartTime > editorClock.CurrentTime + timeline.VisibleRange / 2)
continue;

if (hitObject.GetEndTime() < editorClock.CurrentTime - timeline.VisibleRange / 2)
break;

if (hitObject is IHasRepeats hasRepeats)
smallestTimeGap = Math.Min(smallestTimeGap, hasRepeats.Duration / hasRepeats.SpanCount() / 2);

double gap = lastTime - hitObject.GetEndTime();

// If the gap is less than 1ms, we can assume that the objects are stacked on top of each other
// Contracting doesn't make sense in this case
if (gap > 1 && gap < smallestTimeGap)
smallestTimeGap = gap;

lastTime = hitObject.StartTime;
}

double smallestAbsoluteGap = ((TimelineSelectionBlueprintContainer)SelectionBlueprints).ContentRelativeToAbsoluteFactor.X * smallestTimeGap;
SamplePointContracted.Value = smallestAbsoluteGap < minimum_gap;
}

private readonly Stack<HitObject> currentConcurrentObjects = new Stack<HitObject>();

private void updateStacking()
Expand Down Expand Up @@ -291,6 +340,8 @@ protected partial class TimelineSelectionBlueprintContainer : SelectionBlueprint
{
protected override HitObjectOrderedSelectionContainer Content { get; }

public Vector2 ContentRelativeToAbsoluteFactor => Content.RelativeToAbsoluteFactor;

public TimelineSelectionBlueprintContainer()
{
AddInternal(new TimelinePart<SelectionBlueprint<HitObject>>(Content = new HitObjectOrderedSelectionContainer { RelativeSizeAxes = Axes.Both }) { RelativeSizeAxes = Axes.Both });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Transforms;
Expand Down Expand Up @@ -32,10 +33,10 @@ public partial class ZoomableScrollContainer : OsuScrollContainer
protected override Container<Drawable> Content => zoomedContent;

/// <summary>
/// The current zoom level of <see cref="ZoomableScrollContainer"/>.
/// The current (final) zoom level of <see cref="ZoomableScrollContainer"/>.
/// It may differ from <see cref="Zoom"/> during transitions.
/// </summary>
public float CurrentZoom { get; private set; } = 1;
public BindableFloat CurrentZoom { get; private set; } = new BindableFloat(1);

private bool isZoomSetUp;

Expand Down Expand Up @@ -98,7 +99,7 @@ protected void SetupZoom(float initial, float minimum, float maximum)
minZoom = minimum;
maxZoom = maximum;

CurrentZoom = zoomTarget = initial;
CurrentZoom.Value = zoomTarget = initial;
zoomedContentWidthCache.Invalidate();

isZoomSetUp = true;
Expand All @@ -124,7 +125,7 @@ private void updateZoom(float value)
if (IsLoaded)
setZoomTarget(newZoom, ToSpaceOfOtherDrawable(new Vector2(DrawWidth / 2, 0), zoomedContent).X);
else
CurrentZoom = zoomTarget = newZoom;
CurrentZoom.Value = zoomTarget = newZoom;
}

protected override void UpdateAfterChildren()
Expand Down Expand Up @@ -154,7 +155,7 @@ protected override bool OnScroll(ScrollEvent e)

private void updateZoomedContentWidth()
{
zoomedContent.Width = DrawWidth * CurrentZoom;
zoomedContent.Width = DrawWidth * CurrentZoom.Value;
zoomedContentWidthCache.Validate();
}

Expand Down Expand Up @@ -238,7 +239,7 @@ protected override void Apply(ZoomableScrollContainer d, double time)
float expectedWidth = d.DrawWidth * newZoom;
float targetOffset = expectedWidth * (focusPoint / contentSize) - focusOffset;

d.CurrentZoom = newZoom;
d.CurrentZoom.Value = newZoom;
d.updateZoomedContentWidth();

// Temporarily here to make sure ScrollTo gets the correct DrawSize for scrollable area.
Expand All @@ -247,7 +248,7 @@ protected override void Apply(ZoomableScrollContainer d, double time)
d.ScrollTo(targetOffset, false);
}

protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.CurrentZoom;
protected override void ReadIntoStartValue(ZoomableScrollContainer d) => StartValue = d.CurrentZoom.Value;
}
}
}
Loading