osu/osu.Game/Graphics/Cursor/MenuCursorContainer.cs

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

329 lines
11 KiB
C#
Raw Normal View History

// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2018-04-13 09:19:50 +00:00
2017-03-16 13:41:07 +00:00
using System;
using osu.Framework.Allocation;
2022-01-28 09:13:51 +00:00
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
2019-02-21 10:04:31 +00:00
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
2017-03-16 13:41:07 +00:00
using osu.Framework.Graphics.Textures;
2018-10-02 03:02:47 +00:00
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Configuration;
using osuTK;
2018-04-13 09:19:50 +00:00
2017-03-16 13:41:07 +00:00
namespace osu.Game.Graphics.Cursor
{
public partial class MenuCursorContainer : CursorContainer
2017-03-16 13:41:07 +00:00
{
private readonly IBindable<bool> screenshotCursorVisibility = new Bindable<bool>(true);
public override bool IsPresent => screenshotCursorVisibility.Value && base.IsPresent;
private bool hideCursorOnNonMouseInput;
public bool HideCursorOnNonMouseInput
{
get => hideCursorOnNonMouseInput;
set
{
if (hideCursorOnNonMouseInput == value)
return;
hideCursorOnNonMouseInput = value;
updateState();
}
}
2018-07-06 08:45:39 +00:00
protected override Drawable CreateCursor() => activeCursor = new Cursor();
2022-10-11 13:22:57 +00:00
private Cursor activeCursor = null!;
2018-04-13 09:19:50 +00:00
2018-07-02 07:07:52 +00:00
private DragRotationState dragRotationState;
private Vector2 positionMouseDown;
private Vector2 lastMovePosition;
2022-01-28 09:13:51 +00:00
2022-10-11 13:22:57 +00:00
private Bindable<bool> cursorRotate = null!;
private Sample tapSample = null!;
private MouseInputDetector mouseInputDetector = null!;
private bool visible;
2022-10-11 13:22:57 +00:00
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, ScreenshotManager? screenshotManager, AudioManager audio)
{
cursorRotate = config.GetBindable<bool>(OsuSetting.CursorRotation);
if (screenshotManager != null)
screenshotCursorVisibility.BindTo(screenshotManager.CursorVisibility);
2022-01-28 09:13:51 +00:00
tapSample = audio.Samples.Get(@"UI/cursor-tap");
Add(mouseInputDetector = new MouseInputDetector());
}
2022-10-12 14:50:31 +00:00
[Resolved]
private OsuGame? game { get; set; }
private readonly IBindable<bool> lastInputWasMouse = new BindableBool();
private readonly IBindable<bool> gameActive = new BindableBool(true);
private readonly IBindable<bool> gameIdle = new BindableBool();
protected override void LoadComplete()
{
base.LoadComplete();
lastInputWasMouse.BindTo(mouseInputDetector.LastInputWasMouseSource);
lastInputWasMouse.BindValueChanged(_ => updateState(), true);
2022-10-12 14:50:31 +00:00
if (game != null)
{
gameIdle.BindTo(game.IsIdle);
gameIdle.BindValueChanged(_ => updateState());
gameActive.BindTo(game.IsActive);
gameActive.BindValueChanged(_ => updateState());
2022-10-12 14:50:31 +00:00
}
}
protected override void UpdateState(ValueChangedEvent<Visibility> state) => updateState();
private void updateState()
{
bool combinedVisibility = getCursorVisibility();
if (visible == combinedVisibility)
return;
visible = combinedVisibility;
if (visible)
PopIn();
else
PopOut();
}
private bool getCursorVisibility()
{
// do not display when explicitly set to hidden state.
if (State.Value == Visibility.Hidden)
return false;
// only hide cursor when game is focused, otherwise it should always be displayed.
if (gameActive.Value)
{
// do not display when last input is not mouse.
if (hideCursorOnNonMouseInput && !lastInputWasMouse.Value)
return false;
// do not display when game is idle.
if (gameIdle.Value)
return false;
}
return true;
}
protected override void Update()
{
base.Update();
if (dragRotationState != DragRotationState.NotDragging
&& Vector2.Distance(positionMouseDown, lastMovePosition) > 60)
{
// make the rotation centre point floating.
positionMouseDown = Interpolation.ValueAt(0.04f, positionMouseDown, lastMovePosition, 0, Clock.ElapsedFrameTime);
}
}
2018-10-02 03:02:47 +00:00
protected override bool OnMouseMove(MouseMoveEvent e)
2017-03-18 11:47:49 +00:00
{
2018-07-02 07:07:52 +00:00
if (dragRotationState != DragRotationState.NotDragging)
2017-09-16 23:44:49 +00:00
{
lastMovePosition = e.MousePosition;
float distance = Vector2Extensions.Distance(lastMovePosition, positionMouseDown);
2019-04-01 03:16:05 +00:00
2017-09-16 23:28:02 +00:00
// don't start rotating until we're moved a minimum distance away from the mouse down location,
// else it can have an annoying effect.
if (dragRotationState == DragRotationState.DragStarted && distance > 80)
2018-07-02 07:07:52 +00:00
dragRotationState = DragRotationState.Rotating;
2019-04-01 03:16:05 +00:00
2018-07-02 07:09:33 +00:00
// don't rotate when distance is zero to avoid NaN
2018-07-02 07:07:52 +00:00
if (dragRotationState == DragRotationState.Rotating && distance > 0)
2017-09-16 23:44:49 +00:00
{
Vector2 offset = e.MousePosition - positionMouseDown;
float degrees = MathUtils.RadiansToDegrees(MathF.Atan2(-offset.X, offset.Y)) + 24.3f;
2018-04-13 09:19:50 +00:00
2017-09-16 23:28:02 +00:00
// Always rotate in the direction of least distance
2018-07-06 08:45:39 +00:00
float diff = (degrees - activeCursor.Rotation) % 360;
2017-09-21 20:11:35 +00:00
if (diff < -180) diff += 360;
if (diff > 180) diff -= 360;
2018-07-06 08:45:39 +00:00
degrees = activeCursor.Rotation + diff;
2018-04-13 09:19:50 +00:00
activeCursor.RotateTo(degrees, 120, Easing.OutQuint);
}
2017-03-18 11:47:49 +00:00
}
2018-04-13 09:19:50 +00:00
2018-10-02 03:02:47 +00:00
return base.OnMouseMove(e);
2017-04-02 16:19:59 +00:00
}
2018-07-02 07:20:44 +00:00
2018-10-02 03:02:47 +00:00
protected override bool OnMouseDown(MouseDownEvent e)
2017-03-16 13:41:07 +00:00
{
if (State.Value == Visibility.Visible)
{
// only trigger animation for main mouse buttons
activeCursor.Scale = new Vector2(1);
activeCursor.ScaleTo(0.90f, 800, Easing.OutQuint);
2018-04-13 09:19:50 +00:00
activeCursor.AdditiveLayer.Alpha = 0;
activeCursor.AdditiveLayer.FadeInFromZero(800, Easing.OutQuint);
2018-07-02 07:07:52 +00:00
if (cursorRotate.Value && dragRotationState != DragRotationState.Rotating)
{
// if cursor is already rotating don't reset its rotate origin
dragRotationState = DragRotationState.DragStarted;
positionMouseDown = e.MousePosition;
}
playTapSample();
2018-07-02 07:07:52 +00:00
}
2019-02-28 04:31:40 +00:00
2018-10-02 03:02:47 +00:00
return base.OnMouseDown(e);
2017-03-16 13:41:07 +00:00
}
2018-04-13 09:19:50 +00:00
protected override void OnMouseUp(MouseUpEvent e)
2017-03-16 13:41:07 +00:00
{
if (!e.HasAnyButtonPressed)
2017-09-16 23:44:49 +00:00
{
2018-07-06 08:45:39 +00:00
activeCursor.AdditiveLayer.FadeOutFromOne(500, Easing.OutQuint);
activeCursor.ScaleTo(1, 500, Easing.OutElastic);
2018-04-13 09:19:50 +00:00
if (dragRotationState != DragRotationState.NotDragging)
{
activeCursor.RotateTo(0, 400 * (0.5f + Math.Abs(activeCursor.Rotation / 960)), Easing.OutElasticQuarter);
dragRotationState = DragRotationState.NotDragging;
}
if (State.Value == Visibility.Visible)
playTapSample(0.8);
2018-07-02 07:07:52 +00:00
}
2019-02-28 04:31:40 +00:00
base.OnMouseUp(e);
2017-03-16 13:41:07 +00:00
}
2018-04-13 09:19:50 +00:00
2017-09-16 23:44:49 +00:00
protected override void PopIn()
2017-03-16 14:58:36 +00:00
{
2018-07-06 08:45:39 +00:00
activeCursor.FadeTo(1, 250, Easing.OutQuint);
activeCursor.ScaleTo(1, 400, Easing.OutQuint);
2017-03-16 14:58:36 +00:00
}
2018-04-13 09:19:50 +00:00
2017-09-16 23:44:49 +00:00
protected override void PopOut()
2017-03-16 14:58:36 +00:00
{
2018-07-06 08:45:39 +00:00
activeCursor.FadeTo(0, 250, Easing.OutQuint);
activeCursor.ScaleTo(0.6f, 250, Easing.In);
2017-03-16 14:58:36 +00:00
}
2018-04-13 09:19:50 +00:00
private void playTapSample(double baseFrequency = 1f)
{
const float random_range = 0.02f;
SampleChannel channel = tapSample.GetChannel();
// Scale to [-0.75, 0.75] so that the sample isn't fully panned left or right (sounds weird)
2022-12-16 08:19:07 +00:00
channel.Balance.Value = ((activeCursor.X / DrawWidth) * 2 - 1) * OsuGameBase.SFX_STEREO_STRENGTH;
channel.Frequency.Value = baseFrequency - (random_range / 2f) + RNG.NextDouble(random_range);
channel.Volume.Value = baseFrequency;
channel.Play();
}
2017-03-16 13:41:07 +00:00
public partial class Cursor : Container
{
2022-10-11 13:22:57 +00:00
private Container cursorContainer = null!;
private Bindable<float> cursorScale = null!;
private const float base_scale = 0.15f;
2018-04-13 09:19:50 +00:00
2022-10-11 13:22:57 +00:00
public Sprite AdditiveLayer = null!;
2018-04-13 09:19:50 +00:00
2017-09-16 23:44:49 +00:00
public Cursor()
2017-03-16 13:41:07 +00:00
{
2017-04-19 22:42:40 +00:00
AutoSizeAxes = Axes.Both;
2017-03-16 13:41:07 +00:00
}
2018-04-13 09:19:50 +00:00
2017-03-16 13:41:07 +00:00
[BackgroundDependencyLoader]
2018-08-30 22:04:40 +00:00
private void load(OsuConfigManager config, TextureStore textures, OsuColour colour)
2017-03-16 13:41:07 +00:00
{
2017-09-16 23:44:49 +00:00
Children = new Drawable[]
{
cursorContainer = new Container
{
AutoSizeAxes = Axes.Both,
2017-09-16 23:44:49 +00:00
Children = new Drawable[]
{
new Sprite
{
2018-08-30 22:04:40 +00:00
Texture = textures.Get(@"Cursor/menu-cursor"),
2017-03-16 13:41:07 +00:00
},
2017-09-16 23:44:49 +00:00
AdditiveLayer = new Sprite
{
2019-08-21 04:29:50 +00:00
Blending = BlendingParameters.Additive,
2017-03-17 12:04:46 +00:00
Colour = colour.Pink,
2017-03-16 13:41:07 +00:00
Alpha = 0,
2018-08-30 22:04:40 +00:00
Texture = textures.Get(@"Cursor/menu-cursor-additive"),
2017-03-16 13:41:07 +00:00
},
}
2017-04-17 15:10:55 +00:00
}
2017-03-16 13:41:07 +00:00
};
2018-04-13 09:19:50 +00:00
2019-09-02 22:28:51 +00:00
cursorScale = config.GetBindable<float>(OsuSetting.MenuCursorSize);
cursorScale.BindValueChanged(scale => cursorContainer.Scale = new Vector2(scale.NewValue * base_scale), true);
2017-03-16 13:41:07 +00:00
}
}
2018-07-02 07:07:52 +00:00
private partial class MouseInputDetector : Component
{
/// <summary>
/// Whether the last input applied to the game is sourced from mouse.
/// </summary>
public IBindable<bool> LastInputWasMouseSource => lastInputWasMouseSource;
private readonly Bindable<bool> lastInputWasMouseSource = new Bindable<bool>();
public MouseInputDetector()
{
RelativeSizeAxes = Axes.Both;
}
protected override bool Handle(UIEvent e)
{
switch (e)
{
case MouseDownEvent:
case MouseMoveEvent:
lastInputWasMouseSource.Value = true;
return false;
case KeyDownEvent keyDown when !keyDown.Repeat:
case JoystickPressEvent:
case MidiDownEvent:
lastInputWasMouseSource.Value = false;
return false;
}
return false;
}
}
2018-07-02 07:07:52 +00:00
private enum DragRotationState
{
NotDragging,
DragStarted,
Rotating,
}
2017-03-16 13:41:07 +00:00
}
}