UI Overview: 5.5 A UI Example: TicTacPiece Specifics

Up: GEOS SDK TechDocs | Up | Prev: 5.4 TicTacBoard Specifics

TicTacPieceClass contains most of the game's functionality. Since the user interacts directly with each game piece, the piece must know not only how to draw itself but also how to react to user input.

MSG_PIECE_NEW_GAME
This is the only message generated by the game itself that a game piece receives; it is sent by the TicTacBoard object when the user has pressed the New Game trigger. The game piece object responds by resetting its bounds to the original settings. It does not have to redraw or invalidate its old bounds because the TicTacBoard object will send a MSG_VIS_DRAW later and will invalidate the entire board.
MSG_VIS_DRAW
This message notifies the object that it must draw itself and any accompanying graphics. The game piece responds by drawing the proper shape in the proper color in the proper place. Since every VisClass object inherently knows its location and bounds, the object already knows where and how big the shape should appear. Whether a gray box or circle is drawn depends on the TTP_pieceType instance data field.
MSG_META_START_SELECT
This message is sent by the system when the user clicks a mouse button. The UI sends the message to whatever object lies under the pointer. The UI objects then pass the message down the object tree until it gets handled. The progression sends the message to TicTacApp, which passes it to TicTacPrimary, which passes it to TicTacView, which passes it to TicTacBoard, which (by letting the default VisContentClass method handle it) passes it to the proper game piece object (if any) under the pointer. The game piece responds by grabbing the mouse and all subsequent pointer events.
MSG_META_DRAG_SELECT
This message, like MSG_META_START_SELECT , indicates that the user has clicked a mouse button and has initiated a drag event. (Normally, this is used to select ranges or groups of objects; in TicTac, however, it is treated like MSG_META_START_SELECT .)
MSG_META_DRAG
This message is sent after the user has clicked the mouse button and is now moving the mouse pointer (and has not released the button yet).
MSG_META_PTR
This message is sent when the pointer image is over the bounds of the game piece whether or not a mouse button has been pressed. (After the object has grabbed the mouse events by handling MSG_META_START_SELECT , the pointer event is sent whenever the mouse pointer is moved.) The piece determines whether or not it is being dragged around the screen. This is known as a "drag event," and the game piece responds by drawing a piece-shaped outline around the mouse pointer. This outline will follow the pointer around the screen until the user releases the mouse button (causing a MSG_META_END_SELECT , below). The outline is first drawn in either the MSG_META_START_SELECT or MSG_META_DRAG_SELECT handler (whichever is called to start the drag event). MSG_META_PTR and MSG_META_END_SELECT erase the outline before drawing a new one. The game piece will maintain three locations in its instance data: Its VI_bounds field maintains its position when selected. Its TTP_orig(horiz/vert)Pos fields maintain its original position when the game was first started. Its TTP_(horiz/vert)Pos fields maintain the current position of the outline and where the object would relocate to if the move was ended now. If the event is not a drag, the object will not react because it is assumed that no mouse button has been pressed and therefore the user is taking no action.
MSG_META_END_SELECT
This message is sent when the user releases a pressed mouse button, ending the select-and-drag process. The game piece reacts by checking if the location of the pointer is a legal position on the game board. (It does this by sending a verification message to the TicTacBoard object to make sure the proposed new bounds are on the game board.) If the position is legal, the game piece moves itself there, erasing any leftover outlines (from the drag sequence) and its original image on the board. It then causes itself to draw in the new location by sending itself a MSG_VIS_DRAW . If the new location is not legally on the game board, then the object will reset all its instance data and erase any leftover outlines, causing it to revert to its location before the select-and-drag sequence began.

Each of the methods for the above messages is shown in Methods for TicTacPieceClass .

Code Display 6-6 Methods for TicTacPieceClass

/***********************************************************************
 *
 * MESSAGE:		 MSG_PIECE_NEW_GAME for TicTacPieceClass
 *
 * DESCRIPTION:		This message causes the piece to replace itself
 *		to its original position. It is invoked when the
 *		user presses the New Game trigger; the trigger sends
 * 		MSG_TICTAC_NEW_GAME to the TicTacBoard object, and
 * 		the board object sends this message to each of
 * 		the game piece objects.
 * 
 * PARAMETERS:
 * 	void ()
 ***********************************************************************/
@method TicTacPieceClass, MSG_PIECE_NEW_GAME {
	/* Set the current (motion) positions to the original positions. */
    pself->TTP_vertPos = pself->TTP_origVertPos;
    pself->TTP_horizPos = pself->TTP_origHorizPos;
	/* Send a MSG_VIS_BOUNDS_CHANGED to ourselves to make
	 * sure the old bounds get redrawn. This message will
	 * cause an invalidation of the document where the old
	 * (passed) bounds were, causing that portion of the
	 * window to be redrawn.						*/
    @call self::MSG_VIS_BOUNDS_CHANGED(pself->VI_bounds.R_bottom,
			pself->VI_bounds.R_right, pself->VI_bounds.R_top,
			pself->VI_bounds.R_left);
	/* Set the bounds of the object (VI_bounds) back to
	 * their original values. The Rectangle structure
	 * contains four fields, each of which must be set.						*/
    pself->VI_bounds.R_left = pself->TTP_origHorizPos;
    pself->VI_bounds.R_top = pself->TTP_origVertPos;
    pself->VI_bounds.R_right = (pself->TTP_origHorizPos + PIECE_WIDTH);
    pself->VI_bounds.R_bottom = (pself->TTP_origVertPos + PIECE_HEIGHT);
	/* This method does not need to invoke a MSG_VIS_DRAW
	 * because the TicTacBoard object will do that. The
	 * piece object will later receive a MSG_VIS_DRAW that
	 * will cause the piece to be redrawn back at its
	 * original location (the newly set bounds).						*/
}
/***********************************************************************
 *
 * MESSAGE:		MSG_VIS_DRAW for TicTacPieceClass
 *
 * DESCRIPTION:		Draw the piece at the current location. If the piece
 *		is a "box," draw a gray square. If the piece is a
 *		"ring," draw a gray circle. This message is received
 *		whenever a portion of the view window becomes invalid;
 *		TicTacView will send a MSG_META_EXPOSED to TicTacBoard,
 *		which will send itself (by default) a MSG_VIS_DRAW.
 *		The MSG_VIS_DRAW will be handled and then will be
 *		passed on to each of the game pieces. Then each piece
 *		(in this handler) will draw itself at its own bounds.
 *
 * PARAMETERS:
 *	void (word drawFlags GStateHandle gstate)
 *
 ***********************************************************************/
@method TicTacPieceClass, MSG_VIS_DRAW {
	/* Set the mode to MM_COPY; this means that the image
	 * drawn now will be drawn over whatever is there now.*/
    GrSetMixMode(gstate, MM_COPY);
	/* If the type is TTPT_BOX, set the color to gray and
	 * draw a rectangle the size of the object's bounds.
	 * Otherwise (since there are just two types), set the
	 * color to gray and draw an ellipse of that size.						*/
    if (pself->TTP_pieceType == TTPT_BOX) {
	GrSetAreaColor(gstate, CF_INDEX, C_DARK_GRAY, 0, 0);
	GrFillRect(gstate, pself->VI_bounds.R_left, pself->VI_bounds.R_top,
			pself->VI_bounds.R_right, pself->VI_bounds.R_bottom);
    } else {
	GrSetAreaColor(gstate, CF_INDEX, C_LIGHT_GRAY, 0, 0);
	GrFillEllipse(gstate, pself->VI_bounds.R_left, pself->VI_bounds.R_top,
			pself->VI_bounds.R_right, pself->VI_bounds.R_bottom);
    }
	/* After handling the message, call the superclass to
	 * ensure that no default behavior has been mucked up.
	 * This is actually not necessary in this particular case. */
    @callsuper();
}
/***********************************************************************
 * MESSAGE:		MSG_META_START_SELECT for TicTacPieceClass
 *
 * DESCRIPTION:		Grabs the mouse and calls for future pointer events.
 *		When the user clicks in the view, TicTacView will pass
 *		the click event to TicTacBoard. Since TicTacBoardClass
 *		does not intercept the event, VisContentClass passes
 *		it on to its child object currently under the pointer.
 *
 *		When the piece object receives this message, it means
 *		it has been clicked on by the user and the mouse button
 *		is still down. The piece must grab the mouse so that it
 *		gets all future mouse events, and it must request that
 *		all future mouse events be sent to it. This ensures
 *		that if the pointer leaves the object's bounds while
 *		the button is still pressed, the piece object will still
 *		receive all the pointer events (otherwise they would be
 *		sent to whatever object was under the new pointer
 *		position).
 * PARAMETERS:
 *	void (MouseReturnParams *retVal, word xPosition,
 *			word yPosition, word inputState)
 ***********************************************************************/
@method TicTacPieceClass, MSG_META_START_SELECT {
	/* First grab the gadget exclusive so we're allowed to
	 * grab the mouse. Then grab the mouse, so all future
	 * pointer events get passed directly to the game piece. */
    @call @visParent::MSG_VIS_TAKE_GADGET_EXCL(oself);
    @call self::MSG_VIS_GRAB_MOUSE();						/* grab mouse */
	/* Finally, return that this particular click
	 * event has been processed. If this flag is
	 * not returned, the system will send out the
	 * click event again.					*/
    retVal->flags = MRF_PROCESSED;						/* this event processed */
}
/***********************************************************************
 *
 * MESSAGE:		MSG_META_DRAG_SELECT for TicTacPieceClass
 *
 * DESCRIPTION:		This message is sent to the piece object when the
 *		select button has been pressed and the mouse has been
 *		moved, resulting in a "drag-select" event.
 *		For event processing from the View, see the header
 *		for MSG_META_START_SELECT.
 *
 * PARAMETERS:
 *	void (MouseReturnParams *retVal, word xPosition,
 *			word yPosition, word inputState)
 ***********************************************************************/
@method TicTacPieceClass, MSG_META_DRAG_SELECT {
    GStateHandle gstate;					/* temporary gstate to draw to */
    WindowHandle win;					/* window handle of view window */
	/* Start off by setting the flag indicating that
	 * the piece is being dragged around the screen. */
    pself->TTP_dragging = TRUE;
	/* Next, get the window handle of the view window.
	 * Then, create a new, temporary gstate to draw into
	 * for that window.						*/
    win = @call TicTacView::MSG_GEN_VIEW_GET_WINDOW();
    gstate = GrCreateState(win);
	/* Now, set the current position of the game piece
	 * to be centered on the pointer.						*/
    pself->TTP_vertPos = yPosition - (PIECE_HEIGHT/2);
    pself->TTP_horizPos = xPosition - (PIECE_WIDTH/2);
	/* Now, set the drawing mode of the game piece
	 * to MM_INVERT to draw a new game piece outline.
	 * MM_INVERT is chosen so the outline can be redrawn
	 * in invert mode later to erase it and not destroy
	 * anything under it.					*/
    GrSetMixMode(gstate, MM_INVERT);
	/* Now draw the outline. If the game piece is of type
	 * TTPT_BOX, draw a rectangle outline. Otherwise, draw
	 * an ellipse outline.						*/
    if (pself->TTP_pieceType == TTPT_BOX) {
	GrDrawRect(gstate, pself->TTP_horizPos, pself->TTP_vertPos,
				(pself->TTP_horizPos + PIECE_WIDTH),
				(pself->TTP_vertPos + PIECE_HEIGHT));
    } else {
	GrDrawEllipse(gstate, pself->TTP_horizPos, pself->TTP_vertPos,
				(pself->TTP_horizPos + PIECE_WIDTH),
				(pself->TTP_vertPos + PIECE_HEIGHT));
    }
	/* Next, destroy the temporary gstate. This is important
	 * to make sure the gstate does not stay in memory and
	 * begin to slow down the system as more and more
	 * temporary gstates are created but not destroyed.						*/
    GrDestroyState(gstate);
	/* Finally, return that this event has been processed
	 * by this method.						*/
    retVal->flags = MRF_PROCESSED;
}
/***********************************************************************
 *
 * MESSAGE:		MSG_META_PTR for TicTacPieceClass
 *
 * DESCRIPTION:		This message is received whenever the pointer passes
 *		over this game piece object's bounds (and another
 *		game piece is not sitting directly on top of it).
 *		See MSG_META_START_SELECT for a description of how the event
 *		gets passed from TicTacView to this object.
 *
 *		This message can be either a drag event or a simple
 *		pointer event. If the latter, we want to do nothing
 *		because no mouse button is pressed. If the latter,
 *		we want to execute the same function as MSG_META_DRAG.
 *
 * PARAMETERS:
 *	void (MouseReturnParams *retVal, word xPosition,
 *			word yPosition, word inputState)
 *
 ***********************************************************************/
@method TicTacPieceClass, MSG_META_PTR {
    GStateHandle gstate;					/* temporary gstate to draw to */
    WindowHandle win;					/* window handle of view window */
	/* First check if this is a drag event. If not, do
	 * nothing. If so, then draw a new outline and erase
	 * the old outline.					*/
    if (pself->TTP_dragging) {
	/* Get the view's window handle and create a
	 * temporary gstate for drawing into.					*/
	win = @call TicTacView::MSG_GEN_VIEW_GET_WINDOW();
	gstate = GrCreateState(win);
	/* Set the drawing mode of the game piece to 
	 * MM_INVERT for outline drawing.					*/
	GrSetMixMode(gstate, MM_INVERT);
	/* Erase the old outline by drawing an inverse
	 * outline at the old bounds.					*/
	if (pself->TTP_pieceType == TTPT_BOX) {
	    GrDrawRect(gstate, pself->TTP_horizPos, pself->TTP_vertPos,
					(pself->TTP_horizPos + PIECE_WIDTH),
					(pself->TTP_vertPos + PIECE_HEIGHT));
	} else {
	    GrDrawEllipse(gstate, pself->TTP_horizPos, pself->TTP_vertPos,
					(pself->TTP_horizPos + PIECE_WIDTH),
					(pself->TTP_vertPos + PIECE_HEIGHT));
	}
	/* Now set the current motion position to be
	 * centered on the pointer.					*/
	pself->TTP_vertPos = yPosition - (PIECE_HEIGHT/2);
	pself->TTP_horizPos = xPosition - (PIECE_WIDTH/2);
	/* Draw the new outline at the current position.*/
	if (pself->TTP_pieceType == TTPT_BOX) {
	    GrDrawRect(gstate, pself->TTP_horizPos, pself->TTP_vertPos,
					(pself->TTP_horizPos + PIECE_WIDTH),
					(pself->TTP_vertPos + PIECE_HEIGHT));
	} else {
	    GrDrawEllipse(gstate, pself->TTP_horizPos, pself->TTP_vertPos,
					(pself->TTP_horizPos + PIECE_WIDTH),
					(pself->TTP_vertPos + PIECE_HEIGHT));
	}
	/* Destroy the temporary gstate and return that
	 * this event has been processed.						*/
	GrDestroyState(gstate);
    }
    retVal->flags = MRF_PROCESSED;
}
/***********************************************************************
 *
 * MESSAGE:		MSG_META_END_SELECT for TicTacPieceClass
 *
 * DESCRIPTION:		This message is received when the selection button has
 *		been released and this game piece had the mouse grab.
 *		All it does is release the gadget exclusive, which will
 *		cause us to end any dragging in progress and release
 *		the mouse.
 *		When we release the gadget exclusive, the UI will then
 *		sent MSG_VIS_LOST_GADGET_EXCL to this piece, which will
 *		tell us to erase the outline and draw the game piece.
 * PARAMETERS:
 *	void (MouseReturnParams *retVal, word xPosition,
 * 			word yPosition, word inputState);
 ***********************************************************************/
@method TicTacPieceClass, MSG_META_END_SELECT {
	/* Release the gadget exclusive, then return that the
	 * event has been processed. */
    @call @visParent::MSG_VIS_RELEASE_GADGET_EXCL(oself);
    retVal->flags = MRF_PROCESSED; /* this event processed */
}
/***********************************************************************
 *
 * MESSAGE:		MSG_VIS_LOST_GADGET_EXCL for TicTacPieceClass
 *
 * DESCRIPTION:		This message is received when the piece lots go of the
 *		gadget exclusive (see MSG_META_END_SELECT, above).
 *		It first checks to see if the new, proposed bounds are
 *		on the game board. If the bounds are valid, then
 *		it sets the objects VI_bounds field to the new values
 *		and causes the object to erase its original drawing
 *		and draw itself at its new bounds. If the bounds are
 *		not on the game board, it will retain the original bounds
 *		and redraw using them.
 *
 * PARAMETERS:
 *	void ()
 *
 ***********************************************************************/
@method TicTacPieceClass, MSG_VIS_LOST_GADGET_EXCL {
    WindowHandle win;					/* window handle of view window */
    GStateHandle gstate;					/* temporary gstate to draw to */
	/* First check if the piece was being dragged.
	 * If not, we don't have to do anything.					*/
    if (pself->TTP_dragging) {
	/* Get the window handle of the view window and
	 * create a temporary gstate for it to draw to.						*/
	win = @call TicTacView::MSG_GEN_VIEW_GET_WINDOW();
	gstate = GrCreateState(win);
	/* Set the mode for drawing the outline.						*/
	GrSetMixMode(gstate, MM_INVERT);
	/* If the game piece type is TTPT_BOX, draw a rectangle
	 * outline. Otherwise draw an ellipse outline.						*/
	if (pself->TTP_pieceType == TTPT_BOX) {
	    GrDrawRect(gstate, pself->TTP_horizPos, pself->TTP_vertPos,
				((pself->TTP_horizPos) + PIECE_WIDTH),
				((pself->TTP_vertPos) + PIECE_HEIGHT));
	} else {
	    GrDrawEllipse(gstate, pself->TTP_horizPos, pself->TTP_vertPos,
				((pself->TTP_horizPos) + PIECE_WIDTH),
				((pself->TTP_vertPos) + PIECE_HEIGHT));
	}
	/* Check to see if the new bounds are on the game
	 * board. If they are, set the object's bounds to the
	 * new values. If they are not, retain the original
	 * values and set the values to those last stored.						*/
	if (@call TicTacBoard::MSG_TICTAC_VALIDATE_BOUNDS(
					((pself->TTP_vertPos) + PIECE_HEIGHT),
					((pself->TTP_horizPos) + PIECE_WIDTH),
					pself->TTP_vertPos,
					pself->TTP_horizPos)) {
	/* Invalidate the original drawing of the game piece.
	 * Send the VI_bounds rectangle as the parameters
	 * because they have not been changed since the
	 * START_SELECT. This message is the equivalent of
	 * calling GrInvalRect() with the same bounds.						*/
	    @call self::MSG_VIS_BOUNDS_CHANGED(pself->VI_bounds.R_bottom,
					pself->VI_bounds.R_right,
					pself->VI_bounds.R_top,
					pself->VI_bounds.R_left);
	/* Now set the current position to be centered
	 * on the pointer image.					*/
	    pself->TTP_vertPos = yPosition - (PIECE_HEIGHT/2);
	    pself->TTP_horizPos = xPosition - (PIECE_WIDTH/2);
	/* Set the game piece object's bounds to
	 * the new coordinates.					*/
	    pself->VI_bounds.R_left = pself->TTP_horizPos;
	    pself->VI_bounds.R_right = (pself->TTP_horizPos) + PIECE_WIDTH;
	    pself->VI_bounds.R_top = pself->TTP_vertPos;
	    pself->VI_bounds.R_bottom = (pself->TTP_vertPos) + PIECE_HEIGHT;
	} else {
	/* If the bounds are not on the game board, then reset
	 * the current positions to be the original bounds. */
	    pself->TTP_horizPos = pself->VI_bounds.R_left;
	    pself->TTP_vertPos = pself->VI_bounds.R_top;
	}
	/* Now, the game piece must draw itself at its newly-
	 * set bounds (will draw itself over its original
	 * picture if the new bounds were invalid).						*/
	@call self::MSG_VIS_DRAW(0, gstate);
	/* Destroy the temporary gstate used for drawing. */
	GrDestroyState(gstate);
	/* Finally, clear the dragging flag to indicate that
	 * no drag event is in progress. */
	pself->TTP_dragging = FALSE;
    }
	/* Release the mouse grab now that the move has
	 * finished. Other objects in the view (other game
	 * pieces, for example) may now receive pointer,
	 * select, and drag events.						*/
    @call self::MSG_VIS_RELEASE_MOUSE();
}

Up: GEOS SDK TechDocs | Up | Prev: 5.4 TicTacBoard Specifics