microsoft / microsoft-ui-xaml

Windows UI Library: the latest Windows 10 native controls and Fluent styles for your applications
MIT License
6.36k stars 678 forks source link

Multiple InkCanvases (2 or more) under a VisualInteractionSource will not allow user to simultaneously draw on both #912

Closed stefangavrilasengage closed 5 years ago

stefangavrilasengage commented 5 years ago

Describe the bug Having two InkCanvases (or more) in a "ScrollViewer" will not allow drawing on both of them with two fingers (or 1 finger and 1 mouse).

Steps to reproduce the bug

  1. Create a blank C# Universal Windows app (XAML)
  2. In the MainPage mirror the following structure :
    <ContentPresenter x:Name="scrollViewer">
    <Canvas Width="9000" Height="3000">
      <InkCanvas Name="i1" Width="500" Height="500" />
      <InkCanvas Name="i2" Width="500" Height="500" Canvas.Left="500" />
    </Canvas>
    </ContentPresenter>
  3. In the ctor after InitializeComponent allow the InkCanvases to accept input:
    
    i1.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Touch;
    i2.InkPresenter.InputDeviceTypes = CoreInputDeviceTypes.Mouse | CoreInputDeviceTypes.Pen | CoreInputDeviceTypes.Touch;

var viewportVisual = ElementCompositionPreview.GetElementVisual(scrollViewer);

// Remove this next line to see the desired behavior working var interactionSource = VisualInteractionSource.Create(viewportVisual);


4. Launch the app and attempt to simultaneously draw on both InkCanvases using two separate fingers.

**Expected behavior**
Ink is drawn on both InCanvases.

**Screenshots**
N/A

**Version Info**
N/A

NuGet package version: 
N/A

| Windows 10 version                  | Saw the problem? |
| :--------------------------------- | :-------------------- |
| Insider Build (xxxxx)              | Have not tested  |
| May 2019 Update (18362)            | Yes   |
| October 2018 Update (17763)        | Yes   |
| April 2018 Update (17134)          | Yes   |
| Fall Creators Update (16299)       | Yes   |
| Creators Update (15063)            | Yes   |
| Anniversary Update (14393)         | N/A   |

<!-- Which device form factors did you see the issue on? Leave blank if you didn't try that device. -->
| Device form factor | Saw the problem? |
| :-------------------- | :------------------- |
| Desktop                 | Yes   |
| Tablet                 | Yes   |
| Xbox                      | Have not tested   |
| Surface Hub          | Yes    |
| IoT                         | Have not tested    |

**Additional context**
The app we are trying to build is a sort of a digital whiteboard. Therefore we have a lot of objects on a big canvas. To allow the user to navigate the whole canvas, we wrap it in a ScrollViewer.
Each object has ink on it (on a not editable Win2D layer) and to draw on an object you must toggle it to drawing mode (when toggled, an InkCanvas is injected above the Win2D layer).
However, if you try to draw on two or more InkCanvases the following happens:
1. First InkCanvas is touched (and not released yet) => i1 PointerPressing
2. Second InkCanvas is touched (with first InkCanvas still being touched by another finger) => i1 PointerLost

I will mention that in order to alleviate this issue I've created my own ScrollViewer using the UIElement.Manipulation* events which is far from perfect, but allows multiple inputs.

This relates to [A more flexible ScrollViewer](https://github.com/microsoft/microsoft-ui-xaml/issues/108) where @micahl has requested the creation of a separate issue.
micahl commented 5 years ago

Thanks for opening this! Adding another detail is that this issue exists for both the existing ScrollViewer and the one in WinUI prerelease. The new ScrollViewer has an IgnoreInputKind option to selectively disable to which inputs it responds. Setting it to "All" causes it to act as a dumb container, but even then the issue described above still happens. Seems like it could be related to input issues lower in the stack.

@stefangavrilasengage, I'm assuming your custom ScrollViewer is not based on InteractionTracker, right? Is it based on the various Manipulation* events?

stefangavrilasengage commented 5 years ago

@micahl You are correct sir, my custom ScrollViewer is based on Manipulation* events :) PS : My pleasure to assist !

micahl commented 5 years ago

I've verified that this isn't a ScrollViewer-specific issue. It looks like a bug in the input system below xaml. I've updated the repro to include the minimal steps to repro this without involving a ScrollViewer. All that's required is to create a VisualInteractionSource out of a common ancestor of the InkCanvases. The same bug happens with the old ScrollViewer (Dmanip based).

micahl commented 5 years ago

Tracked by internal issue 22315725.

micahl commented 5 years ago

@stefangavrilasengage are the InkCanvas controls added/removed dynamically based on when a user toggles an item to enter drawing mode? Is it feasible to insert the InkCanvas in your UI tree above the ScrollViewer rather than as a descendent (i.e. child of the Canvas)? Based on my understanding of your setup there are interesting questions related to input in a scenario like this. If one finger is on an InkCanvas but another is outside an InkCanvas and moves then what happens? I'm assuming you wouldn't want one user to be able to pan all the content causing the doodle of another user to streak ink.

stefangavrilasengage commented 5 years ago

@micahl, to answer your questions :)

Sadly that's the limitation I also have to accept - but I just want to know how I could do it and/or if it's possible to toggle this behaviour somehow.

Thank you and apologies for the late reply !

micahl commented 5 years ago
  • Unfortunately I cannot insert the InkCanvas above the ScrollViewer as multiple people can be writing at the same time in different areas and then moving the objects around (with their respective InkCanvases)

Is it that they are moving around individual objects (with their respective InkCanvases) within the larger canvas area or is it that one user is panning the entire surface? If it's the first one then I think that it would still be possible to add the InkCanvases as siblings of the ScrollViewer with each transformed to be positioned over the associated object in the ScrollViewer's content area. I believe this would allow multi-input to work without requiring a custom ScrollViewer. If it's the second one, then is there anything that requires exiting drawing mode on all the objects before the panning surface is active? Or is it that anyone can pan the content at any time? That sounds dangerous to me, but perhaps its not like I imagine. :)

The behavior you're bumping into with the control is related to the system's gesture targeting "parent promotion" behavior. Some details from folks working on input... "When there are two children receiving input with a common ancestor all input is routed to that ancestor to make a routing decision about both contacts. This was done to support scenarios where input spanning two children should actually be grouped as a single action handled by the parent. For example, if you have two nested scrollers supporting panning and a parent supporting zooming then perform a zoom spanning the two children input needs to get routed to the parent and treated as the zoom." I think its worth exploring the alternatives mentioned earlier as I don't expect this behavior to change.

micahl commented 5 years ago

Closing out this issue.