Implemented swap mouse Left Pan <-> Middle zoom#1335
Implemented swap mouse Left Pan <-> Middle zoom#1335EFeru wants to merge 2 commits intoScottPlot:masterfrom EFeru:master
Conversation
The behavior can be activated by setting Configuration.SwapDragMiddleLeft = true;
|
I think if we are going to support remapping keys/mouse buttons we should allow full (or close to it) remapping instead of just one swap. Otherwise, we open ourselves up to maintaining every configuration option in one of several booleans. Perhaps if we do that we could add a convenience function like |
|
Thank you @bclehmann for your feedback. The thing is there are more tools besides Matlab using middle mouse as pan, e.g., Autocad, even Windows Photos. So, what I try to say is that at least middle mouse pan is very common... I am open to suggestions for a more appropriate/generic name for this setting. Edit: Maybe |
|
I wasn't intending to comment on the naming, although I was suggesting that the implementation be more generic, supporting arbitrary bindings and then middle-click panning could be built on top of that. A good way to support changing key bindings would prevent an explosion of complexity if we decide to support 5 different keymaps. |
|
Hi @EFeru, thanks for opening this PR! I agree there should be a way that users can customize what buttons do, so I'm invested in reaching a solution here. This has come up before (#352 and #1222) and has been on the triage list for a while (#1028). The remaining discussion will focus on how to best implement this feature. I recognize this PR gets the job done to meet @EFeru's needs, but it would be great to have a solution that is a little deeper and extensible for alternative bindings. Best Solution: Refactor the BackendI agree with @bclehmann that a I think this goal can be totally achieved just by editing I'm thinking I may be the best person to do this since the rework would be pretty extensive, and I'd probably save it for a weekend. ScottPlot/src/ScottPlot/Control/Backend.cs Lines 1 to 709 in a39f904 Intermediate Solution: Intercept GetInputState()?Thinking out loud, I wonder if a quick solution could be realized by letting the user customize what buttons do what by targeting this function of code in the control itself ScottPlot/src/controls/ScottPlot.WinForms/FormsPlot.cs Lines 194 to 207 in a39f904 Maybe changing @EFeru for the reasons discussed above I probably won't merge this PR as it is, but if this simpler solution works for you feel free to modify this PR or open a new one to change the function modifier and I'd be happy to merge it. If my description wasn't clear enough to communicate the suggestion let me know and I can try to put together a demo or something 👍 |
It can become |
Good catch @bclehmann! Changing @EFeru if you try it and it works as expected I'd be happy to merge a PR that makes this change identically in all 3 controls. |
If we do this we probably need to communicate somehow that this is not protected by the API contract, otherwise any change to |
|
I will have a look at your suggestions and see if can try something. Thinking of loud, all combinations would be a matrix, functions vs keys. Could be like a shortcut assignment table, where functions can be:
|
|
Just tried swapping Left with Middle button in private Control.InputState GetInputState(MouseEventArgs e) =>
new()
{
X = e.X,
Y = e.Y,
LeftWasJustPressed = e.Button == MouseButtons.Middle,
RightWasJustPressed = e.Button == MouseButtons.Right,
MiddleWasJustPressed = e.Button == MouseButtons.Left,
ShiftDown = ModifierKeys.HasFlag(Keys.Shift),
CtrlDown = ModifierKeys.HasFlag(Keys.Control),
AltDown = ModifierKeys.HasFlag(Keys.Alt),
WheelScrolledUp = e.Delta > 0,
WheelScrolledDown = e.Delta < 0,
}; Which works as intended, swaps complete behavior left <-> middle. In my opinion making this mapping accesible to the user is a first step to allow some customization. |
Inherit-and-Override Example
I worked this through on my end to try it out. It's a bit clumsy, but it works. I created a new WinForms application and I did not add a UserControl1.csusing ScottPlot.Control;
using System.Windows.Forms;
namespace WinFormsFrameworkApp
{
public partial class UserControl1 : ScottPlot.FormsPlot // not "UserControl"
{
public UserControl1()
{
InitializeComponent();
}
protected override InputState GetInputState(MouseEventArgs e)
{
return new InputState()
{
X = e.X,
Y = e.Y,
LeftWasJustPressed = e.Button == MouseButtons.Middle, // CUSTOM
RightWasJustPressed = e.Button == MouseButtons.Right, // CUSTOM
MiddleWasJustPressed = e.Button == MouseButtons.Left, // CUSTOM
ShiftDown = ModifierKeys.HasFlag(Keys.Shift),
CtrlDown = ModifierKeys.HasFlag(Keys.Control),
AltDown = ModifierKeys.HasFlag(Keys.Alt),
WheelScrolledUp = e.Delta > 0,
WheelScrolledDown = e.Delta < 0,
};
}
}
}Form1.cspublic Form1()
{
InitializeComponent();
userControl11.Plot.AddSignal(ScottPlot.DataGen.Sin(51));
userControl11.Plot.AddSignal(ScottPlot.DataGen.Cos(51));
userControl11.Refresh();
}Should we make GetInputState() overridable?The key to this example being possible is that @bclehmann's hesitation about this is very justified - if we encourage people to inherit-and-override, we lose our ability to modify these functions later without breaking our users' code. I think decorating this method with @EFeru if this technique seems like a good intermediate solution for you, you're welcome to modify this PR or open a new one and I'd be happy to merge it in. #1274 These changes would be made in all 3 user controls:
[Obsolete("WARNING: API may change in future (override with caution)")]
protected virtual Control.InputState GetInputState(MouseEventArgs e) =>#pragma warning disable CS0618 // Type or member is obsolete |
|
I will have look tomorrow if it's not too late. Maybe I close this one and create a new PR, i will see. |
|
I gave it a bit more tought and even if this solution is feasible, on the long run I agree with both of you that is better not to override since it will be more difficult to update future releases of ScottPlot on NuGet. If I have overrides, I have to posibly always maintain my implementations. In short, I have another idea. Do you think it is possible to add some mapping in the Configuration? Maybe an array giving the mapping for the used bindings, e.g.,: formsPlot.Configuration.MouseBindings = {MouseButtons.Left, MouseButtons.Middle, MouseButtons.Right};
formsPlot.Configuration.KeyBindings = {Keys.Shift, Keys.Control, Keys.Alt};And these bindings are used in I did a quick test, and seems to works: public MouseButtons[] MouseBindings = {MouseButtons.Middle, MouseButtons.Right, MouseButtons.Left };And In private Control.InputState GetInputState(MouseEventArgs e) =>
new()
{
X = e.X,
Y = e.Y,
LeftWasJustPressed = e.Button == Configuration.MouseBindings[0], //MouseButtons.Left,
RightWasJustPressed = e.Button == Configuration.MouseBindings[1], //MouseButtons.Right,
MiddleWasJustPressed = e.Button == Configuration.MouseBindings[2], //MouseButtons.Middle,
ShiftDown = ModifierKeys.HasFlag(Keys.Shift),
CtrlDown = ModifierKeys.HasFlag(Keys.Control),
AltDown = ModifierKeys.HasFlag(Keys.Alt),
WheelScrolledUp = e.Delta > 0,
WheelScrolledDown = e.Delta < 0,
}; |
This is the fundamental problem that makes this hard... I'm guessing the best solution is to handle mapping in the control (not the backend), then eventually update the backend to use button-agnostic names (e.g.,
I like the direction this is going! Maybe something like this is even simpler... public MouseButtons MouseButtonDragPan = MouseButtons.Left;
public MouseButtons MouseButtonDragZoomRectangle = MouseButtons.Middle;
public MouseButtons MouseButtonDragZoom = MouseButtons.Right;
public Keys KeyOnlyHorizontal = Keys.Control;
public Keys KeyOnlyVertical = Keys.Shift;
public Keys KeyZoomRegion = Keys.Alt;
private Control.InputState GetInputState(MouseEventArgs e) =>
new()
{
X = e.X,
Y = e.Y,
LeftWasJustPressed = e.Button == MouseButtonDragPan,
RightWasJustPressed = e.Button == MouseButtonDragZoom,
MiddleWasJustPressed = e.Button == MouseButtonDragZoomRectangle,
ShiftDown = ModifierKeys.HasFlag(KeyOnlyVertical),
CtrlDown = ModifierKeys.HasFlag(KeyOnlyHorizontal),
AltDown = ModifierKeys.HasFlag(KeyZoomRegion),
WheelScrolledUp = e.Delta > 0,
WheelScrolledDown = e.Delta < 0,
}; |
|
I tried the change above. It works in normal plot but not as expected with draggble lines, see below: 2021-10-12.21-49.mp4Also for WPF version is hard to implement this change becasuse the buttons are in the ScottPlot/src/controls/ScottPlot.WPF/WpfPlot.xaml.cs Lines 197 to 202 in 70285d4 Since this become more intricated than expected, I am thinking for now to close this PR, and maybe later if you have a better idea on how to do it we can re-address this topic. |
It technically works because swapping left/right buttons means you have to right-click-drag axis lines 😅 I agree this isn't the intended result though
I'll close this PR for now but I started #1354 to track progress toward a true fix (deep refactoring of the control back-end and event handling system) and hopefully I can complete that soon 👍 |
The behavior can be activated by setting
Configuration.SwapDragLeftMiddle = true;New Contributors:
Please review CONTRIBUTING.md
Hacktoberfest Participants:
Check-out the Hacktoberfest 2021 page
Purpose:
Configuration.SwapDragLeftMiddle = true;Default behavior isfalse, so the original key bindings are not affectedIsssue to be solved:
For some reason when using Draggable lines, the zoom also triggers, which is not wanted of course. @swharden maybe you can help me to fix that. I tried to debug, and the
createMouseMovedToZoomRectangleis not triggering but I still get a zoom.ScottPlot/src/ScottPlot/Control/Backend.cs
Line 582 in a39f904
You can use the Cookbook to test, just set
SwapDragLeftMiddle = trueinConfiguration.cs