Have nothing to do atm so might as well describe the system I'm using for the docking library I'm working on.
Basically, there's 2 parts: The containers and the docking mechanism.
-
The containers form a simple tree. Each container has references to two other containers, and the default container has either two containers immediately below it, or it has none. There's also a "side" for the containers, either top and bottom, or left and right, and of course, center (which is that container itself).
Whenever panels (or anything, really) are added, it's added to the container by default (fill), or you can choose to add it to a particular side (i.e. left, right, top, bottom). If you add it to the left side, then - and here's the important bit - it creates two new containers, and moves all its contents to the opposite side of the container you want to add to (e.g. if you want to add a panel to the left side, it creates two containers and shifts everything into the right side, including the containers below it, etc.).
If there's already a side, say left, then the side you want to add it to is searched. If it exists, then it's added there; otherwise, it's again created and the contents shifted again, but this time the side is that of the complement of the new one supplied. For example, suppose there's a container with side LEFT (and implicitly, RIGHT) and if you want to add anything to the left or the right side, then it will do so. However, if you want to add something to the bottom side, it'll create a new pair of containers, top and bottom sides, shift everything to the top, and add whatever you want to add to the bottom.
In short, it's a basic binary tree. I was considering using a quadtree earlier (where there was a distinct left, top, right and bottom for each) but I came across issues with resizing: which one took priority first? Now it's just based on the order in which they were added, and it looks cleaner too. This has its problems, but there's ways to work around it.
-
There's two parts to docking: 1) finding the mouse's position on the screen (not just within the bounds of the program) and 2) finding out what the mouse is currently under.
As it turns out, the NativeDragEvent and NativeDragManager handle both those tasks!
While dragging, the stage coordinates are used to find the position of the mouse (this works even when dragging outside the bounds of the window) and it's converted to screen coordinates using the nativeWindow.globalToScreen function. Then, while the mouse is over a container (by using a NativeDragOver event) it'll show where to dock, and if the user drops it on a given side then it'll auto-dispatch a NativeDragDrop event. This is then used to check whether the user has dropped it on a container within a window, or not (the action associated with the drag is null, if not dropped over a container) and from there, the window for that panel's container is displayed at that location.
N.B.: Each panel has an associated container that's "parked" in a window of its own. Whenever a panel is to be docked, it's moved there and that window displayed. Other panels can also be docked in those containers, and when moving and merging containers, the contents of the containers are moved, not the containers themselves, so the containers of the panels remain parked throughout the lifetime of the program. New containers are created whenever a panel is nested deeper and deeper, rather than moving their parked containers, because it's easier to handle.
If anyone has any questions or suggestions, feel free to ask/tell. I'm currently stuck on a problem involving the sizing of the containers (when one container has one side with a panel, and the other side with multiple layers of containers, all empty save the last one, what happens? How would the root-level container "know" that there's empty space everywhere except the deepest level, and the other side of the root level?)