"Give me a place to stand on, and i will move the Earth!" - Archimedes
In the physical world, we interact with all kinds of objects every day, basing this interaction mostly on our eyesight. We think we see 3D stuff, but actually a 2D image is projected on our retina, only to be processed later into a spatial perspective. That's why when we see a drawing of a cube on paper, although the paper is 2D, we immediately think of the 3D object.
This article is about rotating elements in a gadget. I hope you find these tips helpful; links to source code of all the examples discussed here can be found at the end of this article.
A gadget can be very similar to a sheet of paper on which the developer is drawing his UI (User Interface) elements. Mathematically, there are many ways of locating a point in a plane, but one of them which is relevant to this article is the polar system: an axis, an angle and a length. When you place an <img> object in your gadget the position, height and width of the image are automatically determined (you can change them explicitly as well).
What we're interested in is the angle, which has a default value of 0 - no rotation. A positive value will turn the image clockwise around its axis (the upper-left corner), and a negative value will turn it counter-clockwise. An example is shown in Figure 1.
When we add an <img> object, we usually specify at least two things: the position and the image source. So, it's only natural that the specified coordinates will become the axis. As i've said in the previous paragraphs, this means that each rotation will take place around the upper-left corner of the image, like in Figure 2.
It's important to know that we can do this not only for images, but other types of elements as well, the rotation property being inherited as part of the basicElement.
I said that the upper-right corner is the default axis when the only things we have are the x and y coordinates. But we can change the rotation axis by giving a certain value to the pinX and pinY properties, which are also inherited by every element. That way we can achieve certain effects like clock hand movement or rotating status indicators very easily (Figure 5 and Figure 7). When pinX and pinY are not set, their value is considered zero, so the axis will naturally become the point located at the (0,0) position.
But how do we actually rotate objects? The answer comes in the next section of the article.
Basically, the rotation property of an element can be changed both in the XML and the Javascript code. An initial angle is usually set in the XML (if it's different from zero), and then, if the gadget purpose requires it, the rotation can be handled dynamically in the Javascript code, like below. This example simulates in a very simple way the behavior of a tachometer - you can see it in cars, measuring engine RPM :)
//in main.xml <img name="needle" src="needle.png" x="67" y="67" rotation="135" /> //since the needle is horizontal at first, //and positioned in the center of the gauge, //we need to put it into the right position
//in main.js
function accelerate(){
needle.rotation+=1;
//we can also set a limit
if (needle.rotation==315){
needle.rotation=135;
alert("Engine overheated!");
}
}
function decelerate(){
if (needle.rotation>135)
needle.rotation-=1;
}
Notice how in this example we're taking advantage of the fact that rotation is a read/write property. This allows us to relatively increase or decrease it, without necessarily having to know its value at a certain moment.
Some possible uses are the following 3, although many more can be found since they are very flexible and easy to implement:
The lines i used to draw the graph you see in Figure 3 are actually <div> objects, and they were added using the following snippet of code:
for (i=0;i>dots.length-1;i++){
lines[i]=chart.appendElement("<div height='2' background='#888888'/>");
lines[i].x=(dots[i].x-minXValue)*xUnit+2;lines[i].y=height-(dots[i].y-minValue)*yUnit-2;
lines[i].rotation=-Math.atan((dots[i+1].y-dots[i].y)*yUnit/(dots[i+1].x-dots[i].x)/xUnit)*180/3.1415;
lines[i].width=Math.sqrt(sqr((dots[i+1].y-dots[i].y)*yUnit)+sqr((dots[i+1].x-dots[i].x)*xUnit));
}
So every <div> is accessible from an element from the lines array, and, for the sake of readability, the important coordinates are set on different lines of code. To match the chart lines perfectly, coordinates like width and rotation are set using mathematical formulas.
We see this all time, in our browsers, media players and other common programs: opposite functions which are symbolized by mirror images (Figure 4). So, for example, a good way to minimize the gadget build size is to have 2 <buttons> with the same image, one of them having a rotation of 180 (degrees). Another way to implement this optimization occurs almost as often: extend/collapse buttons, as indicated in Figure 4. This time it seems intuitive to have a single button, since the same action can't happen twice in a row, so we can just change the rotation (and position) to suggest the opposite of the last triggered action. Here's how i did it in the presented gadget:
function setUpTheDisplay(){
//...
theButton.x=93+options("ShowNews")*153;
theButton.y=(1-options("ShowNews"))*42;
theButton.rotation=180*(1-options("ShowNews"));
}
This is why it's easier to have one button in this case: the behavior is the same, just some variables may differ; so it may sometimes help to try to spot behavioral patterns and define them based on those variables.
The best example is a clock, which measures the only variable which always increases. This is where we also got the clockwise expression from, because of the way in which the hands always turn. The only problem is how to associate an angle with clock value, and it turns out that it's very easy to solve. Every time we have to find such an association we have to see the corresponding values for one of the zero values. If we choose the angle, we see that a horizontal hand with a rotation set to zero points towards 3. This means that angle=(clockValue*5-3*5)*6, where 5 is the number of divisions between two values and 6=360 degrees/60 divisions. This is how trigonometry blends with the real life :)
//in main.xml ... <img name="HourHand" pinX="3" pinY="27" src="hourhand.png" x="52" y="52"/> <img name="MinuteHand" pinX="3" pinY="40" src="minhand.png" x="52" y="52"/> <img name="SecondHand" pinX="2" piny="40" src="secondhand.png" x="52" y="52"/> ...
This time we can set a pin (axis) of our choice, to ensure a propper, realistic rotation.
We can use rotation not only to improve functionality, but looks as well:
An appealing interface determines users to keep your gadget, so why not include some visual effects? As shown in Figure 6, you can easily create some cool effects for events like onmouseover, onmouseout, etc.
When your gadget does something which takes some time, you can communicate this to the user by showing an animated status indicator, which can be as simple as a rotating dot.
Notice that these are actually the same images, just rotated to create a nice and suggestive animation.
In this article i tried to show you how rotating objects can improve not only the interface look, but the functionality behind it as well, also bringing an opportunity to optimize your code in certain situations. As described, to emphasize change in a value, an analog indicator which rotates to point to it can sometimes be a lot better than a digital display, even in the 3rd millennium :)
Many gadgets, everyone!
I'm a natural born programmer. My first contact with a techno-gadget was Star Trek (remember those cool sensors?). I used to fill up a whole room with drawings of them when I was only 3 years old. Generally, I find a lot of inspiration for intuitive interfaces in things with "star" in their names (like Star Wars, Stargate,... :-) . I like the border between interface and function—it's the essence of a program, I think. Anyway, I'm a software engineering student now, and you can learn more about me and what I'm thinking and doing at my website or blog.