My favorites | English | Sign in

Google Desktop APIs (Labs)

Resizable Desktop Gadgets

James Yum, Google Desktop Team
April 2008

Introduction

One of the joys of life is a perfect fit. Whether shoes, clothes, or chairs, the right size makes all the difference, and Desktop gadgets are no exception.

Most Desktop gadgets available today are not resizable—their UI dimensions are fixed unless the developer specifies otherwise. Luckily, the platform does its best to ensure the gadget is usable. For example, you may have a large gadget that is wider than the current Sidebar width. The Sidebar automatically scales such gadgets down so that they are displayed in their entirety. Although the majority of gadgets aren't resizable, many could benefit from taking advantage of this capability.

Two steps are needed to make your gadget resizable. The first is enabling resizing so the user can resize the gadget. When a gadget is resizable, a resize frame appears when the mouse is within the area of the gadget. The second part is writing code that reacts to size changes and updates the gadget's UI accordingly.

Benefits

There are several reasons to make your gadget resizable. First and foremost, your users will appreciate having control over how much space the gadget takes up, especially if it contains a lot of text content.

Also, when docked in the Sidebar, a gadget may scale down to the point of being unreadable (Figure 1). On the other hand, the available Sidebar area may be wider than the gadget, leaving unused space (Figure 2). Ideally, the gadget should attempt to be readable at all times and use all available space.


Figure 1: Text is unreadable


Figure 2: Wasted space

The Twitter gadget is a good example of a resizable gadget. It fits nicely in the Sidebar and can be resized when outside of it.

Resize modes

Desktop gadgets have three different resizing modes, which are specified using the view object's resizable property. This property expects one of the following strings:

  • 'true': the gadget is resizable and sends sizing events.
  • 'false': the gadget cannot be resized.
  • 'zoom': the gadget cannot be resized, but the Sidebar will zoom out (scale down) the gadget to ensure it is visible.

The default behavior is 'zoom'. The gadget is not resizable, but it may be zoomed by the Sidebar when docked.

Of course, you can also set the value at runtime from script.

if (isDocked) {
  view.resizable = 'zoom';
} else {
  view.resizable = 'true';
}

One thing to keep in mind is that the gadget could also be resized by the Sidebar. If the gadget is resizable, the Sidebar updates the width of the gadget to match the current Sidebar width.

Resize handlers

Once the gadget is set to be resizable, two handlers are notified of size changes: view.onsizing and view.onsize. onsizing fires while the gadget is being resized. event.width and event.height contain the new width and height. Within this handler, you can cancel the resizing or override the new dimensions by setting event.width and event.height yourself.

Cancelling a size change

function onSizing() {
  // Cancels the change in size.
  event.returnValue = false;
}

Overriding a size change

function onSizing() {
  // Override the change in size.
  event.width = 200;
  event.height = 200;
}

The onsize event is also provided which is triggered after the resizing is complete—view.width and view.height contain the new dimensions. This event is preferred If you don't need to cancel or override the resizing.

Example

Here's an example of a basic framed gadget. It displays what seems like nonsense.


Figure 3: You're a vegetable

main.xml is defined as follows:

<view width="200" height="140" resizable="false">
  <script src="main.js" />

  <div name="frame" background="frame.png"
       x="0" y="0"
       width="200" height="140" />

  <div name="frameMiddle" background="#DDDDFF"
       x="21" y="22"
       width="156" height="95">
    <label name="content" width="100%" height="100%" wordWrap="true"> You're a vegetable, you're a vegetable Still they hate you, you're a vegetable You're just a buffet, you're a vegetable They eat off of you, you're a vegetable
    </label>
  </div>
</view>

The problem here is the monolithic frame. For the gadget to be resizable, the frame image must be broken up into four corners and four edges (highlighted in Figure 4 below):


Figure 4: Cutting up the frame

The edges are merely slices that serve as a repeating background in a div. Now let's revise main.xml using the cut-up image set, setting its resizable property to 'true', and listening for the onsize event.

<view width="200" height="140" resizable="true" onsize="onSize();">
  <script src="main.js" />

  <div name="frameTopLeft" background="top_left.png"
       x="0" y="0"
       width="21" height="22" />
  <div name="frameTop" background="top.png"
       x="21" y="0"
       width="156" height="22" />
  <div name="frameTopRight" background="top_right.png"
       x="177" y="0"
       width="23" height="22" />
  <div name="frameLeft" background="left.png"
       x="0" y="22"
       width="21" height="95" />
  <div name="frameMiddle" background="#DDDDFF"
       x="21" y="22"
       width="156" height="95">
    <label name="content" width="100%" height="100%" wordWrap="true"> You're a vegetable, you're a vegetable Still they hate you, you're a vegetable You're just a buffet, you're a vegetable They eat off of you, you're a vegetable
    </label>
  </div>
  <div name="frameRight" background="right.png"
       x="177" y="22"
       width="23" height="95" />
  <div name="frameBottomLeft" background="bottom_left.png"
       x="0" y="117"
       width="21" height="23" />
  <div name="frameBottom" background="bottom.png"
       x="21" y="117"
       width="156" height="23" />
  <div name="frameBottomRight" background="bottom_right.png"
       x="177" y="117"
       width="23" height="23" />
</view>

Resizing the UI

Most of the actual work is done in the resize handler. The handler is notified of size changes, and then resizes and positions all of the controls and UI elements appropriately. For example, there may be a label that should be right aligned. When the gadget's size changes, the handler repositions that label to keep it flush to the right. I recommend expressing sizes in percentages rather than pixels whenever possible. Take the example of a listbox: if all of its inner items are set to 100% width, they are resized automatically when the listbox's size changes.

Continuing the example above, the resize handler does the following:

  • Determines the gadget's new width and height.
  • Positions the frame corners.
  • Sets new width/height of frame edges.
  • Resizes the text content.

As you see below, the calculations are fairly simple:

function onSize() {
  // Determine the gadget's new width and height.
  var width = view.width;
  var height = view.height;

  // No need to reposition the top left corner.

  // Top right corner.
  frameTopRight.x = width - frameTopRight.width;

  // Bottom left corner.
  frameBottomLeft.y = height - frameBottomLeft.height;

  // Bottom right corner.
  frameBottomRight.x = width - frameBottomRight.width;
  frameBottomRight.y = height - frameBottomRight.height;

  // Position and resize the edges.
  frameTop.width = width - frameTopLeft.width - frameTopRight.width;
  frameBottom.width = width - frameBottomLeft.width - frameBottomRight.width;
  frameBottom.y = height - frameBottom.height;
  frameLeft.height = height - frameTopLeft.height - frameBottomLeft.height;
  frameRight.height = height - frameTopRight.height - frameBottomRight.height;
  frameRight.x = width - frameRight.width;

  // Resize the center.
  frameMiddle.width = width - frameLeft.width - frameRight.width;
  frameMiddle.height = height - frameTop.height - frameBottom.height;
}

That's all there is to it! This amazing gadget is also available for download.

Conclusion

I hope you see that implementing resizable gadgets isn't too much of a "stretch". Do you think you can "handle" it?

Author Bio

James in bad shape

James likes riding buses, trains, and planes. He's too lazy for bikes. Walking is okay, but not first thing in the morning.