Skip to content

Controls: refactor interactivity management system#4053

Merged
swharden merged 60 commits intomainfrom
3186
Jul 19, 2024
Merged

Controls: refactor interactivity management system#4053
swharden merged 60 commits intomainfrom
3186

Conversation

@swharden
Copy link
Member

@swharden swharden commented Jul 7, 2024

This PR explores alternative strategies for customizing user interactivity in graphical environments.

Primary Goals

  • Ability to re-map inputs with actions (e.g., right-click-drag pan)
  • Ability to add arbitrary inputs (e.g., two-finger pinch)
  • Ability to customize actions (e.g., increasing sensitivity of right-click-drag zoom)
  • Ability to add arbitrary actions (e.g., holding left and right mouse buttons down while dragging only pans horizontally or vertically)
  • Improved ability to unit test interactive behavior
  • Ability to opt-in to the new system, allowing existing users to continue using the original Interaction.cs and PlotActions.cs system while the new system matures.
  • Related: Controls: refactor input binding system #3186

Architecture

  • User inputs are structs that inherit IUserInput and contain any properties that may be unique to that type of input (e.g., LeftMouseDown has a stores a Pixel and DateTime)

  • User input actions are classes that contain logic for when and how to respond to user inputs, including whether to refresh the plot or stop processing further actions (e.g., LeftClickDragPan engages only after LeftMouseDown happens and MouseMove moved by more than 5 pixels, preventing other actions from being processed until LeftMouseUp occurs)

  • Plot controls have a UserInputProcessor that they pass user inputs into, and it holds a collection of user input actions that decide if and how to respond to the events. Users can remove actions (like double-click benchmark), add custom actions (like holding the A key while left-click-dragging will only pan diagonally), and even pass custom input types through (like three-finger-drag).

Rules to Implement

Items are checked only when implemented and unit tests are in place

  • Left-click-drag pan
  • Shift+ left-click-drag pan vertically
  • Ctrl + left-click-drag pan horizontally
  • Right-click-drag zoom
  • Shift + right-click-drag zoom
  • Ctrl + right-click-drag zoom
  • Scroll wheel zoom
  • Shift + scroll wheel zoom vertically
  • Ctrl + scroll wheel zoom horizontally
  • Middle-click-drag zoom rectangle
  • Shift + Middle-click-drag zoom rectangle horizontally
  • Ctrl + Middle-click-drag zoom rectangle vertically
  • Alt + left-click-drag zoom rectangle
  • Right-click open context menu
  • Single left-click custom action
  • Double-click benchmark
  • Arrow keys pan
  • Alt + arrow keys zoom
  • Create a mock IPlotControl that can simulate user inputs for testing
  • Interactions initiated over an axis only act on that axis
  • Test all actions with DPI scaling enabled
  • Test all actions with custom ScaleFactor
  • Create MultiAxisLimits to replace MultiAxisLimitManager
  • Add a demo with examples of common user input customizations

@swharden swharden linked an issue Jul 7, 2024 that may be closed by this pull request
@swharden swharden marked this pull request as ready for review July 18, 2024 23:27
@swharden
Copy link
Member Author

swharden commented Jul 18, 2024

I'm getting close to merging this. The new behavior is demonstrated in the "custom mouse behavior" demo app.

image

The code shows the general style of how the new API can be used.

public CustomMouseActions()
{
InitializeComponent();
// Disable the old input system and enable the new one.
// The new one will be enabled by default in a future release.
formsPlot1.Interaction.IsEnabled = false;
formsPlot1.UserInputProcessor.IsEnabled = true;
formsPlot1.Plot.Add.Signal(ScottPlot.Generate.Sin());
formsPlot1.Plot.Add.Signal(ScottPlot.Generate.Cos());
btnDefault.Click += (s, e) =>
{
richTextBox1.Text = "left-click-drag pan, right-click-drag zoom, middle-click autoscale, " +
"middle-click-drag zoom rectangle, alt+left-click-drag zoom rectangle, right-click menu, " +
"double-click benchmark, scroll wheel zoom, arrow keys pan, " +
"shift or alt with arrow keys pans more or less, ctrl+arrow keys zoom";
formsPlot1.UserInputProcessor.IsEnabled = true;
formsPlot1.UserInputProcessor.Reset();
};
btnDisable.Click += (s, e) =>
{
richTextBox1.Text = "Mouse and keyboard events are disabled";
formsPlot1.UserInputProcessor.IsEnabled = false;
};
btnCustom.Click += (s, e) =>
{
richTextBox1.Text = "middle-click-drag pan, right-click-drag zoom rectangle, " +
"right-click autoscale, left-click menu, Q key autoscale, WASD keys pan";
formsPlot1.UserInputProcessor.IsEnabled = true;
// remove all existing responses so we can create and add our own
formsPlot1.UserInputProcessor.UserActionResponses.Clear();
// middle-click-drag pan
var panButton = ScottPlot.Interactivity.StandardMouseButtons.Middle;
var panResponse = new ScottPlot.Interactivity.UserActionResponses.MouseDragPan(panButton);
formsPlot1.UserInputProcessor.UserActionResponses.Add(panResponse);
// right-click-drag zoom rectangle
var zoomRectangleButton = ScottPlot.Interactivity.StandardMouseButtons.Right;
var zoomRectangleResponse = new ScottPlot.Interactivity.UserActionResponses.MouseDragZoomRectangle(zoomRectangleButton);
formsPlot1.UserInputProcessor.UserActionResponses.Add(zoomRectangleResponse);
// right-click autoscale
var autoscaleButton = ScottPlot.Interactivity.StandardMouseButtons.Right;
var autoscaleResponse = new ScottPlot.Interactivity.UserActionResponses.SingleClickAutoscale(autoscaleButton);
formsPlot1.UserInputProcessor.UserActionResponses.Add(autoscaleResponse);
// left-click menu
var menuButton = ScottPlot.Interactivity.StandardMouseButtons.Left;
var menuResponse = new ScottPlot.Interactivity.UserActionResponses.SingleClickContextMenu(menuButton);
formsPlot1.UserInputProcessor.UserActionResponses.Add(menuResponse);
// Q key autoscale too
var autoscaleKey = new ScottPlot.Interactivity.Key("Q");
Action<ScottPlot.Plot, ScottPlot.Pixel> autoscaleAction = (plot, pixel) => plot.Axes.AutoScale();
var autoscaleKeyResponse = new ScottPlot.Interactivity.UserActionResponses.KeyPressResponse(autoscaleKey, autoscaleAction);
formsPlot1.UserInputProcessor.UserActionResponses.Add(autoscaleKeyResponse);
// WASD keys pan
var keyPanResponse = new ScottPlot.Interactivity.UserActionResponses.KeyboardPanAndZoom()
{
PanUpKey = new ScottPlot.Interactivity.Key("W"),
PanLeftKey = new ScottPlot.Interactivity.Key("A"),
PanDownKey = new ScottPlot.Interactivity.Key("S"),
PanRightKey = new ScottPlot.Interactivity.Key("D"),
};
formsPlot1.UserInputProcessor.UserActionResponses.Add(keyPanResponse);
};
Load += (s, e) => btnDefault.PerformClick();
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Controls: refactor input binding system

1 participant