My favorites | English | Sign in

Google Desktop APIs (Labs)

The Many Faces of a Desktop Gadget

James Yum, Google Desktop Team
April 2007

Are you still writing gadgets that look the same whether they're inside or outside the Sidebar?

Introduction

A cool feature of Google Desktop is the ability to display gadgets outside of the Sidebar (undocked). Gadgets can also be collapsed (minimized) to conserve space. A good gadget adjusts its UI to its current display state.

This article will show you how to:

  • Listen for and handle display state changes
  • Write clean and maintainable UI code
  • Retrieve machine memory statistics with the gadget API

Gadget Display States

The Google Desktop Weather Gadget changes its UI depending whether it's undocked, docked or minimized.

Undocked

Docked

Minimized

Undocked Gadget Docked Gadget Minimized Gadget

The undocked, expanded version is the largest and flashiest. The graphics are high-quality and distinctive, ensuring the gadget is recognizable amidst other objects on the desktop.

The docked gadget has less space to work with, so graphics are smaller and some information is sacrificed.

Gadgets can be collapsed, or minimized, to the size of a title bar. In this state, users will appreciate any useful information you can provide within the confines of the title bar.

To summarize, here are some basic guidelines to follow for the different display states:

  • Undocked - Be creative and make your gadget stand out. You don't have to worry about taking up Sidebar real estate, so feel free to present more detailed information.
  • Docked - Try your best to conserve space. Your neighbors are counting on you.
  • Minimized - Display meaningful information in the title bar. Rotate or refresh new information periodically.

Memory Monitor Gadget

Now let's see these topics in action. We'll be creating a gadget that monitors the system memory. The API offers methods to retrieve memory statistics under framework.system.memory.

Here are the specifications for the gadget:

  • Query for memory statistics and refresh the gadget every 30 seconds.
  • When the gadget is docked, draw a percentage meter based on memory in use. Use a transparent background (because it's cool).
  • If undocked, draw the same percentage meter, but also add a background image and display the current memory usage in MB.

State Change Handlers

Let's set up handlers that will be notified of state change events:

// Called when gadget is first opened.
function onOpen() {
  ...

  // Assume the gadget is docked at first.
  // The onDisplayTargetChange handler will be notified if it is in fact
  // undocked.
  isDocked = true;

  // Handle notifications sent when gadget is docked or undocked.
  pluginHelper.onDisplayTargetChange = onDisplayTargetChange;

  // Handle notifications sent when gadget is minimized. 
  view.onminimize = onMinimize;
  // Handle notifications sent when gadget is restored (maximized)
  view.onrestore = onRestore; 

  ...
}

Let's look at the events used by the gadget:

  • pluginHelper.onDisplayStateChange fires just before the gadget's display location changes, such as from the Sidebar (docked) to a floating desktop window (undocked). The handler has a single argument which specifies the new location.
  • view.onminimize fires when the gadget is minimized.
  • view.onrestore fires when the gadget is restored from the minimized state.

The gadget keeps track of the current gadget state in two variables: isDocked and isMinimized.

The handlers simply update those two variables accordingly. We resist the urge to add UI-related code here, since that code should be contained in a separate drawing function.

NOTE: During gadget startup, pluginHelper.onDisplayStateChange does not fire if the gadget is docked. However, it does fire if the gadget starts up undocked. That is why isDocked is initially set to true in onOpen.

Here is the code for the handlers:

function onDisplayTargetChange(displayTarget) {
  // Find out the new display mode
  if (displayTarget == gddTargetSidebar) {
    debug.trace('Display target changed to docked');
    isDocked = true;
    refresh();
  } else if (displayTarget == gddTargetFloatingView) {
    debug.trace('Display target changed to undocked');
    isDocked = false;
    refresh();
  }
}

function onMinimize() {
  debug.trace('Minimized');
  isMinimized = true;
  refresh();
}

function onRestore() {
  debug.trace('Restored');
  isMinimized = false;
  refresh();
}

Graphics

Here is the view.xml that defines the UI:

<view height="40" width="200" onopen="onOpen()">
  <div name="backgroundPane" width="100%" height="100%" background="#EEEEEE">
    <div name="meterPane" height="20" width="180" x="9" y="13">
      <div height="20" name="meterOff" width="180" background="#000000" />
      <div height="20" name="meterFull" width="180" background="#0000FF" />
      <label name="percentLabel" height="20" x="50" width="80" align="center" vAlign="middle" color="#FFFFFF" bold="true" />
    </div>
    <div name="statusArea" height="20" width="100%" x="10" y="40">
       <label name="statusLabel" height="20" width="100%" />
    </div>
  </div>   
  <script src="main.js" />
</view>

The width of meterFull reflects the percentage of memory in use. The statusArea displays the current memory usage in MB and only appears when the gadget is undocked.

We now draw the gadget within a single function. In many cases, it is easier to centralize all drawing-related code as we've done here. That way the drawing logic is easier to understand.

// Draw the gadget.
function draw(memoryData) {
  var formattedPercentage = (memoryData['usedPercentage'] * 100).toFixed(2);
  
  if (isMinimized) {
    // The gadget is minimized.
    // Write status to the caption.
    var caption = '';
    caption += formattedPercentage + '% ' + strings.MEMORY_USE; 
    // Set the caption (title bar).
    view.caption = caption;

    // Nothing else to do, return.
    return;
  } else {
    // The gadget is not minimized..
    // Set to normal caption.
    view.caption = strings.GADGET_NAME;
  }

  // Expand the width to reflect percentage of memory used.
  meterFull.width = memoryData['usedPercentage'] * METER_WIDTH;
  
  // Write the percentage status.
  percentLabel.innerText = formattedPercentage + '%';

  // Special handling for when the gadget is docked or undocked.
  if (isDocked) {
    // Remove background.
    backgroundPane.background = '';
    
    // Set appropriate height.
    view.height = DOCKED_HEIGHT;

    // Hide the status area.
    statusArea.visible = false;    
  } else {
    // Show the background image.
    backgroundPane.background = 'background.png';

    // Set appropriate height.
    view.height = UNDOCKED_HEIGHT;

    // Show the status area.
    statusArea.visible = true;

    // Update the status area.
    var status = convertToMB(memoryData['used']) + '/' + 
        convertToMB(memoryData['total']) + ' MB '  + strings.MEMORY_USE;    
    statusLabel.innerText = status;    
  }
}

And now the finished product...

Undocked

Docked

Minimized

Docked Memory Gadget

Conclusion

I hope that you consider these things the next time you write a gadget. At the very least, remember to take full advantage of the title bar.

If you want to run the gadget or view the source, you can download it here.

Thanks for reading, and have fun!

Resources