Mosaic: Customizing

We've already created some menu items to set different color schemes and game levels. Now we will make them work.

10.1 - Menu items theory

When you click on a menu item, a WM_COMMAND message is sent to the window that owns them. Because this message is also sent by buttons and other controls, a destinction is needed between menus and other controls. wNotifyCode is 0 if the message is from a menu (wNotifyCode is the high word of wParam). The low word of wParam is the menu item ID, these are defined in the resource (and in your include file).

10.2 - A few new definitions

In mosaic.inc:

EASY                equ     20
MEDIUM              equ     80
HARD                equ     200

In your .data?:

Difficulty          dd  ?

The EASY, MEDIUM and HARD constants are used to identify a game level. Their values are actually the number of shuffles when a new game is started. Difficulty is a variable that holds the current difficulty (one of the constants).

10.3 - WM_COMMAND handler

(note: the ProcessMenuItems is not optimized at all, code is repeated many times and all this could be done more efficient, but this way it's easier to understand)

In WndProc:

    .ELSEIF eax==WM_COMMAND
        mov     eax, wParam
        shr     ax, 16
        .IF        ax==0 ; menu notification
            invoke    ProcessMenuItems, hWnd, wParam
        .ENDIF

In .code:

ProcessMenuItems    PROTO    STDCALL :DWORD, :DWORD

ProcessMenuItems proc hWnd:DWORD, wParam:DWORD
mov        eax, wParam
;-------------------------------------------------------------------------------
; Difficuly
;-------------------------------------------------------------------------------
.IF    ax==MI_MEDIUM
    invoke    CheckMenuItem, hMenu, MI_EASY, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_HARD, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_MEDIUM, MF_CHECKED
    mov        Difficulty, MEDIUM
.ELSEIF ax==MI_EASY
    invoke    CheckMenuItem, hMenu, MI_EASY, MF_CHECKED
    invoke    CheckMenuItem, hMenu, MI_HARD, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_MEDIUM, MF_UNCHECKED
    mov        Difficulty, EASY
.ELSEIF ax==MI_HARD
    invoke    CheckMenuItem, hMenu, MI_EASY, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_HARD, MF_CHECKED
    invoke    CheckMenuItem, hMenu, MI_MEDIUM, MF_UNCHECKED
    mov        Difficulty, HARD
;-------------------------------------------------------------------------------
; Image type
;-------------------------------------------------------------------------------
.ELSEIF ax==MI_USEFILE
    invoke    CheckMenuItem, hMenu, MI_USEFILE, MF_CHECKED
    invoke    CheckMenuItem, hMenu, MI_USENUMBERS, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_USESTANDARD, MF_UNCHECKED
    invoke    SendMessage, hToolbar, TB_CHECKBUTTON, MI_USEFILE, TRUE
    ;--- yet to do ---
.ELSEIF ax==MI_USENUMBERS
    invoke    CheckMenuItem, hMenu, MI_USEFILE, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_USENUMBERS, MF_CHECKED
    invoke    CheckMenuItem, hMenu, MI_USESTANDARD, MF_UNCHECKED
    invoke    SendMessage, hToolbar, TB_CHECKBUTTON, MI_USENUMBERS, TRUE
    invoke    SetBitmap, hWnd, IMAGETYPE_NUMBERS
    invoke    InvalidateRect, hWnd, NULL, FALSE
.ELSEIF ax==MI_USESTANDARD
    invoke    CheckMenuItem, hMenu, MI_USEFILE, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_USENUMBERS, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_USESTANDARD, MF_CHECKED
    invoke    SendMessage, hToolbar, TB_CHECKBUTTON, MI_USESTANDARD, TRUE
    invoke    SetBitmap, hWnd, IMAGETYPE_STANDARD
    invoke    InvalidateRect, hWnd, NULL, FALSE
;-------------------------------------------------------------------------------
; Color Scheme
;-------------------------------------------------------------------------------
.ELSEIF ax==MI_COLORBLUE
    invoke    SetColors, eax
    invoke    CheckMenuItem, hMenu, MI_COLORRED, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_COLORBLUE, MF_CHECKED
    invoke    CheckMenuItem, hMenu, MI_COLORGREEN, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_COLORUGLY, MF_UNCHECKED
    invoke    SetBitmap, hWnd, CurImageType
    invoke    InvalidateRect, hWnd, NULL, FALSE
.ELSEIF ax==MI_COLORRED
    invoke    SetColors, eax
    invoke    CheckMenuItem, hMenu, MI_COLORRED, MF_CHECKED
    invoke    CheckMenuItem, hMenu, MI_COLORBLUE, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_COLORGREEN, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_COLORUGLY, MF_UNCHECKED
    invoke    SetBitmap, hWnd, CurImageType
    invoke    InvalidateRect, hWnd, NULL, FALSE
.ELSEIF ax==MI_COLORGREEN
    invoke    SetColors, eax
    invoke    CheckMenuItem, hMenu, MI_COLORRED, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_COLORBLUE, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_COLORGREEN, MF_CHECKED
    invoke    CheckMenuItem, hMenu, MI_COLORUGLY, MF_UNCHECKED
    invoke    SetBitmap, hWnd, CurImageType
    invoke    InvalidateRect, hWnd, NULL, FALSE
.ELSEIF ax==MI_COLORUGLY
    invoke    SetColors, eax
    invoke    CheckMenuItem, hMenu, MI_COLORRED, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_COLORBLUE, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_COLORGREEN, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_COLORUGLY, MF_CHECKED
    invoke    SetBitmap, hWnd, CurImageType
    invoke    InvalidateRect, hWnd, NULL, FALSE
;-------------------------------------------------------------------------------
; New game
;-------------------------------------------------------------------------------
.ELSEIF ax==MI_NEWGAME
    invoke    NewGame, hWnd
;-------------------------------------------------------------------------------
; Open Bitmap
;-------------------------------------------------------------------------------
.ELSEIF ax==MI_OPENBITMAP
    ; --- yet to do ---
.ENDIF
ret
ProcessMenuItems endp

The handler in WndProc checks if the message is a menu item notification, and if it is, it's ID and the main window handle is passed to ProcessMenuItems. Because we gave the toolbarbuttons the same IDs as the menu items, a click on a toolbarbutton will be handled the same way as a menu item. ProcessMenuItem compares the ID to all the menu ID constants and handles them seperately.

The code above is quite big but quite simple. We will take the difficulty menu items as example:

;-------------------------------------------------------------------------------
; Difficuly
;-------------------------------------------------------------------------------
.IF    ax==MI_MEDIUM
    invoke    CheckMenuItem, hMenu, MI_EASY, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_HARD, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_MEDIUM, MF_CHECKED
    mov        Difficulty, MEDIUM
.ELSEIF ax==MI_EASY
    invoke    CheckMenuItem, hMenu, MI_EASY, MF_CHECKED
    invoke    CheckMenuItem, hMenu, MI_HARD, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_MEDIUM, MF_UNCHECKED
    mov        Difficulty, EASY
.ELSEIF ax==MI_HARD
    invoke    CheckMenuItem, hMenu, MI_EASY, MF_UNCHECKED
    invoke    CheckMenuItem, hMenu, MI_HARD, MF_CHECKED
    invoke    CheckMenuItem, hMenu, MI_MEDIUM, MF_UNCHECKED
    mov        Difficulty, HARD

If we choose medium difficulty (MI_MEDIUM), we first check that menu item (because we can choose between 3 menu items and the selected item is checked) with CheckMenuItem. MF_CHECKED as 3rd parameter will check a menu item, MF_UNCHECKED will uncheck a menu item. CheckMenuItem also needs the handle to the menu (hMenu) and the ID of the menu item (MI_EASY, MI_HARD, MI_MEDIUM). As you can see, the medium item is checked, the others unchecked. It works the same for ther easy and hard menu items. The most important thing, actually setting the difficulty is done by the last line in each handler, one of the constants EASY, MEDIUM and HARD is put in the Difficulty variable.

The handler for the different image types works the same way, but an extra line is included to check the right button on the toolbar (one of the three buttons in the button group that select the image type). SetBitmap is called to set the new image type (though only the numbers image type is implemented right now). InvalidateRect is used to refresh the window so it will draw the image with the new colors.

The color scheme handlers use a new function, SetColors. This function is declared below. SetColors takes the menu ID of one of the color scheme menu items as a parameter, and then uses this ID to set the color variables. SetBitmap is called to refresh the image bitmap (in ImageDC) to make it use the new colors. InvalidateRect is used to refresh the window so it will draw the image with the new colors.

MI_NEWGAME will cause a call to NewGame to start a new game. MI_OPENBITMAP will be used to open a bitmap for a user defined image type, but this is not implemented yet.

10.4 - SetColors

SetColors           PROTO   STDCALL :DWORD
SetColors proc uses ebx cType:DWORD
    invoke  DeleteObject, hBackgroundColor
    invoke  DeleteObject, hTileColor
; ebx = background
; edx = text
; eax = tile
    mov     eax, cType
    .IF     ax==MI_COLORRED
        mov     eax, 00000A0h
        mov     edx, 00000FFh
        mov     ebx, 05050E0h
    .ELSEIF ax==MI_COLORBLUE
        mov     eax, 0FF8080h
        mov     edx, 0800000h
        mov     ebx, 0FF8000h
    .ELSEIF ax==MI_COLORGREEN
        mov     eax, 000A000h
        mov     edx, 000FF00h
        mov     ebx, 050E050h
    .ELSEIF ax==MI_COLORUGLY
        mov     eax, 0FF00FFh
        mov     edx, 000FF00h
        mov     ebx, 00000FFh
    .ENDIF
    mov     TextColor, edx

    invoke  CreateSolidBrush, eax
    mov     hTileColor, eax

    invoke  CreateSolidBrush, ebx
    mov     hBackgroundColor, eax
ret
SetColors endp

The SetColors function sets the color variables (TextColor, hTileColor and hBackgroundColor) according to the menu item we clicked on. First the two old brushes are deleted with DeleteObject (TextColor does not have to be deleted because it isn't a brush but just a colorvalue). Then according to the menu item ID, eax, edx and ebx are set with color values. Then edx, eax and ebx are used to create the brushes. If we wouldn't use the registers this way, the code would be less inefficient (more code would be needed for all the CreateSolidBrush calls etc.).

10.5 - Initial settings

To set the initial settings, we call ProcessMenuItems manually so it will think a menu item is pressed:

In the WM_CREATE handler of WndProc:

invoke  ProcessMenuItems, hWnd, MI_EASY
invoke  ProcessMenuItems, hWnd, MI_USENUMBERS
invoke  ProcessMenuItems, hWnd, MI_COLORBLUE

10.6 - Done

If you run the program, you can test the menu items and you will see that the toolbar and menu items work correctly with each other . The color schemes work fine, but the program can react a little strange sometimes because not all functions are implemented yet.

Current source code is here: mosaic7.zip.

Example of the different color schemes:

Blue color schemeRed color schemeGreen color scheme