Bongo Graphics Model

The graphics model is very similar to the graphics model of the AWT. You will find that a lot of the same classes, names, and metaphors are used. However, there are some significant differences these are explained here.


Design Goals

The AWT is based on a graphics/painting model using overlapping windows. Unfortunately AWT windows are fairly heavy weight and they are always opaque. This is mostly because the AWT uses the platform's native windows.

The graphics model used here is different. It is based on widgets which are transparent areas of the screen and which are painted back-to-front order as required. All painting is done to an off-screen buffer.


The Paint Method

The paint method is called on a widget when it needs to be painted. The paint method comes in two flavors:
    public void paint(Graphics g) {
	...
    }

    public void paint(Graphics g, int x, int y, int width, int height) {
	paint(g);
    }
The first version of the paint methods is used in most cases. If you create your own widget, or if you want to define the look of an existing widget you need to override this method.

The second version of the paint method is always called first. The additional arguments defined the area of the widget (in local coordinates) which needs painting. Complex widgets should override this method and only draw the components that lie within this area to avoid doing unnecessary work. Simple widgets can simply override the first version of the paint method which is called if the second version is not overridden.

When the paint method is called the graphics context which is passed in as an argument is initialized accordingly. The translation is according to the widget's coordinate system, the clipping rect is set to the area that needs painting, the color is set the widget's foreground color, and the font is set to the widget's current font.

Here is an example of a typical paint method which draws raised oval button with a label in the center:

    import java.awt.*;
    import marimba.gui.*;

    public class RoundButtonWidget extends Widget {
        String label;
	boolean fill;
	boolean down;

	...

        public void paint(Graphics g) {
	    FontMetrics fm = g.getFontMetrics();

 	    // Draw the button
	    g.setColor(background);
	    if (fill) {
	        Bevel.fillOval(g, 0, 0, width, height, !down, 2);
	    } else {
	        Bevel.drawOval(g, 0, 0, width, height, !down, 2);
	    }

 	    // Draw the label
	    if (label != null) {
	        g.setColor(foreground);
	        int lx = (width - fm.stringWidth(label))/2;
	        int ly = (height - fm.getHeight())/2 + fm.getAscent();
	        if (down) {
		    g.drawString(label, lx+1, ly+1);
	        } else {
		    g.drawString(label, lx, ly);
	        }
	    }
	}

	...
    }


Double Buffering

Unlike the AWT all painting is done in an off-screen buffer. This avoids unnecessary flickering which may occur in complex painting methods with take a long time to complete. As a result you will notice that all graphics operations are very smooth and are performed entirely without flickering.

To avoid unnecessary use of memory only one off-screen buffer is used to paint all widgets in the system. Instead of allocating a different off-screen buffer for each window, the same off-screen buffer is used to draw into all window. This uses much less memory and does not affect the graphics model.


Transparency

A widget does not have to paint its entire area. In areas which are not painted the background of the widget will remain visible. Such a widget is called a "transparent" widget. This allows you to create great looking interfaces which have patterned backgrounds. All widgets are by default transparent.

If a widget changes, it can't immediately draw to the screen. Widgets need to be drawn in back-to-front order to maintain the correct stacking order. The underlying system takes care of painting the necessary widgets in the correct order.


Indirect Painting

Because widgets can be transparent painting needs to occur in the correct order. That means that you can't call the paint method directly. As a result it is somewhat harder to create interactive widgets. For example, when a mouse down event is sent to a button widget it can't redraw itself directly, instead it must call repaint. The call to repaint indicates to the system that the widget, or a part of the widget needs to be repainted because its state (and therefore its appearance on the screen) has changed.

The repaint method can be called on any widget. It comes in several flavors:

    // repaint the entire widget
    public void repaint();

    // repaint a portion of a widget
    public void repaint(int x, int y, int width, int height);

    // repaint a portion of a widget with a given delay
    public void repaint(long delay, int x, int y, int width, int height);
Each of these version of repaint will eventually result in a call to paint unless the widget is not currently visible.

The last version of repaint allows you to specify a delay in milli-seconds. This delay indicates to the system how long the subsequent paint event can be delayed. This version of repaint should be used when repaint is called very frequently (line in an animation).

After calling repaint the system will automatically call the widget's paint method. Not every call to repaint will result in a corresponding call to paint. It the system is very busy it is allowed to collapse multiple calls to repaint into a single call to paint.

Here is an example of how repaint is used in the handleEvent method of the OvalButtonWidget class:

    import java.awt.*;
    import marimba.gui.*;

    public class RoundButtonWidget extends Widget

	...

        public boolean handleEvent(Event evt) {
 	    switch (evt.id) {
	      case Event.MOUSE_DOWN:
 		down = true;
		repaint();
		return true;

	      case Event.MOUSE_UP:
 		down = false;
		repaint();
		return true;
	    }
	    return super.handleEvent(evt);
	}
    }
The paint method is called automatically after calling repaint. All widgets that occupy the same area of the screen as this button widget will be redrawn in the correct order. This way the button will redraw correctly even if there are other widgets above and below it.

The source code for this example is included in the release. It is located in the demo/programming directory.


Opaque Widgets

Some widgets may set their transparent flag to false. This indicates that the widget is responsible for drawing the entire area of the widget. Thiw allows the system to perform certain optimizations, and can avoid the unnecessary painting of widgets which are not visible.


Visible Widgets

Each widget has a visible flag which is used to indicate whether the widget should be drawn or not. A widget is only painted if the widget is visible, and if all its parents are visible.


The Update Method

The update method is called in some special cases where painting can be optimized. It is called only on opaque widgets which are not overlapped by other widgets. The default implementation of the update method invokes the paint method. This method is used only in cases where graphics performance is critical (such as a multi-font text editor).


AWT widgets

The toolkit allows you to mix AWT components and widgets in the same container hierarchy. Actually, in that case AWT widgets are treated specially. Because AWT components can not be transparent they are always painted on top all other widgets.

AWT component support is provided for backward compatibility with existing widgets and applets. Mixing AWT components and widgets on a regular basis is not recommended.