BasicV a Dabhand Guide Part 4
Chapter 10 : The mouse
The Archimedes is supplied with a three-button mouse as standard, and this device is fully supported by extensions to BBC BASIC. Acorn has recommended that software writers follow the convention that the left- hand button is designated SELECT, the middle button MENU, and the right- hand button ADJUST. However, this convention is not imposed upon the programmer, who can alternatively choose to associate each button with any function appropriate to his program. BASIC V provides instructions to enable and disable the mouse and, when enabled, to return to a program the state of the three buttons and the graphics co-ordinates of its current position.
A pointer can also be displayed such that moving the mouse automatically moves the pointer within any rectangular screen area defined by the user's program. The default is the whole screen area. The default pointer shape is an upward-sloping blue arrow, but it is possible for a user- defined pointer shape to be used instead, and up to four different pointer designs may be valid concurrently, with the ability for a program to select which pointer to display. However, the design of a pointer shape involves setting up a data block in memory and using a system call to the Wimp manager, a task which is outside the immediate scope of this book. Note that because the mouse pointer is controlled by the Wimp manager, removing or deleting this module will make it impossible for any pointer to be displayed.
Whether the default or a user defined pointer shape is used, the size and proportions of the pointer displayed will, like text, depend upon the screen mode in use. If you want a pointer to appear exactly the same in a 40- column mode and an 80-column mode, then one solution would be to define two pointer shapes to proportions which will exactly compensate for any distortions introduced by the characteristics of the different modes.
Whatever pointer shape is used, BASIC provides the means of varying its colours regardless of how these were originally defined.
The first command to consider is not in fact a BASIC instruction at all, but a call to the Wimp manager, in the form:
This enables the pointer to be used with the mouse. The single value parameter is optional. If no parameter is specified then the default system defined pointer is displayed. A parameter of 'V also selects the default pointer, while other values select user-defined pointers. A parameter of '0' disables the pointer. Once *POINTER has been executed, a pointer can be switched on and off with one of BASIC's MOUSE instructions. Thus, using:
will display the default pointer, while an instruction of the form:
MOUSE ON n
will display pointer 'n' with the same values and meanings as above for "pointer. However, unlike "pointer, 'n' may be either a value or an expression in this case, giving more flexibility in the use of this instruction.
To switch a pointer off simply use:
Note that changing mode will also cause the pointer to disappear from the screen, but MOUSE ON, with or without a parameter as required, is sufficient to restore it.
There is a further variation of the MOUSE ON command which is undocumented in the User Guide. If 128 is added to the pointer number (n), the pointer is enabled (ie, it appears on the screen) as described above, but is 'disconnected' from the mouse. Once this has been done, the pointer may be moved to any location using the POINT TO command to specify a point in graphics units on the screen. The only oblique reference in the User Guide is in connection with "FX106 which is entirely equivalent to MOUSE ON. Using MOUSE ON with a pointer number less than 129 will restore mouse control, while MOUSE OFF will remove the pointer altogether.
Input from the Mouse
A single instruction is provided by basic to return the position of the mouse and the state of the mouse buttons. You need to include three variables such as x, y, and z as shown here. The instruction takes the form:
where x and y are the graphics co-ordinates of the active point of the pointer, and z indicates which buttons, if any, are depressed at that time. It is simplest to think of the three buttons as three binary switches with values of 0 (off) and 1 (on or depressed). Combining the resulting three bits gives a number in the range 0 to 7, where a value of 0 indicates all three buttons are 'off, and a value of 7 that all three buttons are 'on'. Operated individually, the adjust button (the right one) returns '1' (binary 001), the menu button (in the middle) returns '2' (binary 010), while the select button (to the left) returns '4' (binary 100).
In use, this instruction will normally be placed at the start of a loop which is executed repeatedly. In most cases, either the button or combination of buttons pressed, or the position of the pointer on the screen, will determine any action to be taken. Typically a CASE statement is the best choice for handling this situation. Consider, for example:
exit%=FALSE REPEAT MOUSE xpos,ypos,state CASE state OF WHEN 7:exit%=TRUE WHEN 4:PROCselect(xpos,ypos) WHEN 2:PROCmenu(xpos,ypos) WHEN l:PROCadjust(xpos,ypos) ENDCASE UNTIL exit%
This responds to any of the three buttons being pressed individually by calling a corresponding procedure, while pressing all three buttons together causes an exit from the loop. An alternative approach, making use of the pointer position, might be written as:
exit%=FALSE REPEAT MOUSE x,y,buttons CASE TRUE OF WHEN FNpos(x,y,ai,bl,width,height):PROCmenu(1,buttons) WHEN FNpos (x,y,a2,b2,width,height):PROCmenu(2,buttons) WHEN FNpos(x,y,a3,b3,width,height):PROCmenu(3,buttons) WHEN FNpos(x,y,a9,b9,width,height):exit%=TRUE ENDCASE UNTIL exit%
This routine assumes the existence of a function (FNpos) which checks whether the current pointer position lies within a specified screen rectangle defined by the co-ordinates of its bottom left-hand comer, width and height, and returns a value of true or false depending on the result. This would suit a situation where a number of menu options were displayed in boxes on the screen. The routine above calls a menu procedure in each case, passing the number of the menu option specified, and the button status.
Both approaches can be combined together. Typically, the select button would be used to pick out a menu choice displayed on the screen, with the other buttons having no effect. An additional CASE OF statement will do the job as follows:
exit%=FALSE REPEAT MOUSE x,y,buttons CASE buttons OF WHEN 4: CASE TRUE OF WHEN FNpos(x,y,al,bl,width,height):PROCmenu(l) WHEN FNpos(x,y,a2,b2,width,height):PROCmenu(2) WHEN FNpos(x,y,a3,b3,width,height):PROCmenu(3) WHEN FNpos(x,y,a9,b9,width,height):exit%=TRUE ENDCASE ENDCASE UNTIL exit%
An if...then...endif structure could be used as an alternative to the outer- most CASE statement - your choice will depend on just what the code is trying to achieve.
One of the problems that can only too easily arise when using the mouse, is that the button is depressed for too long, resulting in several sets of values (for x, y and z) ending up in the mouse buffer. The sheer speed of the Archimedes itself exacerbates this problem. The best way of avoiding this is to use a REPEAT...UNTIL loop to check that all the buttons have been released before checking for the next mouse input, and then a second loop to wait for a button press:
REPEAT REPEAT:Mouse x,y,z:UNTIL z=0 REPEAT:MOUSE x,y,z:UNTIL z<>0 ........ UNTIL exit%
Further Mouse Controls
When the Archimedes is first switched on, or after a mode change, the selected pointer is displayed at the centre of the screen (ie, the MOUSE x,y,z instruction would return x=640, y=512). Otherwise, if MOUSE ON and MOUSE OFF are used to switch pointers on and off, the pointer always appears in the last pointer position. A program can alternatively ensure that the pointer is displayed in a particular position by using the instruction:
MOUSE TO x,y
where x and y are in graphics co-ordinates. A program can also limit the area of the screen over which the pointer may be moved with the instruction:
MOUSE RECTANGLE x,y,w,h
where x and y are the co-ordinates of the bottom left-hand corner, and w and h are the width and height respectively, of the area concerned. This can be useful if the mouse is being used to move an object round the screen, to ensure that the whole of the object remains on-screen at all times. Note that limiting the position of the pointer in this way is quite independent of any text or graphics windows which you may create at the same time. Changing mode restores the pointer area to the full screen display.
A further instruction may be used to control the speed of movement of the pointer on the screen in relation to the movement of the mouse itself. Another way of looking at this is in terms of the physical distance the mouse has to move to cover the full screen width or height. The instruction takes the format:
MOUSE STEP xstep,ystep
where 'xstep' and 'ystep' can be thought of as multipliers controlling horizontal and vertical movement respectively. The second parameter is optional, in which case both are set to the same value. The default values are both 1. Changing both the x and y multipliers to 2 will double the speed of movement and result in the mouse having to be moved only half the previous distance to obtain full screen movement. If non-integers are used the values will be truncated to integer values. Hence values less than 1 all truncate to zero, resulting in no mouse movement whatsoever. Using negative values will cause the screen pointer to move in the opposite direction to that of the mouse itself!
Colouring the Mouse Pointer
All pointers are defined within a rectangular area, and may use up to three colours, logically colours 1, 2 and 3. Logical colour 0 represents the transparent parts of the pointer design, where the screen display is not obscured, and may not be changed, but any of the Archimedes 4096 colours may be assigned to the pointer's three logical colours. There are two equivalent ways of doing this using either:
MOUSE COLOUR l,r,g,b
The logical colour 1 may be set to 1, 2 or 3 (higher values simply repeat values 0 to 3), while the values r, g, and b specify the amounts of red, green and blue (each in the range 0 to 255) to be mixed to obtain the required colour. The values specified follow the rules so that the values of r, g and b only change meaningfully in steps of 16. Both forms of the instruction may be better written as:
MOUSE COLOUR 1,16Ğr, '- 6*g,16*b
where r, g and b are integers in the range 0 to 15. When selecting or changing the colours for a pointer, it is important to take into account the other colours on the screen to avoid the pointer becoming invisible. The default pointer shape with its coloured border ensures that the pointer remains visible even when placed over areas of colour which are the same as those used in the pointer itself. You can also change the default pointer to have a more solid appearance by making both the inside and the border the same shade, provided that that shade is not used elsewhere on the active screen area. The whole subject of colour on the Archimedes is explained in detail in Chapter Eight.
Switching pointers on and off with the MOUSE ON and MOUSE OFF instructions retains the current colour assignments for any pointer, but using *POINTER to delete and then display a pointer will result in the use of the colours in the original definition (dark and light blue for the default pointer). Mode changes also leave the pointer colours unchanged.
Example Mouse Program
We will now consider an extended example, in the form of a complete program, which illustrates the use of the mouse and the new RECTANGLE graphics statement. The whole routine has been written as a procedure, to make it easy to incorporate into other programs, with a short demonstration of what it does. Initially, the procedure displays a rectangle on the screen. As the mouse is moved around, so the rectangle moves. Pressing the select button fixes the bottom left-hand comer while allowing the opposite corner to be moved to change the proportions and size of the rectangle displayed. Pressing select fixes the size, and once more allows the rectangle to be moved as a whole. Both position and size can be repeatedly changed until the adjust button is pressed, whereupon the procedure exits, returning the x,y co-ordinates, width and height of the resulting rectangle. The short demo program accompanying the procedure displays these values and the rectangle on the screen.
The complete program, including the procedure PROCrectangle, is listed below. The procedure requires five parameters, the first being the logical colour to be used for the rectangle, and the other four the x,y co-ordinates of a corner and the width and height of the rectangle. When the procedure is called, these last four parameters should be used to determine the initial position and size of the rectangle to be displayed. On exit they return the final values of the same parameters.
Within the procedure, the main loop runs from lines 1060 to 1200. This is a REPEAT...UNTIL loop which constantly checks the mouse and maintains and changes the rectangle displayed on the screen until the adjust button exits from the loop and, ultimately, the procedure. Exclusive OR plotting is used with the RECTANGLE statement to repeatedly draw and erase the rectangle, while either the reference co-ordinates, or the width and height, are updated accordingly.
Also within the loop, a check is made on the state of the mouse buttons. If one (or more) has been pressed, then a CASE statement is used to determine the correct action. If the select button has been pressed (button value = 4) then the procedure has to switch between changing position and changing size. The pointer also has to be moved from one corner of the rectangle to the opposite one.
The rectangle is drawn near the start of the loop, and erased near the end, so that it remains visible for the maximum length of time within each cycle. Note the use of WAIT at line 1170 to synchronise deletion and redrawing of the rectangle with the vertical sync, and also the REPEAT...UNTIL loop at line 1150 which ensures that the mouse buttons return to a zero state after being pressed, and only after being pressed, before the loop is repeated. Even with the speed of the Archimedes, correct timing and synchronisation are just as important within such routines. The same problems of flickering images can occur just as easily as before, but more rapidly.
Examination of the coding in the rest of the procedure should clear up any further queries, as all is quite straightforward. A procedure such as this can be quite useful in an art or graphics package. For example, it allows the user to define any part of an existing display and then copy this elsewhere, or even produce stunning effects by 'drawing' with this image (see listing 9.1). It also allows any part of a screen display to be defined for saving as a sprite.
Listing 10.1 Dynamic mouse rectangle program
10 REM >Chapter10-1 100 MODE12:OFF 120 p=320:q=256:r=160:s=128 130 FROCrectangle(4,p,q,r,s! 140 PRINT"x = '";p" 1000 DEF PROCrectangle(colour,RETURN xl,RETURN yl,RETURN w,RETURN n) 1010 LOCAL exit%,switcht,x,y:GCOL 3,colour 1020 *POINTER 1030 exit%=FALSE:switch%=TRUE 1040 MOUSE TO xl,yl 1060 REPEAT 1070 RECTANGLE xl,yl,w,h 1080 MOUSE x,y,z 1090 IF z THEN 1100 CASE z OF 1110 WHEN 4:switch%=NOT switch% 1120 IF switch% THEN MOUSE TO xl,yl ELSE MOUSE TO x+w,y+h 1130 WHEN l:exit%=TRUE 1140 ENDCASE 1150 REPEAT:MOUSE x,y,z:UNTIL z=0 1160 ENDIF 1170 WAIT 1180 RECTANGLE xl,yl,w,h 1190 IF switch% THEN xl=x:yl=y ELSE w=x-xl:h=y-yl 1200 UNTIL exit% 1210 *POINTER 0 1220 ENDPROC