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

Merge pull request #2170 from peppy/cursor-trail

Make gameplay cursor vs skip button feel nice again
This commit is contained in:
Dan Balasescu 2018-03-16 14:57:00 +09:00 committed by GitHub
commit e406cf2207
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 162 additions and 79 deletions

@ -1 +1 @@
Subproject commit d29c8365ba3cf7924b57cf22341f4af55658764c
Subproject commit cd6f6e93c958e3e4e98db08dd7a443cabcf4742f

@ -1 +1 @@
Subproject commit 92ec3d10b12c5e9bfc1d3b05d3db174a506efd6d
Subproject commit 7bb0782200abadf73b79ed1a3bc1d5b926c6a81e

View File

@ -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 System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics.Cursor;
using osu.Game.Rulesets.Osu.UI.Cursor;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
[TestFixture]
public class TestCaseGameplayCursor : OsuTestCase, IProvideCursor
{
private GameplayCursor cursor;
public override IReadOnlyList<Type> RequiredTypes => new [] { typeof(CursorTrail) };
public CursorContainer Cursor => cursor;
public bool ProvidingUserCursor => true;
[BackgroundDependencyLoader]
private void load()
{
Add(cursor = new GameplayCursor { RelativeSizeAxes = Axes.Both });
}
}
}

View File

@ -3,9 +3,9 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.OpenGL.Buffers;
using osu.Framework.Graphics.OpenGL.Vertices;
using osu.Framework.Graphics.Primitives;
@ -14,11 +14,12 @@ using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Timing;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Graphics.ES30;
namespace osu.Game.Rulesets.Osu.UI.Cursor
{
internal class CursorTrail : Drawable
internal class CursorTrail : Drawable, IRequireHighFrequencyMousePosition
{
private int currentIndex;
@ -31,6 +32,8 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private float time;
public override bool IsPresent => true;
private readonly TrailDrawNodeSharedData trailDrawNodeSharedData = new TrailDrawNodeSharedData();
private const int max_sprites = 2048;
@ -96,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
const int fade_clock_reset_threshold = 1000000;
time = (float)(Time.Current - timeOffset) / 500f;
time = (float)(Time.Current - timeOffset) / 300f;
if (time > fade_clock_reset_threshold)
resetTime();
}
@ -115,14 +118,16 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
protected override bool OnMouseMove(InputState state)
{
Vector2 pos = state.Mouse.NativeState.Position;
if (lastPosition == null)
{
lastPosition = state.Mouse.NativeState.Position;
lastPosition = pos;
resampler.AddPosition(lastPosition.Value);
return base.OnMouseMove(state);
}
foreach (Vector2 pos2 in resampler.AddPosition(state.Mouse.NativeState.Position))
foreach (Vector2 pos2 in resampler.AddPosition(pos))
{
Trace.Assert(lastPosition.HasValue);
@ -162,7 +167,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
private class TrailDrawNodeSharedData
{
public VertexBuffer<TexturedVertex2D> VertexBuffer;
public VertexBuffer<TexturedTrailVertex> VertexBuffer;
}
private class TrailDrawNode : DrawNode
@ -188,7 +193,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public override void Draw(Action<TexturedVertex2D> vertexAction)
{
if (Shared.VertexBuffer == null)
Shared.VertexBuffer = new QuadVertexBuffer<TexturedVertex2D>(max_sprites, BufferUsageHint.DynamicDraw);
Shared.VertexBuffer = new QuadVertexBuffer<TexturedTrailVertex>(max_sprites, BufferUsageHint.DynamicDraw);
Shader.GetUniform<float>("g_FadeClock").Value = Time;
@ -205,17 +210,19 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
int end = start;
Vector2 pos = Parts[i].Position;
ColourInfo colour = DrawInfo.Colour;
colour.TopLeft.Linear.A = Parts[i].Time + colour.TopLeft.Linear.A;
colour.TopRight.Linear.A = Parts[i].Time + colour.TopRight.Linear.A;
colour.BottomLeft.Linear.A = Parts[i].Time + colour.BottomLeft.Linear.A;
colour.BottomRight.Linear.A = Parts[i].Time + colour.BottomRight.Linear.A;
float time = Parts[i].Time;
Texture.DrawQuad(
new Quad(pos.X - Size.X / 2, pos.Y - Size.Y / 2, Size.X, Size.Y),
colour,
DrawInfo.Colour,
null,
v => Shared.VertexBuffer.Vertices[end++] = v);
v => Shared.VertexBuffer.Vertices[end++] = new TexturedTrailVertex
{
Position = v.Position,
TexturePosition = v.TexturePosition,
Time = time + 1,
Colour = v.Colour,
});
Parts[i].WasUpdated = false;
}
@ -240,5 +247,26 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
Shader.Unbind();
}
}
[StructLayout(LayoutKind.Sequential)]
public struct TexturedTrailVertex : IEquatable<TexturedTrailVertex>, IVertex
{
[VertexMember(2, VertexAttribPointerType.Float)]
public Vector2 Position;
[VertexMember(4, VertexAttribPointerType.Float)]
public Color4 Colour;
[VertexMember(2, VertexAttribPointerType.Float)]
public Vector2 TexturePosition;
[VertexMember(1, VertexAttribPointerType.Float)]
public float Time;
public bool Equals(TexturedTrailVertex other)
{
return Position.Equals(other.Position)
&& TexturePosition.Equals(other.TexturePosition)
&& Colour.Equals(other.Colour)
&& Time.Equals(other.Time);
}
}
}
}

View File

@ -20,13 +20,66 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
{
protected override Drawable CreateCursor() => new OsuCursor();
protected override Container<Drawable> Content => fadeContainer;
private readonly Container<Drawable> fadeContainer;
public GameplayCursor()
{
Add(new CursorTrail { Depth = 1 });
InternalChild = fadeContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new CursorTrail { Depth = 1 }
}
};
}
private int downCount;
public bool OnPressed(OsuAction action)
{
switch (action)
{
case OsuAction.LeftButton:
case OsuAction.RightButton:
downCount++;
ActiveCursor.ScaleTo(1).ScaleTo(1.2f, 100, Easing.OutQuad);
break;
}
return false;
}
public bool OnReleased(OsuAction action)
{
switch (action)
{
case OsuAction.LeftButton:
case OsuAction.RightButton:
if (--downCount == 0)
ActiveCursor.ScaleTo(1, 200, Easing.OutQuad);
break;
}
return false;
}
public override bool HandleMouseInput => true; // OverlayContainer will set this false when we go hidden, but we always want to receive input.
protected override void PopIn()
{
fadeContainer.FadeTo(1, 300, Easing.OutQuint);
ActiveCursor.ScaleTo(1, 400, Easing.OutQuint);
}
protected override void PopOut()
{
fadeContainer.FadeTo(0.05f, 450, Easing.OutQuint);
ActiveCursor.ScaleTo(0.8f, 450, Easing.OutQuint);
}
public class OsuCursor : Container
{
private Container cursorContainer;
@ -131,45 +184,5 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
cursorContainer.Scale = new Vector2(scale);
}
}
public bool OnPressed(OsuAction action)
{
switch (action)
{
case OsuAction.LeftButton:
case OsuAction.RightButton:
downCount++;
ActiveCursor.ScaleTo(1).ScaleTo(1.2f, 100, Easing.OutQuad);
break;
}
return false;
}
public bool OnReleased(OsuAction action)
{
switch (action)
{
case OsuAction.LeftButton:
case OsuAction.RightButton:
if (--downCount == 0)
ActiveCursor.ScaleTo(1, 200, Easing.OutQuad);
break;
}
return false;
}
protected override void PopIn()
{
ActiveCursor.FadeTo(1, 250, Easing.OutQuint);
ActiveCursor.ScaleTo(1, 400, Easing.OutQuint);
}
protected override void PopOut()
{
ActiveCursor.FadeTo(0, 250, Easing.OutQuint);
ActiveCursor.ScaleTo(0.6f, 250, Easing.In);
}
}
}

View File

@ -131,6 +131,7 @@
<Compile Include="Replays\OsuReplayInputHandler.cs" />
<Compile Include="Tests\OsuBeatmapConversionTest.cs" />
<Compile Include="Tests\TestCaseEditor.cs" />
<Compile Include="Tests\TestCaseGameplayCursor.cs" />
<Compile Include="Tests\TestCaseHitCircle.cs" />
<Compile Include="Tests\TestCaseHitCircleHidden.cs" />
<Compile Include="Tests\TestCasePerformancePoints.cs" />

View File

@ -13,7 +13,7 @@ namespace osu.Game.Tests.Visual
{
base.LoadComplete();
Add(new SkipButton(Clock.CurrentTime + 5000));
Add(new SkipOverlay(Clock.CurrentTime + 5000));
}
}
}

View File

@ -18,7 +18,7 @@ using System.Collections.Generic;
namespace osu.Game.Screens.Play
{
public abstract class GameplayMenuOverlay : OverlayContainer, IRequireHighFrequencyMousePosition
public abstract class GameplayMenuOverlay : OverlayContainer
{
private const int transition_duration = 200;
private const int button_height = 70;

View File

@ -164,7 +164,7 @@ namespace osu.Game.Screens.Play
Alpha = 0,
},
RulesetContainer,
new SkipButton(firstObjectTime)
new SkipOverlay(firstObjectTime)
{
Clock = Clock, // skip button doesn't want to use the audio clock directly
ProcessCustomClock = false,

View File

@ -21,7 +21,7 @@ using osu.Game.Input.Bindings;
namespace osu.Game.Screens.Play
{
public class SkipButton : OverlayContainer, IKeyBindingHandler<GlobalAction>
public class SkipOverlay : OverlayContainer, IKeyBindingHandler<GlobalAction>
{
private readonly double startTime;
@ -35,8 +35,9 @@ namespace osu.Game.Screens.Play
private double displayTime;
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true;
protected override bool BlockPassThroughMouse => false;
public SkipButton(double startTime)
public SkipOverlay(double startTime)
{
this.startTime = startTime;
@ -51,12 +52,6 @@ namespace osu.Game.Screens.Play
Origin = Anchor.Centre;
}
protected override bool OnMouseMove(InputState state)
{
fadeContainer.State = Visibility.Visible;
return base.OnMouseMove(state);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
@ -121,15 +116,9 @@ namespace osu.Game.Screens.Play
Expire();
}
protected override void PopIn()
{
this.FadeIn();
}
protected override void PopIn() => this.FadeIn();
protected override void PopOut()
{
this.FadeOut();
}
protected override void PopOut() => this.FadeOut();
protected override void Update()
{
@ -137,6 +126,13 @@ namespace osu.Game.Screens.Play
remainingTimeBox.ResizeWidthTo((float)Math.Max(0, 1 - (Time.Current - displayTime) / (beginFadeTime - displayTime)), 120, Easing.OutQuint);
}
protected override bool OnMouseMove(InputState state)
{
if (!state.Mouse.HasAnyButtonPressed)
fadeContainer.State = Visibility.Visible;
return base.OnMouseMove(state);
}
public bool OnPressed(GlobalAction action)
{
switch (action)
@ -176,7 +172,7 @@ namespace osu.Game.Screens.Play
if (stateChanged)
this.FadeIn(500, Easing.OutExpo);
if (!IsHovered)
if (!IsHovered && !IsDragged)
using (BeginDelayedSequence(1000))
scheduledHide = Schedule(() => State = Visibility.Hidden);
break;
@ -194,6 +190,18 @@ namespace osu.Game.Screens.Play
base.LoadComplete();
State = Visibility.Visible;
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
scheduledHide?.Cancel();
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
State = Visibility.Visible;
return base.OnMouseUp(state, args);
}
}
private class Button : OsuClickableContainer
@ -274,7 +282,7 @@ namespace osu.Game.Screens.Play
flow.TransformSpacingTo(new Vector2(5), 500, Easing.OutQuint);
box.FadeColour(colourHover, 500, Easing.OutQuint);
background.FadeTo(0.4f, 500, Easing.OutQuint);
return base.OnHover(state);
return true;
}
protected override void OnHoverLost(InputState state)

View File

@ -809,7 +809,7 @@
<Compile Include="Screens\Play\PlayerSettings\PlayerCheckbox.cs" />
<Compile Include="Screens\Play\PlayerSettings\PlayerSettingsGroup.cs" />
<Compile Include="Screens\Play\PlayerSettings\PlayerSliderBar.cs" />
<Compile Include="Screens\Play\SkipButton.cs" />
<Compile Include="Screens\Play\SkipOverlay.cs" />
<Compile Include="Screens\Play\SongProgress.cs" />
<Compile Include="Screens\Play\SongProgressBar.cs" />
<Compile Include="Screens\Play\SongProgressGraph.cs" />