Always start with the good news: The good news is that the Manipulation events in the July beta of the Windows Phone 7 development tools no longer have the orientation problem I discussed in my blog entry Basic Manipulation Event Handling in Windows Phone 7 (at least with translation). You can flip the phone emulator sideways and translation coordinates don't need to be finagled. More good news: I am now able to get the emulator to recognize two fingers on my two touch screens, so I've been able to experiment with scaling as well as translation.
Other than that, I'm still not seeing behavior that feels correct to me, based both on my familiarity with the Manipulation events implemented in the Windows Presentation Foundation, and the degree of work necessary to get meaningful information from the WP7 implementations.
A little background: When two fingers are used to increase the size of an object (an operation known as scaling), it makes a difference which fingers actually move. For example, suppose a photo starts out at 100 pixels square. It's sitting on the display so that the upper-left corner is at the point (100, 100) and the lower-right corner is at (200, 200). Here's the photo outlined in blue:
Thoughout this discussion I'll use equal X and Y coordinates to make it easy as possible. The red dots show the positions of your fingers on the screen, at the points (25, 25) and (75, 75) relative to the photo, or (125, 125) and (175, 175) relative to the screen. The fingers are 50 pixels apart in both the horizontal and vertical directions. You then move the lower-right finger 50 pixels to the right and down:
The resized photo is shown in green. You're doubling the distance between your fingers, which means the photo scales up in size by a factor of 2. The photo is now 200 pixels square. However, your fingers remain in the same locations relative to the photo. The finger originally at (25, 25) relative to the photo is now at (50, 50) because the photo has doubled in size. The finger originally at (75, 75) is now at (150, 150).
Alternatively, you could have moved the other finger 50 pixels left and 50 pixels up:
Once again the photo doubles in size but it ends up at a different location because the bottom-right finger has remained stationary. Or you could move both fingers outward by 25 pixels each:
There are many different variations. The two fingers could actually move in the same direction but one finger can move further than the other, which would result in scaling as well as displacement.
In graphics programming, scaling is always performed relative to a center. The center is the point on the figure that remains in a fixed location. In the first two examples I showed, the center is one of the two fingers. In the third example, it's the point midway between the fingers.
In the Manipulation events (at least when implemented in WPF), the scaling center is readily available as the ManipulationOrigin property of ManipulationDeltaEventArgs. In WPF, the Manipulation events occur relative to a container, so the ManipulationOrigin is a point within that container. In the first example, WPF would report the ManipulationOrigin as (125, 125) — the location of the upper-left finger relative to the display surface. In the second example, the ManipulationOrigin would be (175, 175), and in the third, (150, 150), the point midway between the two fingers.
Of course, these are very simplified examples. In the general case, the ManipulationOrigin would change with every ManipulationDelta event as the fingers moved relative to each other. But this ManipulationOrigin is extremely convenient, for it can be simply passed to the ScaleAt and RotateAt methods of the Matrix structure to indicate the center of rotation for each incremental change.
In the July beta of Windows Phone 7, however, ManipulationOrigin is not the scaling center. Instead, it's the midpoint of the two fingers relative to the manipulated element. In the first example shown above, one finger is at the point (25, 25) relative to the photo, and the second finger is at (75, 75). When the second finger moves, it ends up at (150, 150) relative to the photo, while the first finger ends up at (50, 50). Throughout this process, the ManipulationOrigin will have changed from (50, 50) to (100, 100). But that's not the scaling center.
After several confusing hours attempting to log and interpret Manipulation events from the Windows Phone 7 emulator, and then sketching out on paper what I thought was happening, I saw a way to derive the scaling center from the information in the events. It's only necessary to save each ManipulationOrigin for the next ManipulationDelta event. In the following formulas, C is the scaling center — the number we need to derive. S is the delta scaling factor reported in the ManipulationDelta event. O is the ManipulationOrigin value and Oprevious is the ManipulationOrigin value from the previous ManipulationDelta event. (Obviously each of these has X and Y components but each dimension is handled the same.)
During an incremental change in finger location, all points on the photo except C change location. S indicates the degree to which the photo is changing size, but every pixel in the photo is changing location based on its distance from C. For any particular point P,
S (Pprevious – C) = (P – C)
This holds for O and Oprevious as well, so:
S (Oprevious – C) = (O – C)
And just solve for C:
C = (S Oprevious – O) / (S – 1)
which will not be valid for S = 1 but for that case C is irrelevant.
And unless I'm missing something here, it's necessary to use this magic formula to make the tiniest baby steps in interpreting the Manipulation events in the July beta of Windows Phone 7 for scaling as well as translation.
Do I believe that the intention was for the Manipulation events to be used in this way? No, I do not. But at this point I'm just trying to get things to work. That's the purpose of betas.
If you'd like to try it out, here's the source code. Performance is a little flaky, I've found. It helps when experimenting with two fingers to keep them somewhat separated in both the horizontal and vertical dimensions. There's no protection against scaling factors that get very close to zero and result in the element disappearing from view.
Also, you'll notice if you're moving a rectangle with one finger, and then you try to move another with another finger, the system will think you want to scale the first rectangle. That's clearly wrong.
Like everyone else, I'm excited about the prospect of trying out some of this code on an actual device. You'll probably know when I'm able to do that: My blog will suddenly get very quiet while I'm spending my days fiendishly coding and writing my book.