Skip to content

Commit

Permalink
Merge pull request #5964 from bdach/tablet-input-crash
Browse files Browse the repository at this point in the history
Fix crash on tablet stylus touch when stylus is over inactive area and mouse confine is off
  • Loading branch information
peppy authored Aug 12, 2023
2 parents 04dfe11 + c614a4a commit 6bc3e8f
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 3 deletions.
102 changes: 101 additions & 1 deletion osu.Framework.Tests/Visual/Input/TestSceneTabletInput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
Expand All @@ -18,6 +20,11 @@ namespace osu.Framework.Tests.Visual.Input
{
public partial class TestSceneTabletInput : FrameworkTestScene
{
private readonly FillFlowContainer contentFlow;

[Resolved]
private FrameworkConfigManager frameworkConfigManager { get; set; } = null!;

public TestSceneTabletInput()
{
var penButtonFlow = new FillFlowContainer
Expand All @@ -38,7 +45,7 @@ public TestSceneTabletInput()
for (int i = 0; i < 16; i++)
auxButtonFlow.Add(new AuxiliaryButtonHandler(i));

Child = new FillFlowContainer
Child = contentFlow = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Expand All @@ -56,7 +63,100 @@ protected override void LoadComplete()
var tabletHandler = host.AvailableInputHandlers.OfType<OpenTabletDriverHandler>().FirstOrDefault();

if (tabletHandler != null)
{
AddToggleStep("toggle tablet handling", t => tabletHandler.Enabled.Value = t);

contentFlow.Insert(-1, new TabletAreaVisualiser(tabletHandler));
AddSliderStep("change width", 0, 1, 1f,
width => tabletHandler.AreaSize.Value = new Vector2(
tabletHandler.AreaSize.Default.X * width,
tabletHandler.AreaSize.Value.Y));

AddSliderStep("change height", 0, 1, 1f,
height => tabletHandler.AreaSize.Value = new Vector2(
tabletHandler.AreaSize.Value.X,
tabletHandler.AreaSize.Default.Y * height));

AddSliderStep("change X offset", 0, 1, 0.5f,
xOffset => tabletHandler.AreaOffset.Value = new Vector2(
tabletHandler.AreaSize.Default.X * xOffset,
tabletHandler.AreaOffset.Value.Y));

AddSliderStep("change Y offset", 0, 1, 0.5f,
yOffset => tabletHandler.AreaOffset.Value = new Vector2(
tabletHandler.AreaOffset.Value.X,
tabletHandler.AreaSize.Default.Y * yOffset));
}

AddToggleStep("toggle confine mode", enabled => frameworkConfigManager.SetValue(FrameworkSetting.ConfineMouseMode,
enabled ? ConfineMouseMode.Always : ConfineMouseMode.Never));
}

private partial class TabletAreaVisualiser : CompositeDrawable
{
private readonly OpenTabletDriverHandler handler;

private Box fullArea = null!;
private Container activeArea = null!;

private Bindable<Vector2> areaSize = null!;
private Bindable<Vector2> areaOffset = null!;

public TabletAreaVisualiser(OpenTabletDriverHandler handler)
{
this.handler = handler;
}

[BackgroundDependencyLoader]
private void load()
{
Margin = new MarginPadding(10);
AutoSizeAxes = Axes.Both;
InternalChild = new Container
{
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
fullArea = new Box
{
Width = handler.AreaSize.Default.X,
Height = handler.AreaSize.Default.Y,
Colour = FrameworkColour.GreenDark
},
activeArea = new Container
{
Origin = Anchor.Centre,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = FrameworkColour.YellowGreen
},
new SpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Text = "Active area"
}
}
}
}
};
}

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

areaSize = handler.AreaSize.GetBoundCopy();
areaSize.BindValueChanged(size => activeArea.Size = size.NewValue, true);
areaSize.DefaultChanged += fullSize => fullArea.Size = fullSize.NewValue;
fullArea.Size = areaSize.Default;

areaOffset = handler.AreaOffset.GetBoundCopy();
areaOffset.BindValueChanged(offset => activeArea.Position = offset.NewValue, true);
}
}

private partial class PenButtonHandler : CompositeDrawable
Expand Down
9 changes: 7 additions & 2 deletions osu.Framework/Input/ButtonEventManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
Expand Down Expand Up @@ -95,7 +94,13 @@ private bool handleButtonDown(InputState state)
/// <param name="state">The current <see cref="InputState"/>.</param>
private void handleButtonUp(InputState state)
{
Debug.Assert(ButtonDownInputQueue != null);
// in rare cases, a button up event may arrive without a preceding mouse down event.
// one example of this is an absolute mouse up input from a tablet, which happened when the stylus was positioned
// outside the bounds of the active tablet area, with confine mouse to window off.
// it's an awkward configuration and as such it is not exactly clear what should happen in that case,
// but what should definitely not happen is a crash.
if (ButtonDownInputQueue == null)
return;

HandleButtonUp(state, ButtonDownInputQueue.Where(d => d.IsRootedAt(InputManager)).ToList());
ButtonDownInputQueue = null;
Expand Down
3 changes: 3 additions & 0 deletions osu.Framework/Input/UserInputManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ public override void HandleInputStateChange(InputStateChangeEvent inputStateChan
break;

case ButtonStateChangeEvent<MouseButton> buttonChange:
// presses registered when the mouse pointer is outside the window are ignored.
// however, releases registered when the mouse pointer is outside the window cannot be ignored;
// handling them is essential to correctly handling mouse capture (only applicable when relative mode is disabled).
if (buttonChange.Kind == ButtonStateChangeKind.Pressed && Host.Window?.CursorInWindow.Value == false)
return;

Expand Down

0 comments on commit 6bc3e8f

Please sign in to comment.