Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Controls: refactor interactivity management system #4053

Merged
merged 60 commits into from
Jul 19, 2024
Merged

Controls: refactor interactivity management system #4053

merged 60 commits into from
Jul 19, 2024

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();
}

@swharden swharden merged commit 4078033 into main Jul 19, 2024
4 checks passed
@swharden swharden deleted the 3186 branch July 19, 2024 15:24
@swharden swharden mentioned this pull request Jul 20, 2024
swharden added a commit to ByteSore/ScottPlot that referenced this pull request Jul 21, 2024
required by all plot controls after ScottPlot#4053
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