WPF: Dragging an Element via Touch is pretty choppy - c#

I have a Rectangle which can be dragged within some boundaries. This works perfect with the mouse.
As soon as I set IsManipulationEnabled to true the mouse-events don't work anymore.
However I need this to get touch-events on the rectangle. Therefore I set it to true.
I'm trying to compute all changes in the ManipulationDelta-event like in the following function.
Scaling works already pretty good, but moving the object via dragging it with the finger is very choppy + plus sometimes the Rectangle jumps back and forth.
private void UserControl_ManipulationDelta(object sender, ManipulationDeltaEventArgs e)
{
//Scaling works already pretty good
RangeBar.Width *= e.DeltaManipulation.Scale.X;
//Moving the element is very choppy, what am I doing wrong here?
this.startX = this.startX + e.DeltaManipulation.Translation.X;
RangeBar.SetValue(Canvas.LeftProperty, startX);
}

I'd try using the CumulativeManipulation instead.
Whenever I need to do a UI element movement via dragging, I don't try to reuse the same variable and modify it by the delta and then reuse that same variable for setting position. It almost always gives me stability issues regardless of platform. Instead, try storing a variable when dragging starts and adding the delta to that variable only when you need to update position. So something more like this:
Point origin;
void MouseDown(Point location)
{
origin = location;
}
void MouseDrag(Vector cumulativeOffset)
{
SetControlLocation(origin+cumulativeOffset);
}
Also, what's the source of the ManipulationEvent? You'll definitely want to make sure it's not the current rectangle, or that will definitely cause the issues you're seeing.

Related

Place scene object to get behind UI element

I have an object in my scene's sky and want to prevent it from getting cut out of the view, no matter the screen ratio. So I set up a canvas and a PlaceHolder RectTransform on it, that - as being UI element - gets scaled and repositioned in respect to the screen aspect ratio. My goal is to use this position to determine where to put my scene object by using the following script:
Camera mainCam;
RectTransform placeHolder;
private void Start()
{
mainCam = GameObject.FindGameObjectWithTag("MainCamera")
.GetComponent<Camera>();
placeHolder = GameObject.FindGameObjectWithTag("SkyObjectPlaceholder")
.GetComponent<RectTransform>();
var screenPos = mainCam.ScreenToWorldPoint(
new Vector3(placeHolder.rect.x, placeHolder.rect.y, 0));
transform.position = new Vector3(screenPos.x, screenPos.y,
transform.position.z);
}
The issue is that my sky object is always placed in the center of the screen. It is not a child of anything on the scene, I just put it there. The placeholder is to the upper right in the canvas. Note that I tried playing with anchors for the UI element, set it to be left bottom, right top, no change, the scene object is always in the center of the screen.
What am I doing wrong? How to make a Scene element match up the position of a Canvas element, so it gets right behind it?
Note: I'm using a perspective camera. I don't know if it matters, but I thought I mention it, just in case.
Yet another edit: after messing around with the issue a bit, I found that the problem is possibly linked to the Z input I give the ScreenToWorldPoint, in my case it's 0. I couldn't get a viable Z to input though.
Hmmm... i think you should change the sorting layer of that object

Unity UI - Interacting with worldspace UI when cursor is locked

I'm trying to interact with world space UI using a first person controller when Cursor.lockState is set to CursorLockMode.Locked.
world space UI and a character
But when cursor is locked, cursor position is set to (-1, -1),
which is told from the Inspector.
cursor position of (-1, -1)
I performed a graphic raycast with EventSystem.RaycastAll, Sreen.width/2 and PointerEventData. EventSystem.current.RaycastAll collects all UI objects in the middle of screen, but no events is sent to them.
I also tried ExecuteEvents.Execute<IEventSystemHandler> to manully send event to UI targets. This works for button when I send 'submit' event to it. Obviously this is not an elegant solution. I have no idea how to send message to slider either.
// manully send a 'submit' event to UI elements
List<RaycastResult> results = new List<RaycastResult>();
void Update() {
if (Input.GetButtonUp("Fire1")) {
PointerEventData data = new PointerEventData(EventSystem.current);
data.position = new Vector2(Screen.width / 2, Screen.height / 2);
EventSystem.current.RaycastAll(data, results);
foreach (var result in results) {
ExecuteEvents.ExecuteHierarchy<ISubmitHandler>(
result.gameObject, data,
ExecuteEvents.submitHandler
);
}
}
}
This crazy attempt works when played full-screen on Windows. 2333
[System.Runtime.InteropServices.DllImport("user32.dll")]
public static extern int SetCursorPos ( int x , int y );
void SetCursorPositionToCenter()
{
  SetCursorPos(Screen.width/2, Screen.height/2);
}
Relavant Resources
I had a lot of problems working with Graphics Raycaster and Global UI elements as well. I tried a lot of things as well but the event system didn't seem to work. But I was simulating a mouse with a touchpad and maybe I didn't completely simulate how a mouse works with UI elements.
I ended up abandoning graphics raycaster and instead add a box collider to all UI elements. That way a regular physics raycaster would be able to detect it and run some code. For the slider, it is just increasing the slider's value as your x or y of your pointer increase.
Hope this helps a little.
What you're doing with the submit handler is similar to the many different ways this can be done. I've not dealt with sliders specifically, because it's not very easy to control sliders in this method (player controller or, in the case of VR, the user's head, does not do straight lines very well). So for slider input we usually go with +/- buttons. Another idea could be that when the user is pointing at a UI element that you release the cursorLock when they click. When click is released you can resume cursor lock.
UI elements are also often in need of states like hover/onEnter, onExit, sometimes pointer up, and usually pointer down. So we've often used Selectable class for this. I think all the interactable UI elements inherit from this so it's a good spot to access the UI element from. This will usually trigger the proper events but it can be a little tricky. Examine some of the many readily available gaze input systems as they're doing the same thing. I don't think I've ever seen one handle sliders though (they're usually meant for VR and gaze-to-slider control would be bad UX).
Cheers and good luck!
If you're trying to access UI elements without affecting gameplay you can simply set Time.TimeScale = 0; which basically pauses time but UI elements will still be active. Then just unlock your cursor and use it freely.

MouseDown and Click conflict

I have a Drag() method on form_MouseDown event. I also have a click event on the form. The problem is that if I click on the form, MouseDown event gets triggered and it never gets the chance to trigger click event.
What is the best way to solve this issue? I was thinking counting pixels if the form is actually dragged, but there has to be a better way. Any suggestions?
I was thinking counting pixels if the form is actually dragged, but there has to be a better way.
Nope, that's exactly how you have to do it.
This isn't just a software limitation; it's very much a practical one as well. If you think through the problem from a user's perspective, you'll immediately see the problem as well as the solution. Ask yourself, what is the difference between a click and a drag?
Both of them start with the mouse button going down over the object, but one of them ends with the mouse button going back up over the object in the same position and the other one ends with the mouse button going back up in a completely different position.
Since time machines haven't been perfected yet, you have no way of knowing this in advance.
So yes, you need to maintain some kind of a distance threshold, and if the pointer moves outside of that distance threshold while it is down over the object, then you consider it a drag. Otherwise, you consider it a click.
That distance threshold should not be 0. The user should not be required to hold the mouse completely still in order to initiate a click. A lot of users are sub-par mousers. They are very likely to twitch slightly when trying to click. If the threshold is 0, they'll end up doing a lot of inadvertent dragging when they try to click.
Of course, you don't actually have to worry about any of this or compute the drag threshold yourself. Instead, use the Windows default values, obtainable by calling the GetSystemMetrics function and specifying either SM_CXDRAG or SM_CYDRAG. (These might be exposed somewhere by the WinForms framework, but I don't think so. It's just as easy to P/Invoke them yourself.)
const int SM_CXDRAG = 68;
const int SM_CYDRAG = 69;
[DllImport("user32.dll")]
static extern int GetSystemMetrics(int index);
Point GetDragThreshold()
{
return new Point(GetSystemMetrics(SM_CXDRAG), GetSystemMetrics(SM_CYDRAG));
}
In the field of UX/UI, this sort of thing is called hysteresis or debouncing, by analogy to the use of these terms in physics and electronics.
I found this solution, although it is for a double-click and a mouse down events:
void pictureBox_MouseDown(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left && e.Clicks ==1)
{
PictureBox pb = (PictureBox)sender;
DoDragDrop((ImageData)pb.Tag, DragDropEffects.Copy);
}
}
source: http://code.rawlinson.us/2007/04/c-dragdrop-and-doubleclick.html
Unfortunatelly, at the point of time when "button-is-pressed" you don't know yet if the desired action is just a click or a drag-drop. You will find it out it later.
For a click, the determinant is "no movement" and "button up".
For a drag, the determinant is "movement" and "button up".
Hence, to disambiguate those interactions, you have to track not only the buttons, but also the movement. You do not need to track the overall movement, only the movement between button-down and button-up is interesting.
Those events are therefore a good place to start/stop the Mouse.Capture mechanisms (to dynamically present drag adorners and drop location hints), or, in simplier form - to store the origin and target of movement vector and check if the distance is > D (even if movement occurred, there should be some safe minimal distance within which the DRAG is canceleed. The mouse is "jaggy" sometimes, and people would really don't like your app to start dragging when they double click at the end of fast mouse pointer movement :) )

Get the distance of mouse movement

I have a form and inside it I got a small Panel called Player. How can I "link" the panel to user's mouse, so it will move as the mouse moves?
I've already subscribed Player_MouseMove to Player.MouseMove event but I can't figure out how much has the mouse actually moved. Only way I can come up with is to have a such:
private Point previousLocation;
private void Player_MouseMove(object sender, MouseEventArgs e)
{
int differenceX, differenceY;
differenceX = e.X - previousLocation.X;
differenceY = e.Y - previousLocation.Y;
previousLocation = e.Location;
}
This seems pretty stupid, having an extra variable and calculating the difference everytime. Perfect way would be like Player.LinkToCursor(); or such, but if there's no automated way, is there atleast a better way?
Looking at http://msdn.microsoft.com/en-us/library/system.windows.forms.mouseeventargs.aspx I can't see anything that would help you get this done any better.
There is however one thing you could do:
Point difference = e.Location - (Size)previousLocation;
Vector-arithmetics ;)

How to limit cursor movement in WPF based app?

I'm working with a WPF app, more specifically a Canvas with draggable elements.
Once an item is being dragged, I'd like to limit the scope of cursor movement to inside the canvas where the items are being dragged about.
The event which can start a drag is shown below
private void WidgetCanvas_PreviewHeaderLeftMouseDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
... logic to figure out if this is a valid drag, blah blah blah ...
this.IsDragging = true;
// TODO: clip the available cursor movement to actual width of Canvas
}
On the Preview-MouseUp, I'd like to simply "unclip" the cursor movement back to it's normal state.
I'll be monitoring the movement of the mouse once I start dragging (PreviewMouseMove), so worst case, I could manually check the position of the mouse and keep it constrained to the canvas, but that seems a little ugly.
Anyone have a better way to limit the cursor boundaries?
There's no clean way to do this and the not-so-clean ways will make your mouse cursor "jitter" at the border of the clipping area.
Moreover, I'd question if this is really a good idea. The user should really own the mouse and he or she generally gets frustrated when you try to artificially limit things that he or she owns.
If you want to provide feedback when the mouse leaves your canvas, maybe you could leave the item being dragged stuck along the border while the mouse button is still down? This would tell the user that he or she has left the target area without trying to impose limitations on where the mouse can go.
Good luck!
You should be able to do it using the ClipCursor native API.

Categories