Copyright (c) Hyperion Entertainment and contributors.

Proportional Gadget Type

From AmigaOS Documentation Wiki
Jump to navigation Jump to search

Description

Proportional gadgets allow an application to get or display an amount, level, or position by moving a slidable knob within a track. They are called proportional gadgets because the size and position of the knob is proportional to some application-defined quantity, for example the size of a page, and how much and which part of the page is currently visible.

An example of using proportional gadgets is available in Intuition Windows. The SuperBitMap window example, "lines.c", uses proportional gadgets to control the position of the bitmap within the window.

Proportional gadgets are made up of a container, which is the full size of the gadget, and a knob, that travels within the container. Changing the current value of the gadget is done by dragging the knob, or clicking in the container around the knob. Dragging the knob performs a smooth transition from one value to the next, while clicking in the container jumps to the next page or setting. The KNOBHIT flag in the PropInfo structure is available to allow the program to determine if the gadget was changed by dragging the knob or by clicking in the container. If the flag is set, the user changed the value by dragging the knob.

Proportional gadgets allow display and control of fractional settings on the vertical axis, the horizontal axis or both. While the number of settings has a theoretical limit of 65,536 positions, the actual positioning of the gadget through sliding the knob is limited by the resolution of the screen. Further control is available by clicking in the container, although this often is not convenient for the user. Button or arrow gadgets are often provided for fine tuning of the setting of the gadget.

New 3D Look Proportional Gadgets

Set the PROPNEWLOOK flag in the PropInfo Flags field to get the new 3D look. The new 3D look proportional gadgets have a dithered pattern in the container and updated knob imagery. The knob dimensions are also slightly changed for those proportional gadgets with a border.

Set the PROPBORDERLESS flag in the PropInfo Flags field if no border around the container is desired. Setting this flag with PROPNEWLOOK will provide a 3D knob.

Proportional gadgets and the 3D Look
To create prop gadgets that have the same look as the rest of the system, set the PROPNEWLOOK flag and clear the PROPBORDERLESS flag. It is recommended that applications follow this guideline to maintain a compatible look and feel for all gadgets in the system.

New look proportional gadgets placed in the border of a window will change to an inactive display state when the window is deactivated. This only happens to gadgets that have the PROPNEWLOOK flag set and are in the window border. In the inactive state, the knob is filled with BACKGROUNDPEN.

Logical Types of Proportional Gadgets

There are two usual ways in which proportional gadgets are used (corresponding to the scroller and slider gadgets of the GadTools library). The only difference between sliders and scrollers is the way they are managed internally by the application. The GadTools library provides a high level interface to proportional gadgets, simplifying the management task for these types of objects.

Scrollers

The scroller controls and represents a limited window used to display a large amount of data. For instance, a text editor may be operating on a file with hundreds of lines, but is only capable of displaying twenty or thirty lines at a time.

In a scroller, the container of the gadget is analogous to the total amount of data, while the knob represents the window. (Note that window here is used as an abstract concept and does not necessarily mean Intuition window. It just means a display area reserved for viewing the data.)

The size of the knob with respect to its container is proportional to the size of the window with respect to the total data. Thus, if the window can display half the data, the knob should be half the size of the container. When the amount of data is smaller than the window size, the knob should be as large as its container.

The position of the knob with respect to its container is also proportional to the position of the window with respect to the total data. Thus, if the knob starts half way down the container, the top of the window should display information half way into the data.

Scrollers may be one or two dimensional. One dimensional scrollers are used to control linear data; such as a text file, which can be viewed as a linear array of strings. Such scrollers only slide on a single axis.

Two dimensional scrollers are used to control two dimensional data, such as a large graphic image. Such a scroller can slide on both the horizontal and vertical axes, and the knob's horizontal and vertical size and position should be proportional to the window's size and position in the data set.

Multi-dimensional data may also be controlled by a number of one dimensional scrollers, one for each axis. The Workbench windows provide an example of this, where one scroller is used for control of the x-axis of the window and another scroller is used for control of the y-axis of the window. In this case, the size and position of the knob is proportional to the size and position of the axis represented by the gadget.

If the window has a sizing gadget and has a proportional gadget is the right or bottom border, the sizing gadget is usually placed into the border containing the proportional gadget, as the border has already been expanded to contain the gadget. If the window has proportional gadgets in both the right and the bottom borders, place the sizing gadget into both borders. This creates evenly sized borders that match the height and width of the sizing gadget, i.e. it is only done for visual effect.

Sliders

The slider is used to pick a specific value within a set. Usually the set is ordered, but this is not required. An example of this would be choosing the volume of a sound, the speed of an animation or the brightness of a color. Sliders can move on either the vertical or horizontal axis. A slider that moves on both the horizontal and the vertical axis could be created to choose two values at once.

An example slider which picks an integer between one and ten, should have the following attributes:

  • It should slide on only one axis.
  • Values should be evenly distributed over the length of the slider.
  • Clicking in the container to either side of the knob should increase (or decrease) the value by one unit.

Stylistically, sliding the knob to the right or top should increase the value, while sliding it to the left or down should decrease the value. Note that the orientation of proportional gadgets is correct for scrollers (where the minimum value is topmost or leftmost), but is vertically inverted for sliders. Thus, well-behaved vertical sliders need to invert their value somewhere in the calculations (or else the maximum will end up at the bottom).

Proportional Gadget Components

A proportional gadget has several components that work together. They are the container, the knob, the pot variables and the body variables.

The Container

The container is the area in which the knob can move. It is actually the select box of the gadget. The size of the container, like that of any other gadget select box, can be relative to the size of the window. The position of the container can be relative to any of the Intuition window’s border.

Clicking in the container around the knob will increment or decrement the value of the gadget (the pot variables) by the appropriate amount (specified in the body variables). The knob will move towards the point clicked when action is taken.

The Knob

The knob may be manipulated by the user to quickly change the pot variables. The knob acts like a real-world proportional control. For instance, a knob restricted to movement on a single axis can be thought of as a control such as the volume knob on a radio. A knob that moves on both axes is analogous to the control stick of an airplane.

The user can directly move the knob by dragging it on the vertical or horizontal axis. The knob may be indirectly moved by clicking within the select box around the knob. With each click, the pot variable is increased or decreased by one increment, defined by the settings of the body variables.

The current position of the knob reflects the pot value. A pot value of zero will place the knob in the top or leftmost position, a value of MAXPOT will place the knob at the bottom or rightmost position.

The application can provide its own imagery for the knob or it may use Intuition’s auto-knob. The auto-knob is a rectangle that changes its width and height according to the current body settings. The auto-knob is proportional to the size of the gadget. Therefore, an auto-knob can be used in a proportional gadget whose size is relative to the size of the window, and the knob will maintain the correct size, relative to the size of the container.

Use Separate Imagery for Proportional Gadgets
These Image structures may not be shared between proportional gadgets, each must have its own. Again, do not share the Image structures between proportional gadgets. This does not work, either for auto-knob or custom imagery.
Use Only One Image for the Knob
Proportional gadget knob images may not be a list of images. These must be a single image, initialized and ready to display if a custom image is used for the knob.

The Pot Variables

The HorizPot and VertPot variables contain the actual proportional values entered into or displayed by the gadget. The word pot is short for potentiometer, which is an electrical analog device used to adjust a value within a continuous range.

The proportional gadget pots allow the program to set the current position of the knob within the container, or to read the knob's current location.

The pot variable is a 16-bit, unsigned variable that contains a value ranging from zero to 0xFFFF. For clarity, the constant MAXPOT is available, which is equivalent to 0xFFFF. A similar constant MAXBODY is available for the body variables. As the pot variables are only 16 bits, the resolution of the proportional gadgets has a maximum of 65,536 positions (zero to 65,535).

The values represented in the pot variables are usually translated or converted to a range of numbers more useful to the application. For instance, if a slider covered the range one to three, pot values of zero to 16,383 would represent one, values of 16,384 to 49,151 would represent two and values of 49,152 to 65,535 would represent three. The method of deriving these numbers is fairly complex, refer to the sample code below for more information.

There are two pot variables, as proportional gadgets are adjustable on the horizontal axis, the vertical axis or both. The two pot variables are independent and may be initialized to any 16-bit, unsigned value.

Pot values change while the user is manipulating the gadget. The program may read the values in the pots at any time after it has submitted the gadget to the user via Intuition. The values always have the current settings as adjusted by the user.

The Body Variables

The HorizBody and VertBody variables describe the standard increment by which the pot variables change and the relative size of the knob when auto-knob is used. The increment, or typical step value, is the value added to or subtracted from the internal knob position when the user clicks in the container around the knob. For example, a proportional gadget for color mixing might allow the user to add or subtract 1/16 of the full value each time, thus the body variable should be set to MAXBODY / 16.

Body variables are also used in conjunction with the auto-knob (described above) to display for the user how much of the total quantity of data is displayed. Additionally, the user can tell at a glance that clicking in the container around the knob will advance the position by an amount proportional to the size of the knob.

For instance, if the data is a fifteen line text file, and five lines are visible in the display, then the body variable should be set to one third of MAXBODY. In this case, the auto-knob will fill one third of the container, and clicking in the container ahead of the knob will advance the position in the file by one third.

For a slider, the body variables are usually set such that the full percentage increment is represented. This is not always so for a scroller. With a scroller, some overlap is often desired between successive steps. For example, when paging through a text editor, one or two lines are often left on screen from the previous page, making the transition easier on the user.

The two body variables may be set to the same or different increments. When the user clicks in the container, the pot variables are adjusted by an amount derived from the body variables.

Using the Body and Pot Values

The body and pot values of a proportional gadget are "Intuition friendly" numbers, in that they represent concepts convenient to Intuition, and not to the application. The application must translate these numbers to internal values before acting on them.

Functions for Using a Scroller

/*
**   FindScrollerValues( )
**
** Function to calculate the Body and Pot values of a proportional gadget
** given the three values total, displayable, and top, representing the
** total number of items in a list, the number of items displayable at one
** time, and the top item to be displayed.  For example, a file requester
** may be able to display 10 entries at a time.  The directory has 20
** entries in it, and the top one displayed is number 3 (the fourth one,
** counting from zero), then total = 20, displayable = 10, and top = 3.
**
** Note that this routine assumes that the displayable variable is greater
** than the overlap variable.
**
** A final value, overlap, is used to determine the number of lines of
** "overlap" between pages.  This is the number of lines displayed from the
** previous page when jumping to the next page.
*/
void FindScrollerValues(UWORD total, UWORD displayable, UWORD top,
                        WORD overlap, UWORD *body, UWORD *pot)
{
UWORD hidden;

/* Find the number of unseen lines: */
hidden = max(total - displayable, 0);

/* If top is so great that the remainder of the list won't even
** fill the displayable area, reduce top:
*/
if (top > hidden)
    top = hidden;

/* body is the relative size of the proportional gadget's knob.  Its size
** in the container represents the fraction of the total that is in view.
** If there are no lines hidden, then body should be full-size (MAXBODY).
** Otherwise, body should be the fraction of (the number of displayed
** lines - overlap) / (the total number of lines - overlap).  The "- overlap"
** is so that when the  user scrolls by clicking in the container of the
** scroll gadget, then there is some overlap between the two views.
*/
(*body) = (hidden > 0) ?
    (UWORD) (((ULONG) (displayable - overlap) * MAXBODY) / (total - overlap)) :
    MAXBODY;

/* pot is the position of the proportional gadget knob, with zero meaning that
** the scroll gadget is all the way up (or left), and full (MAXPOT) meaning
** that the scroll gadget is all the way down (or right).  If we can see all
** the lines, pot should be zero.  Otherwise, pot is the top displayed line
** divided by the number of unseen lines.
*/
(*pot) = (hidden > 0) ? (UWORD) (((ULONG) top * MAXPOT) / hidden) : 0;
}


/*
**   FindScrollerTop( )
**
** Function to calculate the top line that is displayed in a proportional
** gadget, given the total number of items in the list and the number
** displayable, as well as the HorizPot or VertPot value.
*/
UWORD FindScrollerTop(UWORD total, UWORD displayable, UWORD pot)
{
UWORD top, hidden;

/* Find the number of unseen lines: */
hidden = max(total - displayable, 0);

/* pot can be thought of as the fraction of the hidden lines that are before
** the displayed part of the list, in other words a pot of zero means all
** hidden lines are after the displayed part of the list (i.e. top = 0),
** and a pot of MAXPOT means all the hidden lines are before the displayed
** part (i.e. top = hidden).
**
** MAXPOT/2 is added to round up values more than half way to the next position.
*/
top = (((ULONG) hidden * pot) + (MAXPOT/2)) >> 16;

/* Once you get back the new value of top, only redraw your list if top
** changed from its previous value.  The proportional gadget may not have
** moved far enough to change the value of top.
*/
return(top);
}

Functions for Using a Slider

/*
**     FindSliderValues( )
**
** Function to calculate the Body and Pot values of a slider gadget given the
** two values numlevels and level, representing the number of levels available
** in the slider, and the current level.  For example, a Red, Green, or Blue
** slider would have (currently) numlevels = 16, level = the color level (0-15).
*/
void FindSliderValues(UWORD numlevels, UWORD level, UWORD *body, UWORD *pot)
{
/* body is the relative size of the proportional gadget's body.
** Clearly, this proportion should be 1 / numlevels.
*/

if (numlevels > 0)
    (*body) = (MAXBODY) / numlevels;
else
    (*body) = MAXBODY;

/* pot is the position of the proportional gadget body, with zero meaning that
** the slider is all the way up (or left), and full (MAXPOT) meaning that the
** slider is all the way down (or right).
**
** For slider gadgets the derivation is a bit ugly:
**
** We illustrate a slider of four levels (0, 1, 2, 3) with the slider at
** level 2.  The key observation is that pot refers the the leading edge of
** the knob, and as such MAXPOT is not all the way to the right, but is one
** body's width left of that.
**
** Level:   0       1       2       3
**      ---------------------------------
**      |       |       |*******|       |
**      |       |       |*******|       |
**      |       |       |*******|       |
**      |       |       |*******|       |
**      ---------------------------------
**      |               |       |
**      0              pot    MAXPOT
**
** From which we observe that pot = MAXPOT * (level/(numlevels-1))
*/

if (numlevels > 1)
    {
    (*pot) = (((ULONG)MAXPOT) * level)/(numlevels-1);
    }
else
    {
    (*pot) = 0;
    }
}


/*
**     FindSliderLevel( )
**
** Function to calculate the level of a slider gadget given the total number
** of levels as well as the HorizPot or VertPot value.
*/
UWORD FindSliderLevel(UWORD numlevels, UWORD pot)
{
UWORD level;

/* We illustrate a 4-level slider (0, 1, 2, 3) with the knob on the transition
** point between calling it at levels 1 and 2.
**
** Level:   0       1       2       3
**      ---------------------------------
**      |       |    ***|***    |       |
**      |       |    ***|***    |       |
**      |       |    ***|***    |       |
**      |       |    ***|***    |       |
**      ---------------------------------
**      |           |           |
**      0          pot        MAXPOT
**
**
** We've already shown that the vertical lines (which represent the natural
** position of the knob for a given level are:
**
**     pot = MAXPOT * (level/(numlevels-1))
**
** and we see that the threshold between level and level-1 is half-way between
** pot(level) and pot(level-1), from which we get
**
**     level = (numlevels-1) * (pot/MAXPOT) + 1/2
*/

if (numlevels > 1)
    {
    level = (((ULONG)pot) * (numlevels-1) + MAXPOT/2) / MAXPOT;
    }
else
    {
    level = 0;
    }

return(level);
}

Initialization of a Proportional Gadget

The proportional gadget is initialized like any other gadget, with the addition of the PropInfo structure.

Initialization of the PropInfo Structure

This is the special data required by the proportional gadget.

struct PropInfo
    {
    UWORD Flags;
    UWORD HorizPot;
    UWORD VertPot;
    UWORD HorizBody;
    UWORD VertBody;
    UWORD CWidth;
    UWORD CHeight;
    UWORD HPotRes, VPotRes;
    UWORD LeftBorder;
    UWORD TopBorder;
    };
Flags
In the Flags variable, the following flag bits are of interest:
PROPBORDERLESS Set the PROPBORDERLESS flag to create a proportional gadget without a border.
AUTOKNOB Set the AUTOKNOB flag in the Flags field to use the auto-knob, otherwise the application must provide knob imagery.
FREEHORIZ and FREEVERT Set the FREEHORIZ flag to create a gadget that adjust left-to-right, set the FREEVERT flag for top-to-bottom movement. Both flags may be set in a single gadget.
PROPNEWLOOK Set the PROPNEWLOOK flag to create a gadget with the new look. If this flag is not set, the gadget will be rendered using a V34 compatible design.
KNOBHIT The KNOBHIT flag is set by Intuition when this knob is hit by the user.
HorizPot and VertPot
Initialize the HorizPot and VertPot variables to their starting values before the gadget is added to the system. The variables may be read by the application. The gadget must be removed before writing to these variables, or they may be modified with NewModifyProp().
HorizBody and VertBody
Set the HorizBody and VertBody variables to the desired increment. If there is no data to show or the total amount displayed is less than the area in which to display it, set the body variables to the maximum, MAXBODY.

The remaining variables and flags are reserved for use by Intuition.

Initialization of the Gadget Structure

In the Gadget structure, set the GadgetType field to GTYP_PROPGADGET and place the address of the PropInfo structure in the SpecialInfo field.

When using AUTOKNOB, the GadgetRender field must point to an Image structure. The Image need not be initialized when using AUTOKNOB, but the structure must be provided. These Image structures may not be shared between gadgets, each must have its own.

To use application imagery for the knob, set GadgetRender to point to an initialized Image structure. If the knob highlighting is done by alternate image (GFLG_GADGHIMAGE), the alternate image must be the same size and type as the normal knob image.

Modifying an Existing Proportional Gadget

To change the flags and the pot and body variables after the gadget is displayed, the program can call NewModifyProp().

VOID NewModifyProp( struct Gadget *gadget, struct Window *window,
                    struct Requester *requester, ULONG flags,
                    ULONG horizPot, ULONG vertPot,
                    ULONG horizBody, ULONG vertBody, LONG numGad );

The gadget's internal state will be recalculated and the imagery will be redisplayed to show the new state. When numGads (in the prototype above) is set to all ones, NewModifyProp() will only update those parts of the imagery that have changed, which is much faster than removing the gadget, changing values, adding the gadget back and refreshing its imagery.