:GetPoint() is hard to calculate because it usually returns the edge/corner the frame is closest to which is why Version 1 was flawed.
Are you sure that's not just the :StopMovingOrSizing() that resets the anchor point to nearest? I've never seen GetPoint() return anything other than exactly what I gave in SetPoint(), except after StartMoving/StopMovingOrSizing which reset everything.
StartMoving() and StopMovingOrSizing() should take a reference Frame and Point as optional arguments. that would let you do StopMovingOrSizing(frameParent, "CENTER") to have your new point coordinates relative to the "CENTER" point of frameParent.
with no arguments, they'd function the same as they do now so it would be a backwards compatible method to solve this particular issue.
i was suggesting not calling it and mimicking its behavior with your own OnUpdate() function
Oh, I see; I misunderstood.
I'll have to think about that, maybe that is a better solution. I guess it depends partly on performance; with that method, you'd have to GetCursorPosition(), GetEffectiveScale(), bit of division, ClearAllPoints(), SetPoint() for every frame, although maybe it doesn't matter since the user persumably doesn't drag stuff around all that often while needing top performance.
OnMouseDown->save the cursor and window position and set OnUpdate script
OnUpdate->get the cursor position and calculate the delta (being mindful of the effective scale), then adjust the window position
OnMouseUp->kill the OnUpdate script
Which is, in fact, exactly what I did. In post #4 of this thread. :)
For any other folks just joining the thread, it isn't really a call-for-help anymore; I figured out a way to hack around the (IMO stupid) behavior of :StartMoving() and :StopMovingOrSizing(). The remainder of this thread has been about describing the issue and explaining why it can cause trouble if you're not expecting it.
After moving the frame (whether using OnDrag or OnMouseX) call this?:
We may be confusing terms here at this point. Frames have a "frame parent", which affects things like visibility (if frame-parent is not visible, then child is not visible), but that doesn't really come into play for positioning or dragging. What's at issue here is the "anchor parent" (i.e. the second argument to SetPoint or second return from GetPoint), because it gets changed by StartMoving() and again by StopMovingOrSizing(), and changing it back is non-trivial because then you have to convert coordinates.
I think moving any frame changes its parent to UIParent and its anchor to whichever UIParent anchor is closest (TOPLEFT, BOTTOM, etc.). Correct me if I'm wrong.
You are right (or at least mostly right; I think it gets anchored to the screen, technically, and not UIParent, although they have the same coordinates). That is the behavior that spawned this thread in the first place, because (at that time) it didn't seem to be documented (I've since added it to wowwiki) and it forces you to do a bunch of extra legwork to get dragging to work the way you want it to on a frame that isn't supposed to be anchored to UIParent.
i'm not sure i'm totally following this, but i'll chime in anyways. :P
why do you need the anchor point to be set if the buttons are movable in the window? if it's purely for the ability to move children when the parent moves, i'm wondering if you can call StartMoving() on all the children when you do for the parent. does that work or does it blow up?
I guess because that'd be kind of annoying? :) Having the children anchored to the parent is supposed to automatically let you move the parent and have the children come with it seamlessly. The problem is being able to move the children in relation to the parent without breaking that functionality -- that is, being able to call :StartMoving() and then :StopMovingOrSizing() on a child, and after doing so, having the child *still* anchored to the parent.
By default, when you call :StartMoving() on any frame, it becomes unanchored to whatever you had it anchored to, which means its x,y offset is also no longer relative to the (previous) anchor frame, but instead relative to the whole screen. This is just a quirk of blizzard's :StartMoving() function and there doesn't seem to be a way to make it not do that.
So the trick is, after the child window becomes unanchored and gets a new x,y offset which is relative to the screen, how do you re-translate that offset back into being relative to the parent, so that you can re-anchor the children to the parent at the right coordinates.
I think either I'm misunderstanding your reply, or I didn't explain the problem clearly (which I have since solved, its just an annoying thing to have to hack around, and a potential gotcha for other new developers).
it should be fairly easy to just anchor them relative to the parents topleft (or any other corner if you want to make it user customisable) so it doesnt matter where the parent gets moved to as the children will stay in a relative position at all times.
Sure, of course -- this is how I set it up originally. Getting the children anchored to the parent window isn't the problem, per se. Keeping them anchored that way, while still allowing them to be dragged (within the parent, or not, doesn't matter) -- that is the problem.
you just have to have code in <OnDragStop> to record where the frames new position is when its moved, along with code to keep it whooly inside its parent frame, and every time you draw the frame (child or parent) you need to set the anchor again. it just means storing all the anchor points in your savedvariables (which youre probably doing anyway
This is not as easy as it sounds, in the case when the frame being dragged is not anchored to screen or UIParent or somesuch full-screen frame. If you re-read my original post, you'll see why. :)
In short, WoW's built-in :StartMoving() function re-anchors the affected frame to the screen and converts its x,y offset to compensate. Thus when you finish moving, the x,y from GetPoint() are no longer relative to your original anchor frame, they're relative to the screen, and you have to manually recompute what the proper offset is and reset the anchor to refer to the proper frame.
Scaling issues make this tricky to do formulaicly, so I ended up doing it by storing GetPoint()'s result at a few specific moments and measuring the delta-x and delta-y of the movement, rather than trying to make any useful sense of the final coordinates.
couldnt you just have the parent frame be moveable and anchor all the children to that (children are not moveable)
unfortunately for me the whole point is that both parent and children need to be independently movable. specifically, "parent" is a window which contains many icons (which track auras, but that's beside the point). these windows should themselves be movable, and when moved, should take their children (icons) with them. within each parent window, however, the icons should also be movable so people can rearrange their order in the window.
btw, in your example code the parent frame doesnt have an anchor, a width, or a height, doesnt that cause issues by itself when trying to anchor children to it?
the example was a stripped down excerpt from my real code, which assigns anchor/width/height elsewhere. specifically, my windows are all anchored to UIParent, and width/height depends on the number of child icon frames.
the code has evolved a bit since I posted it; for children (icons), I now track current position OnUpdate (only while moving -- the OnUpdate hook is set OnMouseDown and cleared OnMouseUp, and then only when the windows are unlocked) and pass that to the parent (window), so the parent can rearrange its other children to make room for the currently-moving icon, according to the "slot" that the user has dragged the icon closest to. seems to work well so far.
I still have some oddities to work through when repositioning parent windows, however. something about having them anchored/parented to UIParent seems to confuse my calculation, such that when the drag is released and i try to figure out the total movement and apply it to the original offset, the window ends up somewhere else on the screen. even stranger, that happens exactly twice for any single window; after two screwy drags, then it works fine. still puzzling that one out.
If you want your subframe to be draggable, but anchor to the parent, you're going to have to grab both frame's positions, clear points on the subframe, and reanchor it when the drag ends.
This is what I came up with, too. (As is the way of things, describing the problem here gave me a lead on how to solve it.)
This is my code now:
-- we have to get our current anchor point and position now, because
-- :StartMoving() will immediately re-anchor to UIParent
local framePt,_,parentPt,x,y = self:GetPoint(1)
self.framePt = framePt
self.parentPt = parentPt
self.frameX = x
self.frameY = y
-- once it has done so, we need the starting offset relative to UIParent
-- so we can determine later what the relative movement was
_,_,_,x,y = self:GetPoint(1)
self.screenX = x
self.screenY = y
-- again we need the offset first, because :StopMovingOrSizing() will
-- choose a different relative anchor point than we started with
-- (depending where on the screen we ended up), which would make it
-- harder to compare current offset to original offset
local framePt,_,parentPt,x,y = self:GetPoint(1)
-- once we have it, subtract the starting screen-offset to get the delta,
-- and then add that to the starting frame-offset to get a new frame-offset
x = (x - self.screenX) + self.frameX
y = (y - self.screenY) + self.frameY
self:SetPoint(self.framePt, self:GetParent(), self.parentPt, x, y)
-- and finally, clean up
self.framePt = nil
self.parentPt = nil
self.frameX = nil
self.screenX = nil
self.screenY = nil
Seems to work, but only because the API conveniently delays picking a new (closest) anchor point until after :StopMovingOrSizing(), which means OnMouseUp can still get a final offset to UIParent based on the same anchor point it started with. If the API ever changes to re-anchor on the fly, then this won't work anymore.
Edit: I didn't want to use OnDrag* because they have that "sticky" behavior which is annoying in this context. When I'm dragging items, sure, but I want to be able to move a window by a pixel or two to make it line up, and OnDrag* isn't precise enough for that.
Suddenly subFrame is no longer anchored to parentFrame. Its screen position appears correct, but if I move parentFrame, subFrame no longer comes with it.
Is there an easy way around this, to preserve my anchor? Or do I have to add a bunch of logic OnMouseUp to get parentFrame's coordinates and subtract them to subFrame's offset to re-anchor it to parentFrame at the right place?