GadTools V47 Additions

gadtools.library 47 (AmigaOS 3.2) is the first functional update of GadTools since V40. It adds a font-relative grid layout (NG_GRIDLAYOUT with the new functions SetDesignFontA() and ScaleGadgetRectA()), context frames with headlines for DrawBevelBoxA(), two read-only listview attributes, a scaled MX spacing, square scroller arrows, and support for Intuition's new menu glyphs and mouse-wheel scrolling. Programs that use none of it behave as before; programs that use it should open gadtools.library with version 47 or check the version first. On older versions the new tags are simply ignored ("New to V47 GadTools. Ignored by older versions").

For the classic GadTools programming model (CreateContext, CreateGadgetA, GT_GetIMsg) see GadTools Library Reference.

New functions

Function LVO Registers Purpose
LONG SetDesignFontA(APTR vi, struct TextAttr *tattr, CONST struct TagItem *tags) −138 A0/A1/A2 define the font the grid layout is based on
LONG ScaleGadgetRectA(struct NewGadget *ng, CONST struct TagItem *tags) −144 A0/A1 scale one NewGadget rectangle by the grid

Both have varargs stubs (SetDesignFont(), ScaleGadgetRect()). The four slots that follow (−150 … −168) are private/reserved; GT_GetGadgetAttrsA() stays at −174.

Grid layout: NG_GRIDLAYOUT

#define NG_GRIDLAYOUT 0x0080  /* scale positions along the design font
                                 set by SetDesignFont() */

When NG_GRIDLAYOUT is set in ng_Flags, CreateGadgetA() does not take ng_LeftEdge, ng_TopEdge, ng_Width and ng_Height as pixels. Each of these WORDs is a fixed-point grid value:

Bits Meaning
15–6 whole font cells
5–4 quarter cells (fractional part)
3–0 signed pixel offset: 0 = none, 1…7 = +1…+7, 15 = −1 (two's complement nibble)

In other words, the upper 12 bits count quarters of the font width (horizontal values) or font height (vertical values), and the low 4 bits add a small pixel correction. A whole cell is n << 6; a quarter cell is n << 4. Without SetDesignFontA() the grid is based on an 8×8 cell (topaz 8).

The autodoc gives rules of thumb for sizes:

Gadget Height Notes
Buttons 7 << 4 1.75 cells
Checkboxes 6 << 4 high, 13 << 4 wide 1.5 cells high
Horizontal sliders 5 << 4 1.25 cells

The grid origin is the window's inner top-left corner: GTVI_LeftBorder defaults to screen->WBorLeft, and GTVI_TopBorder to screen->WBorTop + screen->Font->ta_YSize + 1 (a normal window with a drag bar). Set both to 0 for borderless or WFLG_GIMMEZEROZERO windows.

A handy pair of macros:

#define GCELL(n)        ((WORD)((n) << 6))          /* whole cells   */
#define GQUART(q)       ((WORD)((q) << 4))          /* quarter cells */
#define GOFFS(v,pix)    ((WORD)((v) | ((pix) & 15)))/* + pixel offset */

SetDesignFontA

LONG SetDesignFontA(APTR vi, struct TextAttr *tattr, CONST struct TagItem *tags);

Sets the font that the grid layout of this VisualInfo is based on. vi must not be NULL. The TextAttr is not referenced after the call. Returns 0 on failure.

Tag Value Meaning
GTVI_LeftBorder 3.2+ GT_TagBase+96 left offset of the design grid (default: current VisualInfo setting)
GTVI_TopBorder 3.2+ GT_TagBase+97 top offset of the design grid
GTVI_MinFontWidth 3.2+ GT_TagBase+100 minimum cell width in pixels; used if the font is narrower (default 0)
GTVI_MinFontHeight 3.2+ GT_TagBase+101 minimum cell height in pixels (default 0)

GTVI_LeftBorder and GTVI_TopBorder are also accepted by GetVisualInfoA(). GT_TagBase is TAG_USER + 0x80000.

ScaleGadgetRectA

LONG ScaleGadgetRectA(struct NewGadget *ng, CONST struct TagItem *tags);

If NG_GRIDLAYOUT is set, converts the four grid values in ng to pixels using the design font, then clears NG_GRIDLAYOUT. It returns non-zero if it scaled, 0 if ng was left alone. CreateGadgetA() uses the same routine internally; the public entry point exists so you can pre-compute sizes, for example the window size. The design font "should best be identical" to ng_TextAttr.

Tag Value Meaning
GTVI_AlignRight 3.2+ GT_TagBase+98 snap the right edge to a grid position instead of scaling the width
GTVI_AlignBottom 3.2+ GT_TagBase+99 snap the bottom edge to a grid position instead of scaling the height

The autodoc example computes one cell at position 2,3:

ng.ng_LeftEdge = 2 << 6;
ng.ng_TopEdge  = 3 << 6;
ng.ng_Width    = 1 << 6;
ng.ng_Height   = 1 << 6;
ng.ng_Flags    = NG_GRIDLAYOUT;
ScaleGadgetRect(&ng, TAG_DONE);

Gotcha: junk in ng_Flags

Release 47.2 notes: "Some programs leave junk in ng_Flags and then trigger erroneously the font scaling algorithm. The scaler now attempts to detect such junk and disables itself upon its detection." Always initialise ng_Flags fully. Old code that never cleared it can get its gadgets silently scaled, or a new program can get its grid layout disabled.

Example: a font-sensitive window

#include <exec/memory.h>
#include <intuition/intuition.h>
#include <libraries/gadtools.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/gadtools.h>

#define GCELL(n)   ((WORD)((n) << 6))
#define GQUART(q)  ((WORD)((q) << 4))

struct Library *GadToolsBase;
struct IntuitionBase *IntuitionBase;

int main(void)
{
    struct Screen *scr;
    APTR vi;
    struct Gadget *glist = NULL, *gad;
    struct NewGadget ng;
    struct Window *win = NULL;
    WORD innerw, innerh;

    IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 47);
    GadToolsBase  = OpenLibrary("gadtools.library", 47);
    if (!IntuitionBase || !GadToolsBase) goto out;

    if (!(scr = LockPubScreen(NULL))) goto out;
    if ((vi = GetVisualInfo(scr, TAG_DONE)))
    {
        /* grid is based on the screen font */
        SetDesignFont(vi, scr->Font, TAG_DONE);

        /* pre-compute the inner size of a 24 x 5 cell design area */
        ng.ng_LeftEdge = 0;           ng.ng_TopEdge = 0;
        ng.ng_Width    = GCELL(24);   ng.ng_Height  = GCELL(5);
        ng.ng_TextAttr = scr->Font;   ng.ng_VisualInfo = vi;
        ng.ng_GadgetText = NULL;      ng.ng_GadgetID = 0;
        ng.ng_UserData = NULL;        ng.ng_Flags = NG_GRIDLAYOUT;
        ScaleGadgetRect(&ng, TAG_DONE);
        innerw = ng.ng_Width;
        innerh = ng.ng_Height;

        gad = CreateContext(&glist);

        /* string gadget: x=8 cells, y=1 cell, 15 cells wide, 1.75 high */
        ng.ng_LeftEdge   = GCELL(8);  ng.ng_TopEdge = GCELL(1);
        ng.ng_Width      = GCELL(15); ng.ng_Height  = GQUART(7);
        ng.ng_GadgetText = "_Name";   ng.ng_GadgetID = 1;
        ng.ng_Flags      = NG_GRIDLAYOUT | PLACETEXT_LEFT;
        gad = CreateGadget(STRING_KIND, gad, &ng,
                           GT_Underscore, '_', TAG_DONE);

        /* OK button: x=1 cell, y=3 cells */
        ng.ng_LeftEdge   = GCELL(1);  ng.ng_TopEdge = GCELL(3);
        ng.ng_Width      = GCELL(8);  ng.ng_Height  = GQUART(7);
        ng.ng_GadgetText = "_OK";     ng.ng_GadgetID = 2;
        ng.ng_Flags      = NG_GRIDLAYOUT | PLACETEXT_IN;
        gad = CreateGadget(BUTTON_KIND, gad, &ng,
                           GT_Underscore, '_', TAG_DONE);

        if (gad)
            win = OpenWindowTags(NULL,
                WA_PubScreen,   scr,
                WA_Title,       "Grid layout",
                WA_InnerWidth,  innerw,
                WA_InnerHeight, innerh,
                WA_DragBar, TRUE, WA_DepthGadget, TRUE,
                WA_CloseGadget, TRUE, WA_Activate, TRUE,
                WA_Gadgets,     glist,
                WA_IDCMP,       IDCMP_CLOSEWINDOW | IDCMP_REFRESHWINDOW |
                                STRINGIDCMP | BUTTONIDCMP,
                TAG_DONE);

        if (win)
        {
            BOOL done = FALSE;
            GT_RefreshWindow(win, NULL);
            while (!done)
            {
                struct IntuiMessage *im;
                WaitPort(win->UserPort);
                while ((im = GT_GetIMsg(win->UserPort)))
                {
                    if (im->Class == IDCMP_CLOSEWINDOW) done = TRUE;
                    if (im->Class == IDCMP_REFRESHWINDOW)
                    {
                        GT_BeginRefresh(win);
                        GT_EndRefresh(win, TRUE);
                    }
                    GT_ReplyIMsg(im);
                }
            }
            CloseWindow(win);
        }
        FreeGadgets(glist);
        FreeVisualInfo(vi);
    }
    UnlockPubScreen(NULL, scr);
out:
    if (GadToolsBase)  CloseLibrary(GadToolsBase);
    if (IntuitionBase) CloseLibrary((struct Library *)IntuitionBase);
    return 0;
}

The same source then lays out correctly with topaz 8, a 13-point screen font or anything in between. With a fixed pixel layout it would overlap or leave gaps.

GTMX_ScaledSpacing

GTMX_ScaledSpacing (GT_TagBase+102, UWORD) is GTMX_Spacing given in grid units, so radio buttons keep their spacing when the GUI scales. It overrides GTMX_Spacing when NG_GRIDLAYOUT is set and is ignored otherwise.

Bevel boxes: display and context frames

DrawBevelBoxA() gains two frame types and four tags:

Frame type Value Look
BBFT_BUTTON 3.1+ 1 button box
BBFT_RIDGE 3.1+ 2 string-gadget ridge
BBFT_ICONDROPBOX 3.1+ 3 icon drop box
BBFT_DISPLAY 3.2+ 6 recessed display box (V47)
BBFT_CTXTFRAME 3.2+ 7 context frame with headline (V47)
Tag Value Type Meaning
GTBB_Scale 3.2+ GT_TagBase+92 BOOL treat the coordinates as grid values (see above)
GTBB_Headline 3.2+ GT_TagBase+93 UBYTE * headline centred on top of a BBFT_CTXTFRAME box
GTBB_HeadlinePen 3.2+ GT_TagBase+94 UBYTE pen for the headline; default HIGHLIGHTTEXTPEN
GTBB_HeadlineFont 3.2+ GT_TagBase+95 struct TextAttr * headline font; default is the RastPort's font
DrawBevelBox(win->RPort, GCELL(1), GCELL(1), GCELL(22), GCELL(4),
             GT_VisualInfo,  vi,
             GTBB_Scale,     TRUE,
             GTBB_FrameType, BBFT_CTXTFRAME,
             GTBB_Headline,  "Options",
             TAG_DONE);

Notes:

  • BBFT_CTXTFRAME "requires intuition V47 to render anything". It maps to FRAME_CONTEXT in frameiclass, and both constants were renumbered before release to avoid a clash.
  • Headlines need gadtools 47.13 or later. From that version DrawBevelBoxA() passes IA_Label/IA_LabelPen to frameiclass instead of IA_Data/IA_FGPen (see Intuition V47 BOOPSI and Input Extensions).
  • A bevel box is rendering, not a gadget. Redraw it on IDCMP_REFRESHWINDOW like any other rendering.

Listview: GTLV_Total and GTLV_Visible

Two read-only attributes for GT_GetGadgetAttrsA() on LISTVIEW_KIND (gadtools 47.13):

Tag Value Type Meaning
GTLV_Total 3.2+ GT_TagBase+92 UWORD total number of items
GTLV_Visible 3.2+ GT_TagBase+93 UWORD number of visible items

They have the same numeric values as GTBB_Scale/GTBB_Headline. The header explains that "no conflicts are possible due to the wholly different usage contexts". Together with GTLV_Top they let you build page-up/page-down keys and your own scrolling:

ULONG top, vis, total;

if (GT_GetGadgetAttrs(lv, win, NULL,
                      GTLV_Top, &top, GTLV_Visible, &vis,
                      GTLV_Total, &total, TAG_DONE) == 3)
{
    LONG newtop = (LONG)top + (LONG)vis;              /* page down */
    if (newtop > (LONG)total - (LONG)vis) newtop = (LONG)total - (LONG)vis;
    if (newtop < 0) newtop = 0;
    GT_SetGadgetAttrs(lv, win, NULL, GTLV_Top, newtop, TAG_DONE);
}

GT_GetGadgetAttrsA() returns the number of attributes it filled in, so compare the result with the number of tags you asked for before using the values.

Scrollers, sliders and the mouse wheel

  • GTSC_Arrows accepts −1 (47.12) for "square arrow buttons". The size is corrected for screen aspect ratio and capped at a quarter of the scroller length. Listview scrollers always use square arrows now, instead of arrows as tall as one item.
  • Scroller and slider knobs get a 3D embossed look and a darker container with intuition.library 47.44 or later (47.11), and are drawn selected while dragged (47.12).
  • GadTools gadgets take part in Intuition's OS-level scroll-wheel support (47.6). See Intuition V47 BOOPSI and Input Extensions for IDCMP_EXTENDEDMOUSE.
  • Items with a sub-menu no longer get a "»" (ASCII 187) appended. GadTools sets the Intuition SUBMENU flag and Intuition draws its MENUSUB sysiclass image (gadtools 47.8; the image was added in intuition 47.28).
  • Menu layout takes the MENUMX and MENUSUB glyphs into account, and items are never shorter than the tallest standard menu glyph.
  • The Shift symbol for MIF_SHIFTCOMMSEQ items is laid out correctly (47.5, 47.10), and since 47.16 the flag also applies to command strings.
  • LayoutMenusA() no longer crashes on BOOPSI images used as menu labels (45.1).

Other fixes worth knowing

Version Change
47.4 GTMX_Active beyond the number of choices activates the last one instead of crashing
47.7 STRING_KIND with GTST_String longer than GTST_MaxChars is now truncated properly
47.14 a slider prototype change was reverted because it broke programs that peek the gadget structure
47.15 the autodoc now documents GTSL_DispFunc: LONG (*function)(struct Gadget *g, LONG level), both arguments passed on the stack as 32-bit values; level is in −32768…32767

See Also


Sources: NDK 3.2 R4 Include_H/libraries/gadtools.h, SFD/gadtools_lib.sfd, FD/gadtools_lib.fd, Autodocs/gadtools.doc, ReleaseNotes/gadtools-RelNotes, ReleaseNotes/intuition-RelNotes Updated: 2026-11-13