GrGUI 1.3.0

a graphics gui on top of MGRX

Programmer's Guide by examples

Written by Mariano Alvarez Fernández on October 1, 2019

Last update: October 1, 2019


Abstract

  GrGUI is a mini graphics user interface running on top of MGRX. It wants to be small, easy to use and not intrusive whit your program design, this is why it doesn't have a main loop to take control, instead you send individual events to specific functions when you want the GUI takes care of them. You don't need to use all the GUI functionality, you can (by example) use only the menues or the menu bar or some dialogs or some buttons or the GUI Contexts, it's up to you.

In this document, instead to present a detailed manual of each function, we will present eleven examples that covers the GrGUI basic concepts.

Because all MGRX functions begin with "Gr" and all GrGUI functions begin with "GUI" yo can easily know what code is GrGUI related or standard MGRX code.

You can find all the examples in the MGRX distribution under the "testgui" subdirectory.

Contents

  • Example 01. Initiating and ending GrGUI
  • Example 02. GUI Contexts
  • Example 03. Menues
  • Example 04. Menu bar
  • Example 05. GUI Panels
  • Example 06. Common Dialogs
  • Example 07. GUI Tiles
  • Example 08. GUI Objects
  • Example 09. GUI Tiles with Objects and TextArea
  • Example 10. GUI Dialog with Objects
  • Example 11. Fonts, Colors and Double Buffer
  • More information

  • Example 01. Initiating and ending GrGUI

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>
    
    int main()
    {
        char *abouttext[4] = {
            "Welcome to MGRX and GrGUI",
            "MGRX is a small C 2D graphics library",
            "and GrGUI a miniGUI on top of MGRX",
            "visit mgrx.fgrim.com for more info"};
    
        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);
    
        GUICDialogInfo("Hello GrGUI", (void **)abouttext, 4, "Ok");
    
        GUIEnd();
        GrSetMode(GR_default_text);
    
        return 0;
    }
    

    Note we only include "grgui.h", because internally it includes "mgrx.h" and even "mgrxkeys.h".

    "GUIInit" expect the graphics mode to be set before calling it. GUInit takes two parameters that can be 1 (true) or 0 (false). The first one indicates if it must init the MGRX input subsystem. The second if it must use a double-buffer for the graphics output. Using a double-buffer we get a smoother output and it can be faster for most videodrivers, but it needs the user to indicate when bitblt to the screen if the graphic output is not done by GrGUI functions. We will see it in the last example.


    Example 02. GUI Contexts

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>
    
    int main()
    {
        GUIContext *gctx1, *gctx2, *gctx3;
        GrEvent ev;
    
        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);
    
        GrClearScreen(GrAllocColor(0, 100, 0));
        gctx1 = GUIContextCreate(100, 100, 200, 200, 1);
        gctx2 = GUIContextCreate(150, 150, 250, 250, 1);
        gctx3 = GUIContextCreate(200,  50, 400, 300, 1);
        if (gctx1 == NULL || gctx2 == NULL || gctx3 == NULL) exit(1);
    
        GrTextXY(10, 10, "Test GUIContexts, press any key to continue",
                 GrWhite(), GrNOCOLOR);
    
        GUIContextSaveUnder(gctx1);
        GrSetContext(gctx1->c);
        GrClearContext(GrAllocColor(100, 0, 0));
        GrEventWaitKeyOrClick(&ev);
    
        GUIContextSaveUnder(gctx2);
        GrSetContext(gctx2->c);
        GrClearContext(GrAllocColor(0, 0, 100));
        GrEventWaitKeyOrClick(&ev);
    
        GUIContextSaveUnder(gctx3);
        GrSetContext(gctx3->c);
        GrClearContext(GrAllocColor(200, 200, 0));
        GrEventWaitKeyOrClick(&ev);
    
        GUIContextRestoreUnder(gctx3);
        GUIContextDestroy(gctx3);
        GrEventWaitKeyOrClick(&ev);
    
        GUIContextRestoreUnder(gctx2);
        GUIContextDestroy(gctx2);
        GrEventWaitKeyOrClick(&ev);
    
        GUIContextRestoreUnder(gctx1);
        GUIContextDestroy(gctx1);
        GrEventWaitKeyOrClick(&ev);
    
        GUIEnd();
        GrSetMode(GR_default_text);
    
        return 0;
    }
    

    GUI Contexts are the basic GrGUI container, they provides a screen (or memory if we asked for double-buffer) subcontext to draw in. They can save the actual graphic contents under the context and restore it later.


    Example 03. Menues

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>
    
    #define COMMAND_OPTION1     1
    #define COMMAND_OPTION2     2
    #define COMMAND_OPTION3     3
    #define COMMAND_OPTION4     4
    #define COMMAND_EXIT        5
    
    #define ID_MENU1            1
    #define ID_MENU2            2
    
    void print_line(char *s)
    {
        #define LINE_HIGH 16
        static int ypos = 10;
        
        if (ypos >= GrMaxY() - LINE_HIGH) {
            GrClearContext(GrBlack());
            ypos = 10;
        }
        GrTextXY(10, ypos, s, GrWhite(), GrBlack());
        ypos += LINE_HIGH;
    }
        
    int main()
    {
        static GUIMenuItem itemsm1[6] = {
            {GUI_MI_OPER, 1, "Option &1", '1', NULL, 0, COMMAND_OPTION1, 0},
            {GUI_MI_OPER, 1, "Option &2", '2', NULL, 0, COMMAND_OPTION2, 0},
            {GUI_MI_SEP, 1, "", 0, NULL, 0, 0, 0}, 
            {GUI_MI_MENU, 1, "&Submenu", 'S', NULL, 0, ID_MENU2, 0}, 
            {GUI_MI_SEP, 1, "", 0, NULL, 0, 0, 0}, 
            {GUI_MI_OPER, 1, "E&xit", 'X', NULL, 0, COMMAND_EXIT, 0}};
        static GUIMenu menu1 = {ID_MENU1, 6, 0, itemsm1};
    
        static GUIMenuItem itemsm2[2] = {
            {GUI_MI_OPER, 1, "Option &3", '3', NULL, 0, COMMAND_OPTION3, 0},
            {GUI_MI_OPER, 1, "Option &4", '4', NULL, 0, COMMAND_OPTION4, 0}};
        static GUIMenu menu2 = {ID_MENU2, 2, 0, itemsm2};
        
        GrEvent ev;
        int result;
        char s[81];
    
        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);
    
        GUIMenuRegister(&menu1);
        GUIMenuRegister(&menu2);
    
        print_line("Press R to run menu, Esc to quit");
        while(1) {
            GrEventWait(&ev);
            if (ev.type == GREV_KEY) {
                if (ev.p1 == GrKey_Escape) break;
                if (ev.p1 == 'r' || ev.p1 == 'R') {
                    result = GUIMenuRun(ID_MENU1, 100, 100, 0);
                    sprintf(s,"GUIMenuRun returned %d", result);
                    print_line(s);
                }
            }
            if (ev.type == GREV_COMMAND) {
                if (ev.p1 == COMMAND_EXIT) {
                    print_line("Received COMMAND_EXIT event, exiting in 3 seconds");
                    GrMouseEraseCursor();
                    GrSleep(3000);
                    break;
                }
                sprintf(s,"Received COMMAND event %ld", ev.p1);
                print_line(s);
            }
        }
    
        GUIEnd();
        GrSetMode(GR_default_text);
    
        return 0;
    }
    

    We define menues statically as a list of menu items, they can be Operations, Separator or other Menues. When user selects an Operation a GREV_COMMAND event is generated. You can cancel a menu pressing the Escape key or clicking the mouse outside the menu area.

    Menues must be registered with the "GUIMenuRegister" funtion, so that a menu can call another menu.

    Obviusly a menu uses a GUI Context.


    Example 04. Menu bar

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>
    
    #define COMMAND_OPTION1     1
    #define COMMAND_OPTION2     2
    #define COMMAND_OPTION3     3
    #define COMMAND_OPTION4     4
    #define COMMAND_OPTION5     5
    #define COMMAND_OPTION6     6
    #define COMMAND_EXIT        7
    
    #define ID_MENU1            1
    #define ID_MENU2            2
    #define ID_MENU3            3
    
    void print_line(char *s)
    {
        #define LINE_HIGH 16
        static int ypos = 10;
        
        if (ypos >= GrMaxY() - LINE_HIGH) {
            GrClearContext(GrBlack());
            ypos = 10;
        }
        GrTextXY(10, ypos, s, GrWhite(), GrBlack());
        ypos += LINE_HIGH;
    }
        
    int main()
    {
        static GUIMenuItem itemsm1[6] = {
            {GUI_MI_OPER, 1, "Option &1", '1', NULL, 0, COMMAND_OPTION1, 0},
            {GUI_MI_OPER, 1, "Option &2", '2', NULL, 0, COMMAND_OPTION2, 0},
            {GUI_MI_SEP, 1, "", 0, NULL, 0, 0, 0}, 
            {GUI_MI_MENU, 1, "&Submenu", 'S', NULL, 0, ID_MENU2, 0}, 
            {GUI_MI_SEP, 1, "", 0, NULL, 0, 0, 0}, 
            {GUI_MI_OPER, 1, "E&xit", 'X', "Ctrl+X", GrKey_Control_X, COMMAND_EXIT, 0}};
        static GUIMenu menu1 = {ID_MENU1, 6, 0, itemsm1};
    
        static GUIMenuItem itemsm2[2] = {
            {GUI_MI_OPER, 1, "Option &3", '3', NULL, 0, COMMAND_OPTION3, 0},
            {GUI_MI_OPER, 1, "Option &4", '4', NULL, 0, COMMAND_OPTION4, 0}};
        static GUIMenu menu2 = {ID_MENU2, 2, 0, itemsm2};
    
        static GUIMenuItem itemsm3[4] = {
            {GUI_MI_OPER, 1, "Option &5", '5', NULL, 0, COMMAND_OPTION5, 0},
            {GUI_MI_OPER, 1, "Option &6", '6', NULL, 0, COMMAND_OPTION6, 0},
            {GUI_MI_SEP, 1, "", 0, NULL, 0, 0, 0}, 
            {GUI_MI_MENU, 1, "&Submenu", 'S', NULL, 0, ID_MENU2, 0}};
        static GUIMenu menu3 = {ID_MENU3, 4, 0, itemsm3};
    
        static GUIMenuBarItem mbitems[2] = {
            {"&First_menu", 1, GrKey_Alt_F, ID_MENU1}, 
            {"&Second_menu", 1, GrKey_Alt_S, ID_MENU3}};
        static GUIMenuBar menubar = {2 ,0, mbitems};
    
        GrEvent ev;
        char s[81];
        GrContext *ctx;
    
        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);
    
        GUIMenuRegister(&menu1);
        GUIMenuRegister(&menu2);
        GUIMenuRegister(&menu3);
        GUIMenuBarSet(&menubar);
        GUIMenuBarShow();
        ctx = GrCreateSubContext(0, GUIMenuBarGetHeight(),
                                 GrMaxX(), GrMaxY(), NULL, NULL);
        GrSetContext(ctx);
    
        print_line("Try the MenuBar above");
        while(1) {
            GrEventWait(&ev);
            if (ev.type == GREV_KEY) {
                if (ev.p1 == GrKey_Escape) break;
            }
            if (ev.type == GREV_COMMAND) {
                if (ev.p1 == COMMAND_EXIT) {
                    print_line("Received COMMAND_EXIT event, exiting in 3 seconds");
                    GrMouseEraseCursor();
                    GrSleep(3000);
                    break;
                }
                sprintf(s,"Received COMMAND event %ld", ev.p1);
                print_line(s);
            }
        }
    
        GUIEnd();
        GrSetMode(GR_default_text);
    
        return 0;
    }
    

    The menu bar is statically defined as a list of menu bar items. Each item points to a menu id. You can have only a menu bar at any time that is registered using the "GUIMenuBarSet" function. After that you can show it with the "GUIMenuBarShow" function. When showed you can select a menu clicking with the mouse or using the ALT+letter combination defined. When tied to the menu bar, menu operations can define keyboard shortcuts to acces the operation directly, in our example "Ctrl-X" executes the Exit operation.

    The funtion "GUIMenuBarGetHeight" is important because it reports the menu bar height in pixels, so we can set a screen subcontext as our drawing area without interfere with the menu bar.


    Example 05. GUI Panels

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>
    
    typedef struct {
        GUIPanel *gp;
        GrColor fg;
        GrColor bg;
    } UserData;
    
    void paint_panel1(void *data)
    {
        UserData *ud;
    
        ud = (UserData *)data;
        GrClearContext(ud->bg);
        GrTextXY(10, 10, "This is a simple panel with 1 px border", ud->fg, ud->bg);
        GrTextXY(10, 26, "Press R to reverse colors, C to continue", ud->fg, ud->bg);
    }
    
    void paint_panel2(void *data)
    {
        UserData *ud;
    
        ud = (UserData *)data;
        GrClearContext(ud->bg);
        GrTextXY(10, 10, "This is a panel with 4 px border,", ud->fg, ud->bg);
        GrTextXY(10, 26, "title and scroll bars", ud->fg, ud->bg);
        GrTextXY(10, 42, "Press R to reverse colors, C to continue", ud->fg, ud->bg);
    }
    
    void paint_panel2_title(void *data)
    {
        UserData *ud;
    
        ud = (UserData *)data;
        GrClearContext(ud->fg);
        GrTextXY(2, 2, "This is the panel 2 title", ud->bg, ud->fg);
        GrHLine(0, GrMaxX(), GrMaxY(), ud->bg);
    }
    
    int process_panel_event(void *data, GrEvent *ev)
    {
        UserData *ud;
        GrColor aux;
    
        ud = (UserData *)data;
        if (ev->type == GREV_KEY) {
            if (ev->p1 == 'r' || ev->p1 == 'R') {
                aux = ud->fg;
                ud->fg = ud->bg;
                ud->bg = aux;
                ud->gp->paintcl(data);
                return 1;
            }
            if (ev->p1 == 'c' || ev->p1 == 'C') {
                return -1;
            }
        }
        return 0;
    }
    
    int main()
    {
        GUIPanel *gp1, *gp2;
        GrEvent ev;
        UserData ud;
        int ret;
    
        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);
    
        GrClearScreen(GrAllocColor(0, 100, 0));
        gp1 = GUIPanelCreate(100, 100, GrMaxX()-200, GrSizeY()-200,
                             GUI_PCAPB_SU, 1, 0);
        gp2 = GUIPanelCreate(100, 100, GrMaxX()-200, GrSizeY()-200,
                             GUI_PCAPB_SU|GUI_PCAPB_VSCB|GUI_PCAPB_HSCB, 4, 20);
        if (gp1 == NULL || gp2 == NULL) exit(1);
    
        ud.gp = gp1;
        ud.fg = GrBlack();
        ud.bg = GrWhite();
    
        GUIPanelSetClCallBacks(gp1, paint_panel1, process_panel_event);
        GUIPanelSetUserData(gp1, (void *)&ud);
        
        GUIPanelPaint(gp1, GrBlack(), GrWhite());
        while(1) {
            GrEventWait(&ev);
            ret = GUIPanelProcessEvent(gp1, &ev);
            if (ret == -1) break;
        }
        GUIContextRestoreUnder(gp1->gc);
        GUIPanelDestroy(gp1);
        
        ud.gp = gp2;
        ud.fg = GrAllocColor2(0x555555);
        ud.bg = GrAllocColor2(0x55FFFF);
    
        GUIPanelSetClCallBacks(gp2, paint_panel2, process_panel_event);
        GUIPanelSetTlCallBack(gp2, paint_panel2_title);
        GUIPanelSetUserData(gp2, (void *)&ud);
        
        GUIPanelPaint(gp2, GrBlack(), GrWhite());
        while(1) {
            GrEventWait(&ev);
            ret = GUIPanelProcessEvent(gp2, &ev);
            if (ret == -1) break;
        }
        GUIContextRestoreUnder(gp2->gc);
        GUIPanelDestroy(gp2);
    
        GUIEnd();
        GrSetMode(GR_default_text);
    
        return 0;
    }
    

    A GUI Panel is a more elaborated container on top of a GUI Context, it can have a border, a title area, a client area and Scrollbars. And more important you can attach a client area paint, a title area paint and a event processing functions to them.


    Example 06. Common Dialogs

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>
    
    void print_line(char *s)
    {
        #define LINE_HIGH 16
        static int ypos = 10;
        
        if (ypos >= GrMaxY() - LINE_HIGH) {
            GrClearContext(GrBlack());
            ypos = 10;
        }
        GrTextXY(10, ypos, s, GrWhite(), GrBlack());
        ypos += LINE_HIGH;
    }
        
    int main()
    {
        char *bodytext[2] = {
            "This is a GrGUI common dialog",
            "select one option"};
        GrEvent ev;
        char s[81];
        int result;
    
        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);
    
        print_line("Press 1 to run dialog Yes/No");
        print_line("Press 2 to run dialog Yes/No/Cancel");
        print_line("Esc to quit");
    
        while(1) {
            GrEventWait(&ev);
            if (ev.type == GREV_KEY) {
                if (ev.p1 == GrKey_Escape) break;
                if (ev.p1 == '1') {
                    result = GUICDialogYesNo("Test Yes/No",
                                (void **)bodytext, 2, "Yes", "No");
                    sprintf(s,"Dialog Yes/No returned %d", result);
                    print_line(s);
                }
                if (ev.p1 == '2') {
                    result = GUICDialogYesNoCancel("Test Yes/No/Cancel",
                                (void **)bodytext, 2, "Yes", "No", "Cancel");
                    sprintf(s,"Dialog Yes/No returned %d", result);
                    print_line(s);
                }
            }
        }
    
        GUIEnd();
        GrSetMode(GR_default_text);
    
        return 0;
    }
    

    A GUI Dialog is a another container on top of a GUI Panel. GrGUI provides some common dialogs, one of them was used in the first example to show some information to the user. The other two, to ask a Yes/No question or a Yes/No/Cancel question to the user, are showed in this example.

    In example 10 we will see how to construct our own dialog.


    Example 07. GUI Tiles

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>
    
    typedef struct {
        GUITile *gt;
        GrColor fg;
        GrColor bg;
    } UserData;
    
    void paint_tile1(void *data)
    {
        UserData *ud;
    
        ud = (UserData *)data;
        GrClearContext(ud->bg);
        GrTextXY(10, 16, "This is a passive tile with 1 px border", ud->fg, ud->bg);
    }
    
    void paint_tile2(void *data)
    {
        UserData *ud;
    
        ud = (UserData *)data;
        GrClearContext(ud->bg);
        GrTextXY(10, 10, "This is an active tile", ud->fg, ud->bg);
        GrTextXY(10, 26, "When selected:", ud->fg, ud->bg);
        GrTextXY(10, 42, "  Press R to reverse colors", ud->fg, ud->bg);
        GrTextXY(10, 58, "  Esc to finish", ud->fg, ud->bg);
    }
    
    void paint_tile3(void *data)
    {
        UserData *ud;
    
        ud = (UserData *)data;
        GrClearContext(ud->bg);
        GrTextXY(10, 10, "This is a second active tile,", ud->fg, ud->bg);
        GrTextXY(10, 26, "When selected:", ud->fg, ud->bg);
        GrTextXY(10, 42, "  Press R to reverse colors", ud->fg, ud->bg);
        GrTextXY(10, 58, "  Esc to finish", ud->fg, ud->bg);
    }
    
    void paint_tile4(void *data)
    {
        UserData *ud;
    
        ud = (UserData *)data;
        GrClearContext(ud->bg);
        GrTextXY(10, 16, "This is a passive tile borderless", ud->fg, ud->bg);
    }
    
    int process_tile_event(void *data, GrEvent *ev)
    {
        UserData *ud;
        GrColor aux;
    
        ud = (UserData *)data;
        if (ev->type == GREV_KEY) {
            if (ev->p1 == 'r' || ev->p1 == 'R') {
                aux = ud->fg;
                ud->fg = ud->bg;
                ud->bg = aux;
                ud->gt->p->paintcl(data);
                return 1;
            }
            if (ev->p1 == GrKey_Escape) {
                return -1;
            }
        }
        return 0;
    }
    
    int main()
    {
        #define IDT1 1
        #define IDT2 2
        #define IDT3 3
        #define IDT4 4
    
        GUITile *gt1, *gt2, *gt3, *gt4;
        UserData ud1, ud2, ud3, ud4;
        GrEvent ev;
        int ret;
    
        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);
    
        //GrClearScreen(GrAllocColor(0, 100, 0));
        gt1 = GUITileCreate(IDT1, GUI_TT_STATICBORDER, 0, 0,
                            GrSizeX(), 50);
        gt2 = GUITileCreate(IDT2, GUI_TT_ACTIVEBORDER, 0, 50,
                            GrSizeX()/2, GrSizeY()-100);
        gt3 = GUITileCreate(IDT3, GUI_TT_ACTIVEBORDER, GrSizeX()/2, 50,
                            GrSizeX()/2, GrSizeY()-100);
        gt4 = GUITileCreate(IDT4, GUI_TT_BORDERLESS, 0,
                            GrSizeY()-50, GrSizeX(), 50);
        if (gt1 == NULL || gt2 == NULL || gt3 == NULL || gt4 == NULL) exit(1);
        
        GUIPanelSetClCallBacks(gt1->p, paint_tile1, NULL);
        GUIPanelSetUserData(gt1->p, (void *)&ud1);
        ud1.gt = gt1;
        ud1.fg = GrWhite();
        ud1.bg = GrAllocColor2(0x00AAAA);
        GUIPanelSetClCallBacks(gt2->p, paint_tile2, process_tile_event);
        GUIPanelSetUserData(gt2->p, (void *)&ud2);
        ud2.gt = gt2;
        ud2.fg = GrWhite();
        ud2.bg = GrAllocColor2(0x00AA00);
        GUIPanelSetClCallBacks(gt3->p, paint_tile3, process_tile_event);
        GUIPanelSetUserData(gt3->p, (void *)&ud3);
        ud3.gt = gt3;
        ud3.fg = GrWhite();
        ud3.bg = GrAllocColor2(0x00AA00);
        GUIPanelSetClCallBacks(gt4->p, paint_tile4, NULL);
        GUIPanelSetUserData(gt4->p, (void *)&ud4);
        ud4.gt = gt4;
        ud4.fg = GrWhite();
        ud4.bg = GrAllocColor2(0x555555);
    
        GUITileRegister(gt1);
        GUITileRegister(gt2);
        GUITileRegister(gt3);
        GUITileRegister(gt4);
    
        GUITilePaint(IDT1);
        GUITilePaint(IDT2);
        GUITilePaint(IDT3);
        GUITilePaint(IDT4);
    
        while(1) {
            GrEventWait(&ev);
            ret = GUITilesProcessEvent(&ev);
            if (ret == -1) break;
        }
        
        GUITilesDestroyAll();
        
        GUIEnd();
        GrSetMode(GR_default_text);
    
        return 0;
    }
    

    GUI Tiles are containers too, on top of a GUI Panel. But the basic idea of GUI Tiles is to divide the screen area in rectangular areas. Each tile can be passive (only to show information) or active (it can get user input).

    Only one of the active tiles have the focus, and you can change it using the mouse or the Alt+RigtCursor, Alt+LeftCursor keystrokes. To achieve this functionality, GUI Tiles must be registered using the "GUITileRegister" function.


    Example 08. GUI Objects

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <grgui.h>
    #include <mgrxcolr.h>
    
    
    #define COMMAND_EXIT         1
    #define COMMAND_SWITCH_L1    2
    #define COMMAND_SWITCH_L2    3
    #define COMMAND_SWITCH_L3    4
    #define COMMAND_SWITCH_L4    5
    #define COMMAND_GET_DATA     6
    
    char *listopt[5] = { "Primera opción", "Segunda opción", 
        "Tercera opción", "Cuarta opción", "Quinta opción"};
    char bdtline1[81], bdtline2[81], bdtline3[81], bdtline4[81];
    char *buf_test[4] = {bdtline1, bdtline2, bdtline3, bdtline4};
    
    void add_line_to_buf_test(char *s)
    {
        int i;
        
        for (i=0; i<3; i++)
            memcpy(buf_test[i], buf_test[i+1], 81);
        
        strncpy(buf_test[3], s, 80);
        buf_test[3][80] = '\0';
    }
    
    int process_go_event(GUIGroup *go, GrEvent *ev)
    {
        char aux[81];
        char *s, *sonoff[4];
        char *son = "On";
        char *soff = "Off";
        int i, status;
    
        if (ev->type == GREV_COMMAND) {
            switch (ev->p1) {
                case COMMAND_EXIT :
                    return -1;
                case COMMAND_SWITCH_L1 :
                    GUIGroupSetOn(go, 0, 1);
                    return 1;
                case COMMAND_SWITCH_L2 :
                    GUIGroupSetOn(go, 1, 1);
                    return 1;
                case COMMAND_SWITCH_L3 :
                    GUIGroupSetOn(go, 2, 1);
                    return 1;
                case COMMAND_SWITCH_L4 :
                    GUIGroupSetOn(go, 3, 1);
                    return 1;
                case COMMAND_GET_DATA :
                    for (i=0; i<4; i++) {
                        status = GUIGroupGetOn(go, i);
                        sonoff[i] = status ? son : soff;
                    }
                    sprintf(aux, "Lights status 1:%s 2:%s 3:%s 4:%s",
                            sonoff[0], sonoff[1], sonoff[2], sonoff[3]);
                    add_line_to_buf_test(aux);
                    s = GUIGroupGetText(go, 5, GR_UTF8_TEXT);
                    sprintf(aux, "Entry: %s", s);
                    add_line_to_buf_test(aux);
                    free(s);
                    GUIGroupRePaintObject(go, 15);
                    return 1;
            }
        }
        if (ev->type == GREV_FCHANGE) {
            sprintf(aux, "Field changed, p1=%ld, p2=%ld", ev->p1, ev->p2);
            add_line_to_buf_test(aux);
            GUIGroupRePaintObject(go, 15);
        }
    
        return GUIGroupProcessEvent(go, ev);
    }
    
    int main()
    {
        GUIGroup *go;
        GrEvent ev;
    
        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);
        GrGenEgaColorTable();
        GrClearContext(EGAC_DARKGRAY);
    
        GUIObjectsSetColors(EGAC_BLACK, EGAC_LIGHTGRAY, EGAC_DARKGRAY);
        go = GUIGroupCreate(17, 160, 70);
        if (go == NULL) exit(1);
        GUIObjectSetLight(&(go->o[0]), 0,   0, 0, 80, 32, EGAC_LIGHTGREEN, EGAC_BLACK, "Light 1", 0);
        GUIObjectSetLight(&(go->o[1]), 1,  80, 0, 80, 32, EGAC_LIGHTGREEN, EGAC_BLACK, "Light 2", 1);
        GUIObjectSetLight(&(go->o[2]), 2, 160, 0, 80, 32, EGAC_LIGHTRED, EGAC_BLACK, "Light 3", 0);
        GUIObjectSetLight(&(go->o[3]), 3, 240, 0, 80, 32, EGAC_LIGHTCYAN, EGAC_BLACK, "Light 4", 1);
        GUIObjectSetLabel(&(go->o[4]), 4,   0, 40, 160, 30, GrNOCOLOR, EGAC_WHITE, "Editable field");
        GUIObjectSetEntry(&(go->o[5]), 5, 160, 40, 160, 30, EGAC_WHITE, EGAC_BLACK, 30, "entry field");
        GUIObjectSetLabel(&(go->o[6]), 6,   0, 80, 160, 30, GrNOCOLOR, EGAC_WHITE, "List field #1");
        GUIObjectSetList(&(go->o[7]),  7, 160, 80, 160, 30, EGAC_WHITE, EGAC_BLACK, (void **)listopt, 5, 3, 1);
        GUIObjectSetLabel(&(go->o[8]), 8,   0, 120, 160, 30, GrNOCOLOR, EGAC_WHITE, "List field #2");
        GUIObjectSetList(&(go->o[9]),  9, 160, 120, 160, 30, EGAC_WHITE, EGAC_BLACK, (void **)listopt, 5, 5, 2);
        GUIObjectSetButton(&(go->o[10]), 10,   0, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L1", COMMAND_SWITCH_L1, 0, 0);
        GUIObjectSetButton(&(go->o[11]), 11,  40, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L2", COMMAND_SWITCH_L2, 0, 0);
        GUIObjectSetButton(&(go->o[12]), 12,  80, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L3", COMMAND_SWITCH_L3, 0, 0);
        GUIObjectSetButton(&(go->o[13]), 13, 120, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L4", COMMAND_SWITCH_L4, 0, 0);
        GUIObjectSetButton(&(go->o[14]), 14, 160, 160, 160, 40, EGAC_CYAN, EGAC_WHITE, "Get data", COMMAND_GET_DATA, 0, 0);
        GUIObjectSetText(&(go->o[15]), 15, 0, 210, 320, 80, EGAC_LIGHTGRAY, EGAC_BLACK, (void **)buf_test, 4, GR_ALIGN_LEFT, NULL);
        GUIObjectSetButton(&(go->o[16]), 16, 80, 300, 160, 40, EGAC_GREEN, EGAC_WHITE, "Exit", COMMAND_EXIT, 0, 0);
        GUIGroupSetSelected(go, 16, 0);
    
        GUIGroupPaint(go);
    
        while(1) {
            GrEventRead(&ev);
            if ((ev.type == GREV_KEY) && (ev.p1 == GrKey_Escape)) break;
            if (process_go_event(go, &ev) < 0) break;
        }
    
        GUIGroupDestroy(go);
        GUIEnd();
        GrSetMode(GR_default_text);
    
        return 0;
    }
    

    GUI Objects are widgets, small rectangular objects with some function. There are six objects types in GrGUI:

    GUI Objects only can live in a GUI Group. You first create a group and them defines each element to be a GUI Object of any type. All the objects in a group are managed as a whole. In this example we create a group with objects of all types. You can see that it is not necesary to attach a group to a container. We will do it in the next two examples.


    Example 09. GUI Tiles with Objects and TextArea

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <grgui.h>
    #include <mgrxcolr.h>
    
    #define COMMAND_EXIT         1
    #define COMMAND_LOAD         2
    #define COMMAND_SAVE         3
    
    typedef struct {
        GUITextArea *ta;
        GUIGroup *go;
    } UserData;
    
    void paint_tl1(void *data)
    {
        UserData *ud;
    
        ud = (UserData *)data;
        GrClearContext(EGAC_LIGHTGRAY);
        GUIGroupPaint(ud->go);
    }
    
    void paint_tl2(void *data)
    {
        UserData *ud;
    
        ud = (UserData *)data;
        GUITAReDraw(ud->ta);
    }
    
    int process_tl1_event(void *data, GrEvent *ev)
    {
        UserData *ud;
        GUITAStatus tast;
        FILE *fin, *fout;
        char aux[251];
        char *s;
        int len, i;
    
        ud = (UserData *)data;
        if (ev->type == GREV_COMMAND) {
            switch (ev->p1) {
                case COMMAND_EXIT :
                    return -1;
                case COMMAND_LOAD :
                    s = GUIGroupGetText(ud->go, 1, GR_UTF8_TEXT);
                    fin = fopen(s, "r");
                    free(s);
                    if (fin == NULL) return 1;
                    GUITAHideCursor(ud->ta);
                    while (fgets(aux, 250, fin) != NULL) {
                        len = strlen(aux);
                        if (len>0 && aux[len-1]=='\n') aux[len-1] = '\0';
                        GUITADrawString(ud->ta, aux, 0, GR_UTF8_TEXT);
                        GUITANewLine(ud->ta);
                    }
                    GUITAShowCursor(ud->ta);
                    fclose(fin);
                    return 1;
                case COMMAND_SAVE :
                    s = GUIGroupGetText(ud->go, 3, GR_UTF8_TEXT);
                    fout = fopen(s, "w");
                    free(s);
                    if (fout == NULL) return 1;
                    GUITAGetStatus(ud->ta, &tast);
                    for (i=0; i<tast.nlines; i++) {
                        s = GUITAGetString(ud->ta, i, GR_UTF8_TEXT);
                        if (s == NULL) break;
                        fputs(s, fout);
                        putc( '\n', fout);
                        free(s);
                    }
                    fclose(fout);
                    return 1;
            }
        }
    
        return GUIGroupProcessEvent(ud->go, ev);
    }
    
    int process_tl2_event(void *data, GrEvent *ev)
    {
        UserData *ud;
    
        ud = (UserData *)data;
        return GUITAProcessEvent(ud->ta, ev);
    }
    
    int main()
    {
        #define IDT1 1
        #define IDT2 2
    
        GUITile *gt1, *gt2;
        GUITextArea *ta1;
        GUIGroup *go1;
        UserData ud;
        GrEvent ev;
        int ret;
    
        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);
        GrGenEgaColorTable();
        GUIScrollbarsSetColors(EGAC_LIGHTGRAY, EGAC_DARKGRAY);
        GUITilesSetColors(EGAC_BLACK, EGAC_LIGHTGRAY, EGAC_YELLOW);
        GUIObjectsSetColors(EGAC_BLACK, EGAC_LIGHTGRAY, EGAC_DARKGRAY);
    
        gt1 = GUITileCreate(IDT1, GUI_TT_ACTIVEBORDER, 0, 0,
                            168, GrSizeY());
        gt2 = GUITileCreate(IDT2, GUI_TT_ACTIVEBWSCB, 168, 0,
                            GrSizeX()-168, GrSizeY());
        if (gt1 == NULL || gt2 == NULL) exit(1);
        
        GUITileRegister(gt1);
        GUITileRegister(gt2);
    
        go1 = GUIGroupCreate(5, 10, 32);
        GUIObjectSetButton(&(go1->o[0]), 0, 0,   0, 140, 40, EGAC_GREEN, EGAC_WHITE, "Load File", COMMAND_LOAD, 0, 0);
        GUIObjectSetEntry(&(go1->o[1]),  1, 0,  44, 140, 30, EGAC_WHITE, EGAC_BLACK, 30, "inputfile");
        GUIObjectSetButton(&(go1->o[2]), 2, 0,  84, 140, 40, EGAC_GREEN, EGAC_WHITE, "Save File", COMMAND_SAVE, 0, 0);
        GUIObjectSetEntry(&(go1->o[3]),  3, 0, 128, 140, 30, EGAC_WHITE, EGAC_BLACK, 30, "outputfile");
        GUIObjectSetButton(&(go1->o[4]), 4, 0, 168, 140, 40, EGAC_RED, EGAC_WHITE, "Exit", COMMAND_EXIT, 0, 0);
        GUIGroupSetSelected(go1, 0, 0);
        GUIGroupSetPanel(go1, gt1->p);
    
        ta1 = GUITACreate(gt2->p, NULL, 10000);
        if (ta1 == NULL) exit(1);
        GUITASetBgColor(ta1, EGAC_DARKGRAY);
        GUITASetTextColors(ta1, EGAC_WHITE, EGAC_DARKGRAY);
        GUITASetCursorColor(ta1, EGAC_YELLOW);
        GUITAClear(ta1);
    
        GUIPanelSetClCallBacks(gt1->p, paint_tl1, process_tl1_event);
        GUIPanelSetClCallBacks(gt2->p, paint_tl2, process_tl2_event);
        GUIPanelSetUserData(gt1->p, (void *)&ud);
        GUIPanelSetUserData(gt2->p, (void *)&ud);
        ud.ta = ta1;
        ud.go = go1;
    
        GUITilePaint(IDT1);
        GUITilePaint(IDT2);
        GUITAShowCursor(ta1);
    
        while(1) {
            GrEventRead(&ev);
            ret = GUITilesProcessEvent(&ev);
            if (ret == -1) break;
        }
        
        GUIGroupDestroy(go1);
        GUITADestroy(ta1);
        GUITilesDestroyAll();
        
        GUIEnd();
        GrSetMode(GR_default_text);
    
        return 0;
    }
    

    In this example we divide the screen with two active GUI Tiles, we attach a GUI Group of object to one Tile and a GUI TextArea to the other one.

    A GUI TextArea is a window over a editable text. So in our present example we have a simple text editor.


    Example 10. GUI Dialog with Objects

    #include <stdlib.h>
    #include <stdio.h>
    #include <string.h>
    #include <grgui.h>
    #include <mgrxcolr.h>
    
    #define COMMAND_OK           1
    #define COMMAND_SWITCH_L1    2
    #define COMMAND_SWITCH_L2    3
    #define COMMAND_SWITCH_L3    4
    #define COMMAND_SWITCH_L4    5
    #define COMMAND_GET_DATA     6
    
    char *listopt[5] = { "Primera opción", "Segunda opción", 
        "Tercera opción", "Cuarta opción", "Quinta opción"};
    char bdtline1[81], bdtline2[81], bdtline3[81], bdtline4[81];
    char *buf_test[4] = {bdtline1, bdtline2, bdtline3, bdtline4};
    
    void print_line(char *s)
    {
        #define LINE_HIGH 16
        static int ypos = 10;
        
        if (ypos >= GrMaxY() - LINE_HIGH) {
            GrClearContext(GrBlack());
            ypos = 10;
        }
        GrTextXY(10, ypos, s, GrWhite(), GrBlack());
        ypos += LINE_HIGH;
    }
        
    void add_line_to_buf_test(char *s)
    {
        int i;
        
        for (i=0; i<3; i++)
            memcpy(buf_test[i], buf_test[i+1], 81);
        
        strncpy(buf_test[3], s, 80);
        buf_test[3][80] = '\0';
    }
    
    int process_dlg_event(void *udata, GrEvent *ev)
    {
        GUIDialog *d = (GUIDialog *)udata;
        GUIGroup *go = (GUIGroup *)(d->exdata);
        char aux[81];
        char *s, *sonoff[4];
        char *son = "On";
        char *soff = "Off";
        int i, status;
    
        if (ev->type == GREV_COMMAND) {
            switch (ev->p1) {
                case COMMAND_OK :
                    return -1;
                case COMMAND_SWITCH_L1 :
                    GUIGroupSetOn(go, 0, 1);
                    return 1;
                case COMMAND_SWITCH_L2 :
                    GUIGroupSetOn(go, 1, 1);
                    return 1;
                case COMMAND_SWITCH_L3 :
                    GUIGroupSetOn(go, 2, 1);
                    return 1;
                case COMMAND_SWITCH_L4 :
                    GUIGroupSetOn(go, 3, 1);
                    return 1;
                case COMMAND_GET_DATA :
                    for (i=0; i<4; i++) {
                        status = GUIGroupGetOn(go, i);
                        sonoff[i] = status ? son : soff;
                    }
                    sprintf(aux, "Lights status 1:%s 2:%s 3:%s 4:%s",
                            sonoff[0], sonoff[1], sonoff[2], sonoff[3]);
                    add_line_to_buf_test(aux);
                    s = GUIGroupGetText(go, 5, GR_UTF8_TEXT);
                    sprintf(aux, "Entry: %s", s);
                    add_line_to_buf_test(aux);
                    free(s);
                    GUIGroupRePaintObject(go, 15);
                    return 1;
            }
        }
        if (ev->type == GREV_FCHANGE) {
            sprintf(aux, "Field changed, p1=%ld, p2=%ld", ev->p1, ev->p2);
            add_line_to_buf_test(aux);
            GUIGroupRePaintObject(go, 15);
        }
    
        return GUIGroupProcessEvent(go, ev);
    }
    
    int main()
    {
        GUIGroup *go;
        GUIDialog *d;
        GrEvent ev;
        char s[81];
        int result, i;
    
        GrSetMode(GR_default_graphics);
        GUIInit(1, 0);
        GrGenEgaColorTable();
    
        GUIObjectsSetColors(EGAC_BLACK, EGAC_LIGHTGRAY, EGAC_DARKGRAY);
        GUIDialogsSetColors(EGAC_BLACK, EGAC_YELLOW, EGAC_BLUE, EGAC_WHITE);
        go = GUIGroupCreate(17, 160, 70);
        if (go == NULL) exit(1);
        GUIObjectSetLight(&(go->o[0]), 0,   0, 0, 80, 32, EGAC_LIGHTGREEN, EGAC_BLACK, "Light 1", 0);
        GUIObjectSetLight(&(go->o[1]), 1,  80, 0, 80, 32, EGAC_LIGHTGREEN, EGAC_BLACK, "Light 2", 1);
        GUIObjectSetLight(&(go->o[2]), 2, 160, 0, 80, 32, EGAC_LIGHTRED, EGAC_BLACK, "Light 3", 0);
        GUIObjectSetLight(&(go->o[3]), 3, 240, 0, 80, 32, EGAC_LIGHTCYAN, EGAC_BLACK, "Light 4", 1);
        GUIObjectSetLabel(&(go->o[4]), 4,   0, 40, 160, 30, GrNOCOLOR, EGAC_WHITE, "Editable field");
        GUIObjectSetEntry(&(go->o[5]), 5, 160, 40, 160, 30, EGAC_WHITE, EGAC_BLACK, 30, "entry field");
        GUIObjectSetLabel(&(go->o[6]), 6,   0, 80, 160, 30, GrNOCOLOR, EGAC_WHITE, "List field #1");
        GUIObjectSetList(&(go->o[7]),  7, 160, 80, 160, 30, EGAC_WHITE, EGAC_BLACK, (void **)listopt, 5, 3, 1);
        GUIObjectSetLabel(&(go->o[8]), 8,   0, 120, 160, 30, GrNOCOLOR, EGAC_WHITE, "List field #2");
        GUIObjectSetList(&(go->o[9]),  9, 160, 120, 160, 30, EGAC_WHITE, EGAC_BLACK, (void **)listopt, 5, 5, 2);
        GUIObjectSetButton(&(go->o[10]), 10,   0, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L1", COMMAND_SWITCH_L1, 0, 0);
        GUIObjectSetButton(&(go->o[11]), 11,  40, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L2", COMMAND_SWITCH_L2, 0, 0);
        GUIObjectSetButton(&(go->o[12]), 12,  80, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L3", COMMAND_SWITCH_L3, 0, 0);
        GUIObjectSetButton(&(go->o[13]), 13, 120, 160, 40, 40, EGAC_CYAN, EGAC_WHITE, "L4", COMMAND_SWITCH_L4, 0, 0);
        GUIObjectSetButton(&(go->o[14]), 14, 160, 160, 160, 40, EGAC_CYAN, EGAC_WHITE, "Get data", COMMAND_GET_DATA, 0, 0);
        GUIObjectSetText(&(go->o[15]), 15, 0, 210, 320, 80, EGAC_LIGHTGRAY, EGAC_BLACK, (void **)buf_test, 4, GR_ALIGN_LEFT, NULL);
        GUIObjectSetButton(&(go->o[16]), 16, 80, 300, 160, 40, EGAC_GREEN, EGAC_WHITE, "Exit", COMMAND_OK, 0, 0);
        GUIGroupSetSelected(go, 16, 0);
    
        d = GUIGroupDialogCreate("Test various objects", go, process_dlg_event);
        if (d == NULL) exit(1);
    
        print_line("Press D to run dialog");
        print_line("Esc to quit");
    
        while(1) {
            GrEventRead(&ev);
            if (ev.type == GREV_KEY) {
                if (ev.p1 == GrKey_Escape) break;
                if (ev.p1 == 'd' || ev.p1 == 'D') {
                    result = GUIDialogRun(d);
                    sprintf(s,"Dialog returned %d, Text object contens:", result);
                    print_line(s);
                    for (i=0; i<4; i++) 
                        print_line(buf_test[i]);
                }
            }
        }
    
        GUIDialogDestroy(d);
        GUIGroupDestroy(go);
        GUIEnd();
        GrSetMode(GR_default_text);
    
        return 0;
    }
    

    Now we reuse the GUi Group we create in the example 8 and attach it to a dialog to show we can have complex dialogs with GrGUI.


    Example 11. Fonts, Colors and Double Buffer

    #include <stdlib.h>
    #include <stdio.h>
    #include <grgui.h>
    #include "mgrxcolr.h"
    
    #if defined(__MSDOS__) || defined(__WIN32__)
    #define JPGIMGBG  "..\\testimg\\jpeg4.jpg"
    #else
    #define JPGIMGBG  "../testimg/jpeg4.jpg"
    #endif
    
    int main()
    {
        char *abouttext[4] = {
            "Welcome to MGRX and GrGUI",
            "MGRX is a small C 2D graphics library",
            "and GrGUI a miniGUI on top of MGRX",
            "visit mgrx.fgrim.com for more info"};
        GrContext *globctx;
    
        GrSetMode(GR_width_height_bpp_graphics, 640, 480, 32);
        GrGenWebColorTable();
        GrSetFontPath("../fonts/;./");
        GUIInit(1, 1);
    
        GUIObjectsSetColors(WEBC_KHAKI, WEBC_PERU, WEBC_SIENNA);
        GUIObjectsSetFontByName("tmgrx16b.fnt");
        GUIDialogsSetColors(WEBC_BLACK, WEBC_ORANGE, WEBC_MAROON,
                            WEBC_ANTIQUEWHITE);
        GUICDialogsSetColors(WEBC_TAN, WEBC_BLACK);
        GUIDialogsSetTitleFontByName("ncen40bi.fnt");
        GUICDialogsSetFontByName("tmgrx18b.fnt");
    
        globctx = GUIGetGlobalContext();
        GrSetContext(globctx);
    
        if (GrNumColors() > 256 && GrJpegSupport()) {
            GrLoadContextFromJpeg(NULL, JPGIMGBG, 1);
        } else {
            GrFilledBox(0, 0, GrMaxX(), GrMaxY(), WEBC_GOLDENROD);
        }
        GUIGlobalBltRectToScreen(0, 0, GrScreenX()-1, GrScreenY()-1);
    
        GUICDialogInfo("Hello GrGUI", (void **)abouttext, 4, "Okey");
    
        GUIEnd();
        GrSetMode(GR_default_text);
    
        return 0;
    }
    

    Until now sometimes we have left the default black and whiite colors, sometime we have used GrGUI functions to set the default colors of some elements. In all cases we have left the default font.

    In this last example we return to our first hello GrGUI example but changing default colors and fonts. We draw a background image too and activate the double buffer (second parameter of "GUIInit" function).

    While we use the GUI functions to do the drawing there are no difference, but in other case we have to use special code. You can notice that to draw the image background instead doing it in the Screen context we use the "GUIGetGlobalContext" to get the memory context GrGUI is using to draw, and after putting our image in, we bitblt to the real Screen using "GUIGlobalBltRectToScreen".

    So it is different to code with or without double buffer. The good news is that "GUIGetGlobalContext" returns the Screen context if GrGUI isn't using double buffer and "GUIGlobalBltRectToScreen" doesn't do anything in that case. So we can code as the double buffer is ever set and it works ok without double buffer.


    More information

    I think the eleven examples covers most of the GrGUI functionality, but GrGUI is a small GUI, you can use the "grgui.h" main include file as a reference, or even read the source code if nedeed.

    A big example is the "demintl2.c" program, it use is mainly to test the international features of MGRX, but it makes extensive use of GrGUI too.

    Enjoy.