Control Surfaces

What's a control surface?

Control surfaces are geometrical regions within a widget that can position, size and draw themselves. Additionally, they can define their own behavior. They do all this via callbacks and event-handling flags that are supplied when the surface is created.

Control surfaces let you redefine the behavior for any area within a widget's drawable extent. Additionally, they can draw themselves as well as calculate their own geometry. Conceptually, they can be considered as lightweight “widgets within widgets.”

For example, consider a scroll bar. You get different actions, depending on where you click on it: the arrow buttons step up and down; clicking in the trough pages up and down; dragging on the handle scrolls as you move. PtScrollbar is implemented as a single widget with several control surfaces on it.

You could also use control surfaces for:

It's important to note that control surfaces are a property of a widget; they require a widget in order to exist. However, a widget can possess any number of control surfaces, making it possible to implement a whole user interface using only one widget (say a PtWindow) at a fraction of the runtime data size (8% being a reasonable upper bound) as opposed to implementing the same UI using widgets.

Limitations

There are a few limitations to control surfaces:

Binding actions to control surfaces

You can bind control surfaces to any of a widget's predefined actions or to user-defined actions.

The types of control surfaces are:

Regular surfaces
Let you define an event mask and callback function for the control surface.
Action surfaces
Let you automatically bind a control surface to one of a widget's predefined actions.

Referring to control surfaces

You can refer to a control surface via:

While the pointer method is more direct and therefore quicker, it's not as safe as the ID method. To understand why, consider how control surfaces are organized and stored in memory.

Unlike the widget hierarchy, which is implemented as a linked list, control surfaces are stored as an array of surface structures (PtSurface_t). The major reasons for storing them this way are:

As you physically move control surfaces around in the stacking order, their placement in the array changes, affecting their address in memory. In addition, as you add or remove control surfaces to or from a widget, the array needs to be reallocated, which also may cause the array itself to move around in memory. With all this possibility of memory movement, numerical identifiers are the only reliable way of locating a surface.

If you're pretty certain that a widget's surface configuration isn't going to change, then the pointer method is safe (and quicker, since the ID method needs to do a linear lookup in the surface array).

Control-surface API

The functions listed below are described in the Photon Library Reference.

Creating and destroying control surfaces

The following functions create and destroy control surfaces:

PtCreateActionSurface()
Create a control surface within a widget, bound to a widget action
PtCreateSurface()
Create a regular control surface within a widget
PtDestroyAllSurfaces()
Destroy all of a widget's control surfaces
PtDestroySurface()
Destroy a control surface
PtDestroySurfaceById()
Destroy the control surface with a given ID

Finding IDs for control surfaces

To find surface and action IDs, use these functions:

PtSurfaceActionId()
Get the action ID for a surface
PtSurfaceId()
Get the ID of a control surface

Calculating geometry for control surfaces

You must provide a function that calculates the control surface's geometry. Control surfaces are asked to calculate their geometry twice when the widget that owns them is asked to calculate its geometry:

The post argument that's passed to the geometry function tells you which case is in progress.

A surface may also calculate its geometry based on the geometry of other surfaces. Using PtCalcSurface() or PtCalcSurfaceById(), you can ensure that the surface you're interested in has calculated its geometry prior to examining it.

The actual recording of the surface's geometry is simply a matter of directly modifying the surface's points array. Be sure you know how this array is organized before proceeding. This organization is detailed in the documentation for PtCreateSurface().

These functions deal with a control surface's geometry:

PtCalcSurface()
Force a surface to calculate its geometry
PtCalcSurfaceByAction()
Force all surfaces associated with an action to calculate their geometry
PtCalcSurfaceById()
Force the control surface with a given ID to calculate its geometry
PtSurfaceCalcBoundingBox(), PtSurfaceCalcBoundingBoxById()
Calculate the bounding box for a control surface
PtSurfaceExtent(), PtSurfaceExtentById()
Calculate the extent of a control surface
PtSurfaceHit()
Find the control surface hit by a given point
PtSurfaceRect(), PtSurfaceRectById()
Get the bounding box of a control surface
PtSurfaceTestPoint()
Test whether or not a point is inside a control surface

Drawing control surfaces

Control surfaces are asked to draw themselves from back to front, after the widget itself has drawn. No clipping is done for you. If you want clipping, you have to implement the necessary logic to adjust the clipping list as surfaces are traversed, and then reinstate the clipping stack after the last surface is drawn. Otherwise, you'll get some unexpected results.

The following functions damage control surfaces so they'll be redrawn:

PtDamageSurface(), PtDamageSurfaceById()
Mark a surface as damaged so that it will be redrawn
PtDamageSurfaceByAction()
Damage all surfaces that are associated with an action

Activating control surfaces

This function activates a control surface:

PtCheckSurfaces()
Match an event with the control surfaces belonging to a widget

Enabling and disabling control surfaces

You can enable and disable control surfaces, like widgets:

PtDisableSurface(), PtDisableSurfaceById()
Disable a control surface
PtDisableSurfaceByAction()
Disable all control surfaces associated with an action
PtEnableSurface(), PtEnableSurfaceById()
Enable a control surface
PtEnableSurfaceByAction()
Enable all control surfaces associated with an action
PtSurfaceIsDisabled()
Determine if a control surface is disabled
PtSurfaceIsEnabled()
Determine if a control surface is enabled

Finding control surfaces

To find a control surface, use these functions:

PtFindSurface()
Find the control surface with a given ID
PtFindSurfaceByAction()
Find the control surface associated with a given action
PtWidgetActiveSurface()
Get a widget's currently active control surface

Hiding and showing control surfaces

You can hide and show control surfaces, too:

PtHideSurface(), PtHideSurfaceById()
Hide a control surface
PtHideSurfaceByAction()
Hide all control surfaces associated with an action
PtShowSurface(), PtShowSurfaceById()
Show a hidden control surface
PtShowSurfaceByAction()
Show all hidden control surfaces associated with an action
PtSurfaceIsHidden()
Determine if a control surface is hidden
PtSurfaceIsShown()
Determine if a control surface is shown

Ordering control surfaces

Like widgets, you can stack control surfaces:

PtInsertSurface(), PtInsertSurfaceById()
Insert a control surface in front of or behind another
PtSurfaceBrotherBehind()
Get the control surface behind a given one
PtSurfaceBrotherInFront()
Get the control surface in front of a given one
PtSurfaceInBack()
Get the backmost control surface belonging to a widget
PtSurfaceInFront()
Get the frontmost control surface belonging to a widget
PtSurfaceToBack(), PtSurfaceToBackById()
Move a control surface behind all other control surfaces belonging to a widget
PtSurfaceToFront(), PtSurfaceToFrontById()
Move a control surface in front of all other control surfaces belonging to a widget

Storing user data with control surfaces

There's no callback data in the function associated with control surfaces; you can store user data with control surfaces by calling:

PtSurfaceAddData(), PtSurfaceAddDataById()
Add data to a control surface
PtSurfaceGetData(), PtSurfaceGetDataById()
Get data associated with a control surface
PtSurfaceRemoveData(), PtSurfaceRemoveDataById()
Remove data from a control surface

Example

Here's a program that creates some control surfaces:

#include <Pt.h>

/* This is the function that's called when an event occurs
   for our rectangular control surface. When a user clicks
   on this surface, we'll tally up the clicks and print how
   many have occurred. */

static int rect_surface_callback( PtWidget_t *widget,
                                  PtSurface_t *surface,
                                  PhEvent_t *event)
{
 static int rclicks = 1;
 printf("Rectangle clicks:  %d\n", rclicks++);
 return(Pt_END);
}
 
/* This is the function which draws the contents of our
   rectangular control surface. This is a very simple
   example; it draws a red rectangle. */

static void rect_surface_draw( PtWidget_t *widget,
                               PtSurface_t *surface,
                               PhTile_t *damage)
{
 PgSetFillColor(Pg_RED);
 PgDrawRect(PtSurfaceRect(surface, NULL), Pg_DRAW_FILL);
}
 
/* This is the function keeps the size of the control
   surface in sync with the size of the widget.
   PtWidgetExtent() returns a rect containing the current size
   of the widget.

   PtSurfaceRect() is a macro; this means that you have direct
   access to the data within your control surface. You do not
   need to call any functions to change its size. Change the
   data directly. */

static void rect_surface_calc( PtWidget_t *widget,
                               PtSurface_t *surface,
                               uint8_t post)
{
 /* Do this only after widget has extented. */
 if(post)
 {
  /* The rect occupies the top left quadrant of the window. */
  PhRect_t *extent;
  PhRect_t *srect;

  extent = PtWidgetExtent(widget, NULL);
  srect = PtSurfaceRect(surface, NULL);

  srect->ul = extent->ul;
  srect->lr.x = (extent->ul.x + extent->lr.x) / 2;
  srect->lr.y = (extent->ul.y + extent->lr.y) / 2;
 }
}

/* This is the function that's called when an event occurs
   for our elliptical control surface. When a user clicks on
   this surface, we'll tally up the clicks and print how
   many have occurred. */

static int ell_surface_callback( PtWidget_t *widget,
                                 PtSurface_t *surface,
                                 PhEvent_t *event)
{
 static int eclicks = 1;
 printf("Ellipse clicks:  %d\n", eclicks++);
 return(Pt_END);
}

/* This is the function that draws the contents of our
   elliptical control surface. This is a very simple
   example; it draws a green ellipse. */

static void ell_surface_draw( PtWidget_t *widget,
                              PtSurface_t *surface,
                              PhTile_t *damage)
{
 PhRect_t *s = PtSurfaceRect(surface, NULL);
 PgSetFillColor(Pg_GREEN);
 PgDrawEllipse(&(s->ul), &(s->lr),
               Pg_DRAW_FILL | Pg_EXTENT_BASED);
}

/* This is our main function. We create a window, initialize
   our application with the Photon server and create two
   control surfaces.
    
   Notice that the second surface doesn't supply the last
   parameter, the extent calculation function. This isn't
   needed because of the fifth parameter, the height and
   width stored in a point structure. This is a pointer
   to the actual point structure within the window widget.
   Thus, if the window's extent changes, changing the
   extent point structure, the control surface is
   automatically updated with the new values! */

int main(int argc, char **argv)
{
 PtArg_t args[1];
 PtWidget_t *window;
 const PhDim_t dim = { 200, 200 };

 PtSetArg(&args[0], Pt_ARG_DIM, &dim, 0);
 window = PtAppInit(NULL, &argc, argv, 1, args);

 /* Create a rectangular control surface. */
 PtCreateSurface( window, 0, 0, Pt_SURFACE_RECT, NULL,
                  Ph_EV_BUT_PRESS, rect_surface_callback,
                  rect_surface_draw, rect_surface_calc);

 /* Create an elliptical control surface to fill the window. */
 PtCreateSurface( window, 0, 0, Pt_SURFACE_ELLIPSE,
                  (PhPoint_t*)PtWidgetExtent(window, NULL),
                  Ph_EV_BUT_PRESS, ell_surface_callback,
                  ell_surface_draw, NULL);

 PtRealizeWidget(window);
 PtMainLoop();

 return(EXIT_SUCCESS);
}