Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 79 additions & 0 deletions fluXis/Graphics/Containers/SeekContainer.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osuTK;
using osuTK.Input;

namespace fluXis.Graphics.Containers;

public partial class SeekContainer : Container
{
public Action<float> OnSeek { get; set; }
public Func<bool> IsPlaying { get; set; }

public bool AlwaysDebounce { get; set; } = false; // debounce regardless of playing state

public float HorizontalOffset { get; set; } = 0;
public double DebounceTime { get; set; } = Styling.SEEK_DEBOUNCE;

public Bindable<float> Progress { get; private set; } = new(0);

private double? lastSeekTime;

protected override bool OnClick(ClickEvent e)
{
if (e.Button != MouseButton.Left)
return false;

seekToMousePosition(e.MousePosition);
return true;
}

protected override bool OnDragStart(DragStartEvent e)
{
if (e.Button != MouseButton.Left)
return false;

seekToMousePosition(e.MousePosition);
return true;
}

protected override void OnDrag(DragEvent e)
{
float x = Math.Clamp(e.MousePosition.X - HorizontalOffset, 0, DrawWidth);
float progress = DrawWidth > 0 ? x / DrawWidth : 0;

if (!float.IsFinite(progress) || float.IsNaN(progress))
progress = 0;

Progress.Value = progress;

bool shouldDebounce = IsPlaying != null && IsPlaying() && DebounceTime > 0 || AlwaysDebounce;

if (shouldDebounce && lastSeekTime != null && Time.Current - lastSeekTime < DebounceTime)
return;

OnSeek?.Invoke(progress);
lastSeekTime = Time.Current;
}

protected override void OnDragEnd(DragEndEvent e)
{
base.OnDragEnd(e);
seekToMousePosition(e.MousePosition);
lastSeekTime = null;
}

private void seekToMousePosition(Vector2 position)
{
float x = Math.Clamp(position.X - HorizontalOffset, 0, DrawWidth);
float progress = DrawWidth > 0 ? x / DrawWidth : 0;

if (!float.IsFinite(progress) || float.IsNaN(progress))
progress = 0;

Progress.Value = progress;
OnSeek?.Invoke(progress);
}
}
2 changes: 2 additions & 0 deletions fluXis/Graphics/Styling.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ public static class Styling
public const float TRANSITION_FADE = 300;
public const float TRANSITION_ENTER_DELAY = 100;

public const float SEEK_DEBOUNCE = 50;

public static EdgeEffectParameters ShadowSmall => createShadow(5, 2);
public static EdgeEffectParameters ShadowSmallNoOffset => createShadow(5, 0);

Expand Down
107 changes: 97 additions & 10 deletions fluXis/Overlay/Music/MusicPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
Expand Down Expand Up @@ -79,7 +80,7 @@ public partial class MusicPlayer : OverlayContainer, IKeyBindingHandler<FluXisGl

private Container gradient;
private VerticalSectionedGradient colorGradient;
private Box progress;
private Progress progress;

protected override bool StartHidden => true;

Expand Down Expand Up @@ -269,13 +270,12 @@ private void load()
}
}
},
progress = new Box
progress = new Progress(globalClock)
{
Size = new Vector2(0, 4),
RelativeSizeAxes = Axes.X,
Colour = Theme.Text,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Width = 1f
}
}
}
Expand All @@ -299,8 +299,6 @@ protected override void Update()
globalBackground.Alpha = screens.Alpha = dim.Alpha = animationProgress >= 1f ? 0 : 1;
backgrounds.Alpha = video.Alpha >= 1f ? 0 : 1;

progress.Width = (float)((globalClock.CurrentTrack?.CurrentTime ?? 0) / (globalClock.CurrentTrack?.Length ?? 1000));

pausePlay.IconSprite.Icon = globalClock.IsRunning ? Phosphor.Bold.Pause : Phosphor.Bold.Play;
fullscreenToggle.IconSprite.Icon = fullscreen ? Phosphor.Bold.ArrowsInSimple : Phosphor.Bold.ArrowsOutSimple;

Expand Down Expand Up @@ -351,8 +349,6 @@ protected override void Update()
Top = Interpolation.ValueAt(animationProgress, 12, 20, 0, 1),
Left = Interpolation.ValueAt(animationProgress, cover_small / 2f - 196 / 2f, 0, 0, 1)
};

progress.Height = Interpolation.ValueAt(animationProgress, 4, 8, 0, 1);
}

protected override void Dispose(bool isDisposing)
Expand Down Expand Up @@ -486,4 +482,95 @@ protected override bool OnMouseMove(MouseMoveEvent e)
showMetadata();
return true;
}

private partial class Progress : Container
{
private const float collapsed_height = 6;
private const float expanded_height = 10;

private readonly GlobalClock clock;

private Box fill;
private Box background;
private SeekContainer seekContainer;

public Progress(GlobalClock clock)
{
this.clock = clock;
}

[BackgroundDependencyLoader]
private void load()
{
Anchor = Anchor.BottomLeft;
Origin = Anchor.BottomLeft;
RelativeSizeAxes = Axes.X;
Width = 1f;
Height = collapsed_height;

Child = seekContainer = new SeekContainer
{
RelativeSizeAxes = Axes.Both,
IsPlaying = () => clock.IsRunning,
AlwaysDebounce = true,
OnSeek = onSeek,
Children = new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Colour4.White,
Alpha = 0.1f
},
fill = new Box
{
RelativeSizeAxes = Axes.Both,
Width = 0,
Colour = Theme.Text,
}
}
};
}

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

if (seekContainer.IsDragged)
{
fill.Width = seekContainer.Progress.Value;
}
else
{
var width = clock.CurrentTime / clock.CurrentTrack.Length;
if (!double.IsFinite(width) || double.IsNaN(width)) width = 0;

fill.Width = (float)width;
}
}

protected override bool OnHover(HoverEvent e)
{
this.ResizeHeightTo(expanded_height, 200, Easing.OutQuad);
return base.OnHover(e);
}

protected override void OnHoverLost(HoverLostEvent e)
{
base.OnHoverLost(e);
this.ResizeHeightTo(collapsed_height, 300, Easing.OutQuad);
}

public void FadeColour(ColourInfo colour, double duration = 0, Easing easing = Easing.None)
{
fill.FadeColour(colour, duration, easing);
}

private void onSeek(float progress)
{
if (clock.CurrentTrack == null) return;
double targetTime = progress * clock.CurrentTrack.Length;
clock.Seek(targetTime);
}
}
}
86 changes: 29 additions & 57 deletions fluXis/Screens/Edit/UI/BottomBar/Timeline/EditorTimeline.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
using System;
using fluXis.Graphics;
using fluXis.Graphics.Containers;
using fluXis.Graphics.UserInterface.Color;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osuTK;
using osuTK.Input;

namespace fluXis.Screens.Edit.UI.BottomBar.Timeline;

Expand All @@ -19,10 +18,7 @@ public partial class EditorTimeline : Container
private EditorMap map { get; set; }

private TimelineIndicator indicator;

private const double debounce = 50;

private double? lastSeekTime;
private SeekContainer seekContainer;

[BackgroundDependencyLoader]
private void load()
Expand All @@ -31,25 +27,39 @@ private void load()

Children = new Drawable[]
{
new Circle
seekContainer = new SeekContainer
{
Colour = Theme.Text,
RelativeSizeAxes = Axes.X,
Height = 5,
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
new TimelineTagContainer() { Offset = 10 },
new TimelineDensity(),
indicator = new TimelineIndicator()
RelativeSizeAxes = Axes.Both,
HorizontalOffset = 0,
IsPlaying = () => clock.IsRunning,
OnSeek = onSeek,
Children = new Drawable[]
{
new Circle
{
Colour = Theme.Text,
RelativeSizeAxes = Axes.X,
Height = 5,
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
new TimelineTagContainer() { Offset = 10 },
new TimelineDensity(),
indicator = new TimelineIndicator()
}
}
};
}

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

if (!IsDragged)
if (seekContainer.IsDragged)
{
indicator.X = seekContainer.Progress.Value;
}
else
{
var x = clock.CurrentTime / clock.TrackLength;
if (!double.IsFinite(x) || double.IsNaN(x)) x = 0;
Expand All @@ -58,47 +68,9 @@ protected override void Update()
}
}

protected override bool OnClick(ClickEvent e)
{
if (e.Button != MouseButton.Left)
return false;

seekToMousePosition(e.MousePosition, instant: true);
return true;
}

protected override bool OnDragStart(DragStartEvent e)
private void onSeek(float progress)
{
if (e.Button != MouseButton.Left)
return false;

seekToMousePosition(e.MousePosition, instant: false);
return true;
}

protected override void OnDrag(DragEvent e) => seekToMousePosition(e.MousePosition, instant: false);

protected override void OnDragEnd(DragEndEvent e)
{
base.OnDragEnd(e);
seekToMousePosition(e.MousePosition, instant: true);
}

private void seekToMousePosition(Vector2 position, bool instant)
{
// why is there a 20px offset??
float x = Math.Clamp(position.X - 20, 0, DrawWidth);
float progress = x / DrawWidth;
double targetTime = progress * clock.TrackLength;

var indicatorPos = targetTime / clock.TrackLength;
if (!double.IsFinite(indicatorPos) || double.IsNaN(indicatorPos)) indicatorPos = 0;
indicator.X = (float)indicatorPos;

if (!instant && lastSeekTime != null && Time.Current - lastSeekTime < debounce)
return;

clock.SeekSmoothly(targetTime);
lastSeekTime = instant ? null : Time.Current;
}
}
Loading