[Previous] [Contents] [Index] [Next]

PtGraphic

Common resources for graphical widgets

Class hierarchy:

PtWidget --> PtBasic --> PtGraphic

For more information, see the diagram of the widget hierarchy.

PhAB icon:

None - not normally instantiated.

Public header:

<photon/PtGraphic.h>

Description:

The PtGraphic superclass provides the common resources used by all graphic widgets. Graphical widgets provide attributes for color, fills, patterns, line thickness, joins, and much more.

When you want to incorporate simple, static drawings in your interface, use the widgets subclassed to PtGraphic.


Note: Don't call the Pg... drawing primitives directly, as they won't be redrawn when widgets are damaged and repaired. If you need to draw something that can't be done with these widgets, do your drawing inside a PtRaw widget. For more information, see the Raw Drawing and Animation chapter of the Photon Programmer's Guide.

Each graphical widget draws a single graphical primitive. The provided primitives are:

You can build up a drawing by creating one widget for each of the graphical primitives you need. You should create the widgets from back to front, so that their stacking order is correct and they consequently appear correct when drawn to the screen. You should also place the widgets in a container widget (such as PtPane).

Creating vector graphics

All the vector graphics widgets are defined by an origin and a set of coordinates.

The origin is an offset from the graphic widget's origin, which is used as the origin of the coordinate space for the graphics primitive.

The set of coordinates is implemented as an array of PhPoint_t structures. Each coordinate specifies a vertex or control point of the primitive. Here's a code fragment to illustrate:

PhPoint_t points[]=
    { { 0, 0},
      {40,40},
      {40, 5},
      { 0, 0}};
PtSetArg(&argt[0], Pt_ARG_POINTS, points, 4 ) ;
PtCreateWidget( PtPolygon, parent, 1, argt ) ;

Each of the coordinates defining the primitive are relative to the graphics widget's origin.

The graphics widget's origin is specified by the Pt_ARG_ORIGIN resource. This resource takes a PhPoint_t as a value. This point is the origin of the primitive's coordinate space, in pixels, relative to the widget's origin.

The set of points is specified using the array resource Pt_ARG_POINTS. The number of points required in the array - and the interpretation of those points - depends on the type of graphics primitive being defined.

When drawing the points for the primitive or the curve, the widget uses its associated line drawing attributes to determine the color and drawing style to use. These line drawing attributes are specified using the following resources defined on the PtGraphic widget class:

Pt_ARG_LINE_WIDTH
The curve's width, in pixels.
Pt_ARG_LINE_CAP
The cap style for capping off the curve's start and end points.
Pt_ARG_LINE_JOIN
The join style for connecting any intermediate vertices in the curve.

For explanations of the values you may specify for these resources, see the documentation for PgSetStrokeCap() and PgSetStrokeJoin().

Most of the graphics primitives can also be configured to draw a closed curve. When this is done, the interior of the primitive may also be filled using the fill color and style associated with the widget to determine. These fill attributes are specified using the following resources defined by the PtBasic widget class:

Pt_ARG_FILL_COLOR
Color to use to fill the interior.
Pt_ARG_FILL_PATTERN
Stipple pattern to use to fill the interior.

Creating a drawing

To create a drawing composed of several graphical widgets, you should first create a container-class widget to place the widgets in. Normally, you set the border width and margins of the container to zero.

At this point, you may create the graphics primitives and position them within the container widget. You may choose to position and size the graphics widgets in one of several ways, but the simplest is to place them all at position (0,0), which is adequate for most purposes.

The origin resource, Pt_ARG_ORIGIN, provides a reference point or datum for all the primitives added to the container-class widget. This resource defines a coordinate space for each graphic allowing maximum flexibility for positioning and sizing primitives.

For example, the origin lets you create a library of symbols defined in their own coordinate space. You can then use the origin to place the symbol anywhere in the drawing, and the widget itself doesn't need to be positioned. The only thing you have to do then is scale the symbol itself.

Sizing the primitives

If you know the overall dimensions of the drawing, you may want to explicitly set the dimensions of the graphics widgets as you create them. You might also want to set the dimensions if you want to have a standard symbol clipped to obtain a new shape. The figure below illustrates how a five-pointed star will be drawn when Pt_ARG_ORIGIN is set to (50,50) and the dimensions of the widget are fixed at 101 x 101 pixels. The star will be constructed from this set of points:

{(95, -31), (-58, 81), (0, -100), (58, 81), (-95, -31)}

poly fig


A five-pointed star before clipping.


Note the resulting bounding box of the widget as well as the origin of the polygon's coordinate space, which is fixed at position (50,50) of the widget's canvas.


clipped poly fig


The star after clipping.


If you don't need special clipping, however, you should use the graphics widget's resize policy or another geometry management mechanism to determine the widget's size. The default resize policy for graphics is Pt_RESIZE_...AS_REQUIRED for both the width and height. To fix the dimensions of the widget as shown in the case above, you have to override the default resize policy. For more information, see the Pt_ARG_RESIZE_FLAGS resource in the description of PtWidget.

Grouping elements of the drawing

Occasionally you'll want to group together a number of graphical primitives and define them as a group or unit. All the primitives within the group are defined in terms of their common origin, and the unit may be repositioned or resized without affecting the group's components. You can do this simply by creating a container-class widget and placing the widgets for the graphical primitives within it.


Note: Using this technique, you can create a complex drawing composed of a number of sub-drawings. There's no limit to the number of drawings that can be nested in this way. The only limiting factor here is the number of resources consumed. Recall that each container widget requires a Photon region, which is a Photon server resource that consumes server memory. In a system with constrained memory, many deeply nested drawings may consume excess amounts of server resources.

Rescaling graphics widgets

The advantage of vector graphics is they use less space to define and are easy to scale. They may be scaled independently in the x and y directions.

To rescale any of your graphics primitives, you must multiply each control point and the origin for the primitive by x and y scale factors. The Pt_CB_RESCALE callback makes this easier.

This callback is invoked whenever the primitive's size is redefined, whether by a PtSetResources() call or directly by the parent.

The callback should be attached to an application function that scales each point of the widget and its origin.

You can thus rescale an entire drawing by creating a resize function for the container that implements the drawing. The resize function repositions each of the children and multiplies their dimensions by the scale factors for x and y. Graphics primitives will automatically be scaled because their Pt_CB_RESCALE callbacks will be invoked.

If a container is used to create sub-drawings, the resize function must be called for each container in order for the rescaling to be propagated through all the sub-drawings. This can be handled by attaching the same resize callback that was used for the drawing as the resize callback for each sub-drawing.

To scale drawings correctly in this manner, follow these two steps:

  1. Attach all the graphics widgets and sub-drawings to the appropriate resize or rescale functions.
  2. Determine the correct scale factors and rescale each of the container's children when the widget holding the drawing is resized.

Attaching resize and rescale functions

A resize function is required for each container used for a drawing or sub-drawing.

This resize function determines the x and y scale factors to apply to the drawing. It looks at the new extents for the drawing widget to determine the ratio of the new width and height compared to the widget's current width and height. It can use these as the scale factor, or it may choose one of these values - presumably the smaller of the two - to use as the scale factor for both width and height in order to preserve the aspect ratio of the drawing (or sub-drawing).

The resize function then passes over each of its children and scales the x and y members of the Pt_ARG_POS and Pt_ARG_DIM resources by the x and y scale factors. In order for children to be resized correctly, you must change their resize policy to Pt_NEVER for both the width and height. You can do this in your resize function or after the graphics are realized.

The code below shows a resize function that performs the actions indicated above. The function takes a PhDim_t pointer as client_data indicating the old extents of the widget. These dimensions are initialized when the widget is realized and updated whenever it's resized.

void resize(PtWidget_t *wgt, void *client_data,
            PtCallbackInfo_t *info)
{
   PhPoint_t   *pos, newpos;
   PhDim_t     *dim, newdim;
   PtArg_t     arg[2];
   PhDim_t     *old_extents = (PhDim_t *)client_data;
   PhRect_t    *extents = (PhRect_t *)info->cbdata;
   int         width = extents->lr.x - extents->ul.x + 1;
   int         height = extents->lr.y - extents->ul.y + 1;
   double      xs = width;
   double      ys = height;
   PtWidget_t  *child = PtWidgetChildFront(wgt);

   if (old_extents)
   {
      xs = xs / old_extents->w;
      ys = ys / old_extents->h;

      while (child != NULL)
      {
         PtSetArg(&arg[0], Pt_ARG_POS, &pos, 0);
         PtSetArg(&arg[1], Pt_ARG_DIM, &dim, 0);
         PtGetResources(child, 2, arg);

         if (dim && pos)
         {
            newpos = *pos;
            newpos.x *= xs;
            newpos.y *= ys;

            newdim = *dim;
            newdim.w *= xs;
            newdim.h *= ys;

            PtSetArg(&arg[0], Pt_ARG_DIM, &newdim, 0);
            PtSetArg(&arg[1], Pt_ARG_POS, &newpos, 0);
            PtSetResources(child, 2, arg);
         }
   
         child = PtWidgetBrotherBehind(child);
      }
   }
}

Calling PtAddCallback() after the widget is created attaches the resize function to the resize callback. When the widget is created, a closure is created for it and the address of closure is associated with the callback in the call to PtAddCallback(). This closure is a dynamically allocated data structure associated with one of more of a widget's callbacks; the closure contains state information for use by those callbacks. In this case, the closure is a PhDim_t that maintains the widget's current dimensions. The closure is initialized by a Pt_CB_REALIZED callback that sets the dimensions to those of the widget.

Any sub-drawings created using container-class widgets are also attached to the same resize callback function and require a closure of their own. The resize callback will be invoked on the sub-drawing when the parent resizes the sub-drawing within the resize callback invoked on the parent.

A scaling function is required for all the graphics primitives. This function calculates x and y scale factors based on the extents passed in the call data. It then scales the Pt_ARG_ORIGIN resource and each point in the Pt_ARG_POINTS array by the scale factors. the scaling function doesn't try to modify the scale factor to preserve a particular aspect ratio because this has already been done by its parent's resize function. The scaling function is attached to the Pt_CB_RESCALE callback of each graphics widget after the widget is created.

Calculating the scale factor

The scaling function must be called with new scale factors in both the x and y directions. The simplest way to get the scale factors is to get the container widget's old and new dimensions. The scale factors can then be expressed as the ratio between the old and new dimensions.

The sample callback function below shows a simple function for rescaling graphics primitives. As with the resize function given previously, the widget's current dimensions are obtained from the closure passed to the callback function as client_data.

void rescale(PtWidget_t *wgt, void *client_data,
             PtCallbackInfo_t *info)
{
   PhDim_t      *old_extents = (PhDim_t *)client_data;
   PhArea_t     *area = (PhArea_t *)info->cbdata;
   PhDim_t      *dim;
   PhPoint_t    *points;
   PhPoint_t    neworg, *org;
   short        *npoints;
   PtArg_t      arg[3];

   PtSetArg(&arg[0], Pt_ARG_DIM, &dim, 0);
   PtSetArg(&arg[1], Pt_ARG_POINTS, &points,
            (ulong_t)&npoints);
   PtSetArg(&arg[2], Pt_ARG_ORIGIN, &org, 0);
   PtGetResources(wgt, 3, arg);

   if (old_extents && dim && points)
   {
      double     xs = dim->w;
      double     ys = dim->h;
      PhPoint_t  *newpoints = (PhPoint_t *)alloca(*npoints
                              * sizeof(PhPoint_t));
      int i;

      xs = xs / old_extents->w;
      ys = ys / old_extents->h;
      neworg.x = org->x * xs;
      neworg.y = org->y * ys;

      for (i = 0; i < *npoints; i++)
      {
         newpoints[i].x = points[i].x * xs;
         newpoints[i].y = points[i].y * ys;
      }

      PtSetArg(&arg[0], Pt_ARG_POINTS, newpoints,
               *npoints);
      PtSetArg(&arg[1], Pt_ARG_ORIGIN, &neworg, 0);
      PtSetResources(wgt, 2, arg);
   }
}

Taken together with the resizing function given previously, this can be used to create a rescalable picture as in the example below. This example creates a drawing using a PtGroup and populates it with three ellipses and an arc to create a simple happy face.

main(int argc, char *argv[])
{
  PtAppContext_t app;
  PtArg_t    args[8];
  int        nargs;
  PtWidget_t *window, *container_widget, *widget;
  PhPoint_t  face[] = { {0,0}, {100,100} };
  PhPoint_t  eye[] = { {0,0}, {6,10} };
  PhPoint_t  mouth[] = { {0,0}, {79,49} };
  PhPoint_t  org;
  PhDim_t    *dim;
  void       resize(PtWidget_t *, void *, 
                    PtCallbackInfo_t *);
  void       rescale(PtWidget_t *, void *,
                     PtCallbackInfo_t *);
  void       realized(PtWidget_t *, void *,
                      PtCallbackInfo_t *);

  if ((window = PtAppInit(&app, &argc, argv, 0, NULL))
      == NULL)
    exit(EXIT_FAILURE);

  /* Create a container for the drawing */
  nargs = 0;
  PtSetArg(&args[nargs], Pt_ARG_GROUP_ORIENTATION,
           Pt_GROUP_ASIS, 0); nargs++;
  PtSetArg(&args[nargs], Pt_ARG_ANCHOR_FLAGS,
           Pt_LEFT_ANCHORED_LEFT|Pt_RIGHT_ANCHORED_RIGHT|

           Pt_TOP_ANCHORED_TOP|Pt_BOTTOM_ANCHORED_BOTTOM,
           Pt_IS_ANCHORED); nargs++;
  container_widget = PtCreateWidget(PtGroup, window, nargs, args);
  dim = (PhDim_t *)malloc(sizeof(PhDim_t));
  PtAddCallback(container_widget, Pt_CB_REALIZED, realized, dim);
  PtAddCallback(container_widget, Pt_CB_RESIZE, resize, dim); 

  nargs = 0;
  dim = (PhDim_t *)malloc(sizeof(PhDim_t));
  PtSetArg(&args[nargs], Pt_ARG_POINTS, face,
           sizeof(face)/sizeof(face[0]));
  nargs++; 
  PtSetArg(&args[nargs], Pt_ARG_FILL_COLOR, Pg_YELLOW, 0);
           nargs++;
  widget = PtCreateWidget(PtEllipse, container_widget, nargs, args);
  PtAddCallback(widget, Pt_CB_REALIZED, realized, dim);
  PtAddCallback(widget, Pt_CB_RESCALE, rescale, dim);

  nargs = 0;
  dim = (PhDim_t *)malloc(sizeof(PhDim_t));
  org.x = 10;
  org.y = 30;
  PtSetArg(&args[nargs], Pt_ARG_POINTS, mouth,
           sizeof(mouth)/sizeof(mouth[0])); nargs++;
  PtSetArg(&args[nargs], Pt_ARG_ORIGIN, &org, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_ARC_START, 2300, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_ARC_END, 3100, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_ARC_TYPE, Pt_ARC_CHORD, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_FILL_COLOR, Pg_BLACK, 0);
           nargs++;
  widget = PtCreateWidget(PtArc, container_widget, nargs, args);
  PtAddCallback(widget, Pt_CB_REALIZED, realized, dim);
  PtAddCallback(widget, Pt_CB_RESCALE, rescale, dim);

  nargs = 0;
  dim = (PhDim_t *)malloc(sizeof(PhDim_t));
  org.x = 27;
  org.y = 30;
  PtSetArg(&args[nargs], Pt_ARG_POINTS, eye,
           sizeof(eye)/sizeof(eye[0]));
  nargs++;
  PtSetArg(&args[nargs], Pt_ARG_ORIGIN, &org, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_FILL_COLOR, Pg_BLACK, 0);
           nargs++;
  widget = PtCreateWidget(PtEllipse, container_widget, nargs, args);
  PtAddCallback(widget, Pt_CB_REALIZED, realized, dim);
  PtAddCallback(widget, Pt_CB_RESCALE, rescale, dim);

  dim = (PhDim_t *)malloc(sizeof(PhDim_t));
  nargs = 0;
  org.x = 67;
  PtSetArg(&args[nargs], Pt_ARG_POINTS, eye,
           sizeof(eye)/sizeof(eye[0]));
  nargs++;
  PtSetArg(&args[nargs], Pt_ARG_ORIGIN, &org, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_FILL_COLOR, Pg_BLACK, 0);
           nargs++;
  widget = PtCreateWidget(PtEllipse, container_widget, nargs, args);
  PtAddCallback(widget, Pt_CB_REALIZED, realized, dim);
  PtAddCallback(widget, Pt_CB_RESCALE, rescale, dim);

  PtRealizeWidget(window);

  PtMainLoop();
}

void realized(PtWidget_t *wgt, void *client_data,
              PtCallbackInfo_t *info)
{
   PhDim_t      *dim;
   PhDim_t      *size = (PhDim_t *)client_data;
   PtArg_t      arg[2];

   info = info;

   PtSetArg(&arg[0], Pt_ARG_DIM, &dim, 0);
   PtGetResources(wgt, 1, arg);

   if (size && dim)
      *size = *dim;
}

Remember to consider the appearance of the drawing if the x and y dimensions are scaled independently in this way. The drawing may look odd if its original aspect ratio isn't preserved. This will be especially true if the drawing contains arcs - circular arcs will become elliptical if the aspect ratio isn't preserved.

You can preserve the aspect ratio of the original drawing by choosing the same scale factor for the x and y dimensions. The scale factor is best determined by:

To do this, calculate the two scale factors and use the minimum of the two:

xs = new.x / old.x;
ys = new.x / old.x;
s = min(xs, ys);

Then scale the old dimensions by the value s to get the desired new dimensions:

desired.x = s * old.x;
desired.y = s * old.x;

Now call the scaling function with the scale factors expressed by the two dimensions, old, and desired.

You can easily modify the resize and scaling callback functions given above to preserve the aspect ratio of all or part of a drawing. To do so, change the closure to a structure that contains the widget dimensions and change the callback functions accordingly.

The closure should also have a flag indicating whether or not the aspect ratio is to be preserved. You could then add the following lines after the calculation of the xs and ys scale factors:

if (old_extents->preserve_aspect)
   xs = xs < ys ? xs : ys;

The aspect ratio of the entire drawing in our happy face example can be preserved by setting the preserve_aspect flag on the closure for the PtGroup widget alone.

Scaling based on original coordinates

Scaling a graphic in the manner indicated above can introduce small round-off errors in the calculation every time the scaling is performed. After many successive recalculations, the graphic may no longer appear correct.

For this reason, it may be a good idea to keep the original coordinates of the graphical object around so that scaling can be performed from them. Keep the array of control points and the origin for the graphic in normalized integer coordinates. When you must scale the graphic, simply calculate a new origin and coordinates by:

  1. Scaling the original values by the widget's dimensions.
  2. Dividing by your normalization factor.

You'll need a way of obtaining the original coordinates inside your callbacks. The simplest way is to have an object associated with each of your graphics widgets. Define a structure, say Graphic_t, for these objects that maintains all the original coordinate data for the graphics widget as well as any additional information you might like to keep about the graphics primitive. This should consist of the graphic control points, the origin, and a position and dimensions for the widget. When you create the graphics widget, store the pointer to this object in the Pt_ARG_USER_DATA resource of the graphics widget.

The rescaling function for graphics widgets must obtain the Graphic_t object associated with the widget and must calculate a new origin and points based on the widget's new dimensions and the original coordinate data.

The resizing function should obtain the Graphic_t object for each child of the container and calculate a new position and dimension for that child based on its original coordinate data and the container's scaling factor.

The following example illustrates how our happy face can be implemented using this strategy:

#include <Pt.h>

typedef struct graphic_str {
   PhPoint_t pos;
   PhDim_t dim;
   PhPoint_t org;
   PhPoint_t *points;
   int n;
} Graphic;

static Graphic *widget_to_graphic(PtWidget_t *w)
{
   PtArg_t arg[1];
   void *data;
   Graphic *graphic;

   PtSetArg(&arg[0], Pt_ARG_USER_DATA, &data, 0);
   PtGetResources(w, 1, arg);

   graphic = data ? *(Graphic **)data : NULL;

   return graphic;
}

int rescale_cb(PtWidget_t *w, void *client_data,
                PtCallbackInfo_t *info)
{
   PtArg_t args[2];
   PhDim_t factor;
   PhDim_t *dim;
   PhRect_t rect;
   Graphic *self = widget_to_graphic(w);
   PhPoint_t org; 
   PhPoint_t *points; 
   int n;

   if (!self) return ( Pt_CONTINUE );
   if (!self->points) return ( Pt_CONTINUE );

   points = (PhPoint_t *)alloca(self->n * sizeof(PhPoint_t));

   if (!points) return ( Pt_CONTINUE );

   PtBasicWidgetCanvas(PtWidgetParent(w), &rect);

   PtSetArg(&args[0], Pt_ARG_DIM, &dim, 0);
   PtGetResources(w, 1, args);

   factor = *dim;

   for (n = 0; n < self->n; n++)
   {
      points[n].x = ( self->points[n].x * factor.w ) / 
         100 + rect.ul.x;
      points[n].y = ( self->points[n].y * factor.h ) /
         100 + rect.ul.y;
   }
   org.x = ( self->org.x * factor.w ) / 
           100 + rect.ul.x;
   org.y = ( self->org.y * factor.h ) /
           100 + rect.ul.y;

   PtSetArg(&args[0], Pt_ARG_POINTS, points, self->n);
   PtSetArg(&args[0], Pt_ARG_ORIGIN, &org, 0);
   PtSetResources(w, 2, args);
   
   return ( Pt_CONTINUE );
}

int resize_cb(PtWidget_t *w, void *client_data,
               PtCallbackInfo_t *info)
{
   PtArg_t args[2];
   PhPoint_t pos;
   PhDim_t dim;
   PhDim_t *size;
   PhDim_t factor, nf;
   PhRect_t rect, prect;
   PhRect_t *extents = (PhRect_t *)info->cbdata;
   Graphic *self = widget_to_graphic(w);
   PtWidget_t *child = PtWidgetChildFront(w);

   if (!self) return ( Pt_CONTINUE );

   PtBasicWidgetCanvas(PtWidgetParent(w), &prect);
   PtBasicWidgetCanvas(w, &rect);

   PtSetArg(&args[0], Pt_ARG_DIM, &size, 0);
   PtGetResources(w, 1, args);
   nf = *size;

   factor.w = extents->lr.x - extents->ul.x + 1;
   factor.h = extents->lr.y - extents->ul.y + 1;

   pos = self->pos;
   dim = self->dim;

   pos.x = ( pos.x * factor.w ) / 100 + prect.ul.x;
   pos.y = ( pos.y * factor.h ) / 100 + prect.ul.y;

   /* this is superfluous */
   dim.w = ( dim.w * factor.w ) / 100;
   dim.h = ( dim.h * factor.h ) / 100;

   PtSetArg(&args[0], Pt_ARG_POS, &pos, 0);
   PtSetResources(w, 1, args);

   while (child != NULL)
   {
      Graphic *g = widget_to_graphic(child);

      if (g)
      {
         pos = g->pos;
         dim = g->dim;

         pos.x = ( pos.x * factor.w ) / 100 + rect.ul.x;
         pos.y = ( pos.y * factor.h ) / 100 + rect.ul.y;
         dim.w = ( dim.w * factor.w ) / 100;
         dim.h = ( dim.h * factor.h ) / 100;

         PtSetArg(&args[0], Pt_ARG_POS, &pos, 0);
         PtSetArg(&args[1], Pt_ARG_DIM, &dim, 0);
         PtSetResources(child, 2, args);
      }

      child = PtWidgetBrotherBehind(child);
   }

   return ( Pt_CONTINUE );
}

int main(int argc, char *argv[])
{
  PtAppContext_t app;
  PtArg_t    args[8];
  int        nargs;
  PhPoint_t     dim;
  PhRect_t     offsets = {0, 0, 0, 0};
  PtWidget_t    *window, *drawing, *widget;

  static PhPoint_t face_pts[] = { {0,0}, {100,100} };
  static PhPoint_t eye_pts[] = { {0,0}, {100,100} };
  static PhPoint_t mouth_pts[] = { {0,0}, {100,100} };

  Graphic *graphic;
  Graphic smiley = {{0,0}, {100,100}, {0,0}, NULL, 0};
  Graphic face = {{0,0}, {100,100}, {0,0}, &face_pts,
                  sizeof(face_pts)/sizeof(face_pts[0])};
  Graphic left_eye = {{27,30}, {7,11}, {0,0}, &eye_pts,
                      sizeof(eye_pts)/sizeof(eye_pts[0])};
  Graphic right_eye = {{67,30}, {7,11}, {0,0}, &eye_pts,
                       sizeof(eye_pts)/sizeof(eye_pts[0])};
  Graphic mouth = {{10,30}, {80,50}, {0,0}, &mouth_pts,
                   sizeof(mouth_pts)/sizeof(mouth_pts[0])};

  dim.x = 200;
  dim.y = 200;
  PtSetArg(&args[0], Pt_ARG_DIM, &dim, 0);

  if ((window = PtAppInit(&app, &argc, argv, 0, NULL))
      == NULL)
  {
    return EXIT_FAILURE;
  }

  graphic = &smiley;
  nargs = 0;
  PtSetArg(&args[nargs], Pt_ARG_ANCHOR_FLAGS,
           Pt_LEFT_ANCHORED_LEFT|Pt_RIGHT_ANCHORED_RIGHT|
           Pt_TOP_ANCHORED_TOP|Pt_BOTTOM_ANCHORED_BOTTOM,
           Pt_IS_ANCHORED); nargs++;
  PtSetArg(&args[nargs], Pt_ARG_ANCHOR_OFFSETS,
           &offsets, 0); nargs++;
  PtSetArg(&args[nargs], Pt_ARG_GROUP_ORIENTATION,
           Pt_GROUP_ASIS, 0); nargs++;
  PtSetArg(&args[nargs], Pt_ARG_USER_DATA, &graphic,
           sizeof (Graphic *)); nargs++;
  PtSetArg(&args[nargs], Pt_ARG_POS, &smiley.pos, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_DIM, &smiley.dim, 0);
           nargs++;
  drawing = PtCreateWidget(PtGroup, window, nargs, args);
  PtAddCallback(drawing, Pt_CB_RESIZE, resize_cb, NULL);

  graphic = &face;
  nargs = 0;
  PtSetArg(&args[nargs], Pt_ARG_FILL_COLOR, Pg_YELLOW, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_USER_DATA, &graphic,
           sizeof (Graphic *)); nargs++;
  PtSetArg(&args[nargs], Pt_ARG_POS, &face.pos, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_DIM, &face.dim, 0);
           nargs++;
  widget = PtCreateWidget(PtEllipse, drawing, nargs, args);
  PtAddCallback(widget, Pt_CB_RESCALE, rescale_cb, NULL);

  graphic = &mouth;
  nargs = 0;
  PtSetArg(&args[nargs], Pt_ARG_ARC_START, 2300, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_ARC_END, 3100, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_ARC_TYPE, Pt_ARC_CHORD, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_FILL_COLOR, Pg_BLACK, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_USER_DATA, &graphic,
           sizeof (Graphic *)); nargs++;
  PtSetArg(&args[nargs], Pt_ARG_POS, &mouth.pos, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_DIM, &mouth.dim, 0);
           nargs++;
  widget = PtCreateWidget(PtArc, drawing, nargs, args);
  PtAddCallback(widget, Pt_CB_RESCALE, rescale_cb, NULL);

  graphic = &left_eye;
  nargs = 0;
  PtSetArg(&args[nargs], Pt_ARG_USER_DATA, &graphic,
           sizeof (Graphic *)); nargs++;
  PtSetArg(&args[nargs], Pt_ARG_FILL_COLOR, Pg_BLACK, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_POS, &left_eye.pos, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_DIM, &left_eye.dim, 0);
           nargs++;
  widget = PtCreateWidget(PtEllipse, drawing, nargs, args);
  PtAddCallback(widget, Pt_CB_RESCALE, rescale_cb, NULL);

  graphic = &right_eye;
  nargs = 0;
  PtSetArg(&args[nargs], Pt_ARG_USER_DATA, &graphic,
           sizeof (Graphic *)); nargs++;
  PtSetArg(&args[nargs], Pt_ARG_FILL_COLOR, Pg_BLACK, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_POS, &right_eye.pos, 0);
           nargs++;
  PtSetArg(&args[nargs], Pt_ARG_DIM, &right_eye.dim, 0);
           nargs++;
  widget = PtCreateWidget(PtEllipse, drawing, nargs, args);
  PtAddCallback(widget, Pt_CB_RESCALE, rescale_cb, NULL);

  PtRealizeWidget(window);

  PtMainLoop();

  return EXIT_SUCCESS;
}

Effects of positional parameters on scaling

It's important to realize the effect that the position, origin, and positional coordinates have when rescaling the widget. This will affect how you choose to use these resources when you create the graphics widgets and how you scale these resources in your resizing and scaling functions.

The sample resizing function shown here modifies the position of each of its children, and the sample scaling function modifies the origin and all the points that define the primitive. This makes the positions of all sub-drawing and primitives scale along with the drawing itself.

Since the widget's origin and position allow independent control over the widget's position in addition to its size, you have maximum flexibility in rescaling drawings. To rescale the drawing's elements without altering their positions, scale the points that define the primitive and leave the position and origin alone.

In general, you should alter the widget's position to affect the position of the bounding box for the primitive that it will be clipped to. Use the origin to translate the primitive so that part of it will be clipped.


Caution: You should always scale graphics based on their original set of points. If you don't use the original points, the aspect will be lost after scaling.

New resources:

Resource C type Pt type Default
Pt_ARG_DASH_LIST char, short Array NULL
Pt_ARG_DASH_SCALE long Scalar 0
Pt_ARG_GRAPHIC_FLAGS char Flag 0
Pt_ARG_LINE_CAP unsigned short Scalar Pg_BUTT_CAP
Pt_ARG_LINE_JOIN unsigned short Scalar Pg_MITER_JOIN
Pt_ARG_LINE_WIDTH long Scalar 0
Pt_ARG_ORIGIN PhPoint_t Struct 0,0
Pt_ARG_POINTS PhPoint_t *, short Array NULL
Pt_CB_RESCALE PtCallback_t * Link NULL

Pt_ARG_DASH_LIST

C type Pt type Default
char, short Array NULL

An array of bytes that describes the on and off bits for stroke operations.

Pt_ARG_DASH_SCALE

C type Pt type Default
long Scalar 0

A scaling factor that's applied to each of the bits in the dash list to determine the number of pixels for each dash. For information on setting this factor, see PgSetStrokeDash() in the Photon Library Reference.

Pt_ARG_GRAPHIC_FLAGS

C type Pt type Default
char Flag 0

Possible values:

Pt_FLOAT_POS
Adjusts the position and origin of the widget, but leaves the graphic in place relative to the widget's parent. The upper left corner of the widget's canvas will be at the upper left corner of the bounding box described by the point array. Depending on its resize policy, the widget may resize to fit the rendering.
Pt_FLOAT_ORIGIN
Adjusts the origin of the graphic, but leaves the widget in place relative to its parent. The upper left corner of the bounding box described by the point array will be at the upper left corner of the widget's canvas.

The default setting of this resource is 0; that is, no flags have been set.

Pt_ARG_LINE_CAP

C type Pt type Default
unsigned short Scalar Pg_BUTT_CAP

Defines how the ends of thick lines are drawn; see PgSetStrokeCap(). Possible values:

Pt_ARG_LINE_JOIN

C type Pt type Default
unsigned short Scalar Pg_MITER_JOIN

Defines how thick lines are connected; see PgSetStrokeJoin(). Possible values:

Pt_ARG_LINE_WIDTH

C type Pt type Default
long Scalar 0

The line width for any graphically based widget.

Pt_ARG_ORIGIN

C type Pt type Default
PhPoint_t Struct 0,0

An offset from the upper left corner of the widget's canvas. The graphic will be rendered with its origin at:

    (widget position) + (Pt_ARG_ORIGIN)

Pt_ARG_POINTS

C type Pt type Default
PhPoint_t *, short Array NULL

An array of points (PhPoint_t structures) describing the graphic. The number of points required in the array - and the interpretation of those points - depends on the type of graphics primitive being defined.

Pt_CB_RESCALE

C type Pt type Default
PtCallback_t * Link NULL

A list of callbacks that the widget invokes if its Pt_ARG_DIM or Pt_ARG_AREA resources are modified. You can use this callback to rescale the widget.

Each callback is passed a PtCallbackInfo_t structure that contains at least the following members:

reason
Pt_CB_RESCALE
reason_subtype
0 (not used).
cbdata
Points to a PhArea_t structure that indicates the old area of the widget (prior to the area/dimension change). You can retrieve the current area by calling PtGetResources().

These callbacks should return Pt_CONTINUE.

Inherited resources:

If the widget modifies an inherited resource, the "Default override" column indicates the new value. This modification affects any subclasses of the widget.

Resource Inherited from Default override
Pt_ARG_AREA PtWidget
Pt_ARG_BANDWIDTH_THRESHOLD PtBasic Not used by this class.
Pt_ARG_BITMAP_CURSOR PtWidget
Pt_ARG_BORDER_WIDTH PtWidget 0
Pt_ARG_BOT_BORDER_COLOR PtBasic
Pt_ARG_COLOR PtBasic
Pt_ARG_CURSOR_COLOR PtWidget
Pt_ARG_CURSOR_TYPE PtWidget
Pt_ARG_DATA PtWidget
Pt_ARG_DIM PtWidget
Pt_ARG_EFLAGS PtWidget
Pt_ARG_FILL_COLOR PtBasic Pg_TRANSPARENT
Pt_ARG_FILL_PATTERN PtBasic
Pt_ARG_FLAGS PtWidget
Pt_ARG_HELP_TOPIC PtWidget
Pt_ARG_HIGHLIGHT_ROUNDNESS PtBasic
Pt_ARG_MARGIN_HEIGHT PtBasic
Pt_ARG_MARGIN_WIDTH PtBasic
Pt_ARG_POS PtWidget
Pt_ARG_RESIZE_FLAGS PtWidget
Pt_ARG_TOP_BORDER_COLOR PtBasic
Pt_ARG_TRANS_PATTERN PtBasic
Pt_ARG_USER_DATA PtWidget
Pt_CB_ACTIVATE PtBasic
Pt_CB_ARM PtBasic
Pt_CB_BLOCKED PtWidget
Pt_CB_DESTROYED PtWidget
Pt_CB_DISARM PtBasic
Pt_CB_GOT_FOCUS PtBasic
Pt_CB_HOTKEY PtWidget
Pt_CB_LOST_FOCUS PtBasic
Pt_CB_MENU PtBasic
Pt_CB_RAW PtWidget
Pt_CB_REALIZED PtWidget
Pt_CB_REPEAT PtBasic
Pt_CB_UNREALIZED PtWidget

[Previous] [Contents] [Index] [Next]