Main Page | Namespace List | Class Hierarchy | Class List | File List | Namespace Members | Class Members | Related Pages

Coder's guide - creating WFTK widgets

written by Ron Steinke, March 7 2003

It's fairly easy to create new widgets under wftk. All it takes is an understanding of the way the drawing algorithm works. Drawing is implemented by a number of virtual functions in the ScreenArea class. The most relevant functions are:

class ScreenArea
{
  ...

 protected:

  /// Invalidate part of the widget to force a redraw
  void invalidate(const Region&);
  /// Invalidate the whole widget to force a redraw
  void invalidate();

  /// The widget's drawing implementation.
  virtual void draw(Surface&, const Region&, const Point& offset);

  ...
};

The drawing algorithm is modeled after X11, with the SDL window being treated as a stateless root window. This means that you cannot simply draw your widget whenever you feel like it. Instead, you inform wftk that part of your widget needs to be redrawn, via a call to invalidate(). Then, after determining what part of your widget actually needs to be drawn (accounting for overlap by other widgets, dialogs, etc.), it calls draw(), and you actually do your drawing.

A call to invalidate() informs wftk that part or all of your widget has changed and needs to be redrawn. The optional Region argument is taken relative to the origin of your widget, so the default case of invalidate() is equivalent to passing Rect(0, 0, width(), height()) as the region. Being careful to only invalidate the minimum necessary Region can greatly increase performance.

At the end of each mainloop cycle, wftk calls RootWindow::sync(), which calls the draw() functions of all widgets which need to be redrawn. The first argument passed to draw() is a reference to the screen Surface. The second is the Region which needs to be redrawn, which is again given relative to the origin of the drawing widget. The third is the offset from the origin of the root window to the origin of the widget. Only draw the portions of your widget specified by the Region you are passed! There are region-masked blit functions in wftk::Surface to help with this.

There is also a drawAfter() virtual function, with a signature identical to draw(). This function allows widgets to draw after their children. It is occasionally useful (e.g., Widget uses it to grey out itself and all its children when it is disabled), but will not be needed by most users.

The wftk library also allows partially or fully transparent widgets. Most users will implement transparent widgets by overriding Widget::isOpaque(). A ScreenArea is transparent by default, and the opacity of a Widget defaults to the opacity of its background. Widget::isOpaque() should only return true if your widget is opaque at every point in its allocated rectangle. For shaped or partially transparent widgets, we turn to the lower level functionality in ScreenArea:

class ScreenArea
{
 public:

  /// return geometry
  const Rect& getRect() const {return rect_;}
  /// return geometry for shaped widgets
  const Region& getShape() const {return shape_;}
  /// return opaque subarea for partially transparent widgets
  const Region& getCoverage() const {return covered_;}

  ...

 protected:

  /// Set the shape of the widget
  void setShape(const Region& shape, const Region& coverage);
  /// Set the area the widget covers opaquely
  void setCoverage(const Region& coverage) {setShape(shape_, coverage);}

  /// Calculate new shape for shaped widgets, call to setShape() after a resize
  virtual void handleResize(Uint16 w, Uint16 h);

  ...
};

The basic position and size of a widget is determined by its allocated screen section, accessible by getRect(). One important thing to note is that getRect() is given in coordinates relative to the widget's parent. Thus, the basic widget shape (in the widget's own coordinates) is Rect(0, 0, getRect().w, getRect().h).

The actual shape of a widget is given by getShape(). For most widgets, this will be equal to Rect(0, 0, getRect().w, getRect().h), but it can be set to any subset of this rectangle. This shape is important for things like determining which widgets receive mouse clicks.

The area of a widget which is considered to be opaque is determined by getCoverage(). The default value for this is either the whole widget or nothing, depending on the return value of Widget::isOpaque(). It is this value that figures in the drawing algorithm, since any widgets or portions of widgets which lie behind this opaque region will not be drawn.

The shape and coverage of a widget can be set by a call to setShape(). This function is generally called in a widget's constructor, and in handleResize() to give the new shape for a resized widget. It can, however, be called at any time, unlike draw().

A widget's prefered size is specified in its setPackingInfo() virtual function. This is used to set the protected packing_info_ structure, which is of type ScreenArea::PackingInfo:

class ScreenArea
{
 public:

  struct PackingInfo {
    struct Expander {
      Uint16 pref;
      Uint16 min;
      bool expand;
      unsigned char filler;
    } x, y;
  }
};

The 'pref' field gives the prefered size of the widget in each dimension, and 'min' gives the minimum size. You should always set pref >= min. The 'expand' field tells whether the widget is allowed to expand beyond its prefered size. Thus, to get a fixed size widget, set pref == min, and expand == false. The 'filler' field gives the filler rank of this widget. If expand is false, 'filler' is ignored. Any excess space in a container goes to the widget with the highest filler rank, or is split between them if there are more than one widget at the highest rank. For nonzero filler rank, the excess space is split evenly, while for zero filler rank ('default expand') it is weighted by the prefered size of the widget. If there isn't enough space, space is removed from widgets as weighted by (pref - min).

A container widget is somewhat more complicated, as it needs to combine its children's PackingInfo into a single set of PackingInfo for the container. They it needs to resize() its children in its handleResize() functions, after it sets its shape using setShape().

See Also
Documentation for wftk::ScreenArea, wftk::Region, wftk::Rect, wftk::Widget

Back to Related Pages


Generated Wed Jul 28 17:28:43 2004.
Copyright © 1998-2003 by the respective authors.

This document is licensed under the terms of the GNU Free Documentation License and may be freely distributed under the conditions given by this license.