      /**
       * A custom animation that moves a small image around a circle in an
       * {@link AbsolutePanel}.
       */
      public class CustomAnimation extends Animation {
        /**
         * The x-coordinate of the center of the circle.
         */
        private int centerX = 120;

        /**
         * The y-coordinate of the center of the circle.
         */
        private int centerY = 120;

        /**
         * The radius of the circle.
         */
        private int radius = 100;

        @Override
        protected void onComplete() {
          super.onComplete();
          startButton.setEnabled(true);
          cancelButton.setEnabled(false);
        }

        @Override
        protected void onStart() {
          super.onStart();
          startButton.setEnabled(false);
          cancelButton.setEnabled(true);
        }

        @Override
        protected void onUpdate(double progress) {
          double radian = 2 * Math.PI * progress;
          updatePosition(animateeLeft, radian, 0);
          updatePosition(animateeBottom, radian, 0.5 * Math.PI);
          updatePosition(animateeRight, radian, Math.PI);
          updatePosition(animateeTop, radian, 1.5 * Math.PI);
        }

        /**
         * Update the position of the widget, adding a rotational offset.
         *
         * @param w the widget to move
         * @param radian the progress in radian
         * @param offset the offset in radian
         */
        private void updatePosition(Widget w, double radian, double offset) {
          radian += offset;
          double x = radius * Math.cos(radian) + centerX;
          double y = radius * Math.sin(radian) + centerY;
          absolutePanel.setWidgetPosition(w, (int) x, (int) y);
        }
      }

      /**
       * The constants used in this Content Widget.
       */
      public static interface CwConstants extends Constants,
          ContentWidget.CwConstants {
        @DefaultStringValue("Cancel")
        String cwAnimationCancel();

        String cwAnimationDescription();

        String cwAnimationName();

        @DefaultStringValue("Animation Options")
        String cwAnimationOptions();

        @DefaultStringValue("Start")
        String cwAnimationStart();
      }

      /**
       * The absolute panel used in the example.
       */
      private AbsolutePanel absolutePanel = null;

      /**
       * The widget that is being animated.
       */
      private Widget animateeBottom = null;

      /**
       * The widget that is being animated.
       */
      private Widget animateeLeft = null;

      /**
       * The widget that is being animated.
       */
      private Widget animateeRight = null;

      /**
       * The widget that is being animated.
       */
      private Widget animateeTop = null;

      /**
       * The instance of an animation.
       */
      private CustomAnimation animation = null;

      /**
       * The {@link Button} used to cancel the {@link Animation}.
       */
      private Button cancelButton = null;

      /**
       * An instance of the constants.
       */
      private CwConstants constants;

      /**
       * The {@link Button} used to start the {@link Animation}.
       */
      private Button startButton = null;

      /**
       * Initialize this example.
       */
      @Override
      public Widget onInitialize() {
        // Create a new panel
        absolutePanel = new AbsolutePanel();
        absolutePanel.setSize("250px", "250px");
        absolutePanel.ensureDebugId("cwAbsolutePanel");

        // Add a widget that we will animate
        animateeTop = Showcase.images.gwtLogoThumb().createImage();
        animateeBottom = Showcase.images.gwtLogoThumb().createImage();
        animateeLeft = Showcase.images.gwtLogoThumb().createImage();
        animateeRight = Showcase.images.gwtLogoThumb().createImage();
        absolutePanel.add(animateeTop);
        absolutePanel.add(animateeBottom);
        absolutePanel.add(animateeLeft);
        absolutePanel.add(animateeRight);

        // Wrap the absolute panel in a DecoratorPanel
        DecoratorPanel absolutePanelWrapper = new DecoratorPanel();
        absolutePanelWrapper.setWidget(absolutePanel);

        // Create the options bar
        DecoratorPanel optionsWrapper = new DecoratorPanel();
        optionsWrapper.setWidget(createOptionsBar());

        // Add the components to a panel and return it
        HorizontalPanel mainLayout = new HorizontalPanel();
        mainLayout.setSpacing(10);
        mainLayout.add(optionsWrapper);
        mainLayout.add(absolutePanelWrapper);

        // Create the custom animation
        animation = new CustomAnimation();

        // Set the start position of the widgets
        animation.onComplete();

        // Return the layout
        return mainLayout;
      }

      /**
       * Create an options panel that allows users to select a widget and reposition
       * it.
       *
       * @return the new options panel
       */
      private Widget createOptionsBar() {
        // Create a panel to move components around
        VerticalPanel optionsBar = new VerticalPanel();
        optionsBar.setSpacing(5);
        optionsBar.setHorizontalAlignment(HasHorizontalAlignment.ALIGN_CENTER);

        // Add a title
        optionsBar.add(new HTML("<b>" + constants.cwAnimationOptions() + "</b>"));

        // Add start button
        startButton = new Button(constants.cwAnimationStart());
        startButton.addClickListener(new ClickListener() {
          public void onClick(Widget sender) {
            animation.run(2000);
          }
        });
        optionsBar.add(startButton);

        // Add cancel button
        cancelButton = new Button(constants.cwAnimationCancel());
        cancelButton.addClickListener(new ClickListener() {
          public void onClick(Widget sender) {
            animation.cancel();
          }
        });
        optionsBar.add(cancelButton);

        // Return the options bar
        return optionsBar;
      }