TEACH RC_LINEPIC Aaron Sloman June 1997 Last updated 11 Aug 1997 Change notes in HELP * RCLIB_NEWS For an overview of RCLIB facilities see HELP * RCLIB For a shorter introduction with examples see TEACH * RCLIB_DEMO.P To make all this available you need to do uses rclib [That presupposes that the RCLIB package has been installed appropriately at your site.] CONTENTS -- Introduction -- Ensure required libraries are compiled -- Introduction to the class rc_window_object -- Some examples of creating, moving and hiding windows -- Creating picture objects to add to window objects -- Using LIB RC_POINT -- Creating mouse-draggable points. -- Define a class of draggable objects -- -- Create another instance of dragpic -- Demonstrating program-controlled movement -- The objects can also be dragged by mouse -- Testing the picture selection algorithm -- Making rc_mouse_limit a procedure -- Example: a toy painting easel -- -- Exercise on extending the painting demo -- How the objects were made draggable: method definitions -- Constrained movers -- A more complex example: rc_blocks -- Using lib rc_buttons.p to create a VED control panel -- Non-draggable objects -- A class of static objects -- A class of movable objects -- Make some instances -- Drawing an instance of rc_static -- -- Changing the default colour used -- Drawing instances of rc_mover -- The appearance of overlapping objects -- Making moving pictures -- Demonstrating motion in "trail" mode -- Predefined types of sub-pictures: RECT and SQUARE -- Rotatable objects -- Static, movable and rotatable pictures -- -- Classes based on rc_linepic -- -- Classes based on rc_mousepic and rc_window_object -- -- Class rc_window_object -- -- Class rc_button -- -- Features of these classes -- Undrawing: rc_undrawn and rc_undraw_all -- Drawing special figures -- -- Drawing circles, using CIRCLE -- -- Drawing a rectangle or square, using RECT, or SQUARE -- -- Other figures -- WIDTH: Pictures with variable line widths -- Pictures with dashed and other line styles -- Pictures with COLOUR specifications -- Drawing a sub-picture with a different scale XSCALE, YSCALE -- Drawing part of a picture at an angle -- A simple move handler for windows -- Detecting keyboard events: rc_handle_keypress -- Key code mappings for rc_handle_keypress -- Defining additional event handlers -- MORE ON ROTATABLE OBJECTS -- -- How to make a printed string rotate -- Allowing squares or rectangles to rotate -- -- Part of a rotatable object may be offset by an angle -- Giving a class of objects a non-standard line-thickness -- A demonstration of moving pictures the "ant" demo -- -- Exercises on extending the ant demo. -- Related documentation -- Introduction ------------------------------------------------------- This file gives a basic overview of the facilities in the RCLIB library, which extend the Pop-11 RC_GRAPHIC library. The introduction is based on detailed examples that you can compile and run, and then modify to see what effects the changes have. For a higher level level introductory overview see TEACH * RC_INTRO As explained there, it helps if you already have background knowledge about (a) object oriented programming in Pop-11 (See TEACH * OOP, and teach files referenced there) (b) the Pop-11 RC_GRAPHIC library. See TEACH * GSTART, and TEACH * RC_GRAPHIC. You may find that some things don't work as described, e.g. pictures not disappearing or moving when they should. In that case see HELP * RCLIB_PROBLEMS -- Ensure required libraries are compiled ----------------------------- The following are among the library commands that will be needed. Some of these are are done implicitly, since some libraries invoke others. Start with this command: uses rclib Extends various documentatino and library search lists uses objectclass Loads the objectclass extension to Pop-11 uses rc_graphic Loads the basic relative coordinates graphic package on top of which the others are built. uses rc_linepic Defines the main utilities for drawing and moving picture objects. Static and movable pictures are defined by line drawing and string printing commands, using picture-centred coordinates. uses rc_window_object Defines the mechanisms for creating multiple graphical windows into which pictures can be drawn. The windows can be hidden and then shown again, relocated and resized under program control. uses rc_mousepic Defines event handling mechanisms for mouse events and keyboard events. The events can be handled either by the window concerned, or by selected objects in the window. uses rc_point Defines a simple picture class for points that can be moved by the mouse, in order to locate objects or modify graphical structures. uses rc_buttons Defines facilities for attaching buttons to graphical windows. Additional demonstration programs built on top of these facilities include rcdemo painting_demo Defines an easel with coloured paint pots and brushes for painting on a canvas. Accessed by ENTER rcdemo painting_demo lib rc_blocks Defines a demo of a simlulated robot that can be given commands to moves blocks around and asked questions about where the blocks are etc. rcdemo rc_ant_demo Shows a demonstration with varying numbers of "ants" moving about and interacting with each other. A control button can be used to increase the number of ants. We now provide some examples, first of movable windows, then objects that can be drawn in the windows, moved, made mouse sensitive, etc. -- Introduction to the class rc_window_object ------------------------- This section makes use of LIB RC_WINDOW_OBJECT Part of the RCLIB library is a mechanism for associating instances of the class rc_window_object (defined in LIB * RC_WINDOW_OBJECT) with windows. E.g. we can create a graphical window object using the procedure rc_new_window_object, which can be invoked in the following format (NB this command is not executable -- it is a template). rc_new_window_object(x, y, width, height, setframe, "hidden", string) -> win_obj The word "hidden" may be omitted, in which case the window is immediately made visible when it is created. The string may also be omitted. If provided it is used to give a name to the window, as described in HELP * RCLIB/rc_new_window_object. If the string is not provided a default title is used, 'Xgraphic'. The fifth argument, setframe, can be false, true, or a vector. It is used to determine the coordinate frame in the window, as described in HELP * RCLIB/rc_new_window_object. The result returned by rc_new_window is not the graphical widget but a window object (an instance of the class rc-window_object) corresponding to the new window. -- Some examples of creating, moving and hiding windows --------------- The following commands may now be executed: uses rclib uses rc_window_object vars win1 = rc_new_window_object(400, 40, 500, 400, true, 'win1'); win1 => ** The default print_instance method for the rc_window_object class gives the window's title, the screen coordinates, the width, height, and the number of sensitive picture objects on the window (currently none). You may see slightly different window coordinates. The screen coordinates are not necessarily exactly the same as those given to rc_new_window_object because the window manager adds a border and titlebar. The exact coordinates shown will differ according to how your window manager is set up. It's possible to interrogate or update the title of the window: rc_window_title(win1) => ** win1 We can give it a different title 'WIN1' -> rc_window_title(win1); win1 => We can change the window's location or size, using rc_window_location: rc_window_location(win1) => ** 400 40 500 400 That returns xloc, yloc, width, height. The updater changes things, and false arguments are ignored. E.g. change the location (this may be a bit slow the first time, as it attempts to work out the correction needed for the window frame): 400, 400, false, false -> rc_window_location(win1); win1 => Change the size false, false, 450, 350 -> rc_window_location(win1); Put it near the top left corner 10, 20, false, false -> rc_window_location(win1); win1 => You can also hide the window rc_hide_window(win1); then change its location and show it again. 500, 40, false, false -> rc_window_location(win1); rc_show_window(win1); ;;; may change the location win1 => The window may appear in the wrong location then jump to the correct one, depending on the window manager. As before, the actual window coordinates shown are not necessarily the same as those given to rc_window_location. Let's create another window. vars win2 = rc_new_window_object(600, 450, 300, 300, true, 'win2'); win2 => ** We can change the window that is current, using the active variable rc_current_window and its updater. rc_current_window_object => win1 -> rc_current_window_object; rc_current_window_object => Draw on it: rc_drawline(0, 0, 150, 150); If it is hidden, try this rc_raise_window(win1); Now draw on win2 win2 -> rc_current_window_object; rc_raise_window(win2); rc_drawline(0, 0, 150, 200); We can destroy those windows: rc_kill_window_object(win1); rc_kill_window_object(win2); If you are using the CTWM window manager you may have to move the mouse over the window to make it finally go away. -- Creating picture objects to add to window objects ------------------ We now show how to create various kinds of picture objects on such windows. The objects may simply be static pictures, or they may have any of these additional features: they may be moveable rotatable mouse sensitve keysensitive In order for a picture object to be capable of being dragged it has to be movable and mouse sensitive. So we'll start with examples illustrating this. We shall use the predefined class of rc_point objects. These can be created, drawn, moved with the mouse and destroyed. Create two windows so that we can draw different pictures on them. uses rclib; uses rc_window_object; vars win1 = rc_new_window_object(200, 40, 300, 250, true, 'win1'), win2 = rc_new_window_object(510, 40, 300, 250, true, 'win2'); Make them both mouse sensitive: rc_mousepic(win1); rc_mousepic(win2); After doing that you can make a window the value of rc_current_window_object simply by holding down the CTRL key and then clicking in the relevant window. Try clicking with CTRL and mouse button 1 in either of win1 or win2 and then print out rc_current_window_object => -- Using LIB RC_POINT ------------------------------------------------- uses rc_point Create two points, move them, then undraw them. vars pt1 = rc_cons_point(0, 0, 6), pt2 = rc_cons_point(100,100,10); pt1, pt2 => ** win1 -> rc_current_window_object; rc_draw_linepic(pt1); rc_draw_linepic(pt2); ;;; Move them without leaving any "trail". rc_move_to(pt1, -100, 50, true); rc_move_to(pt2, -100, -50, true); Now undraw them. rc_undraw_linepic(pt1); rc_undraw_linepic(pt2); Try making win2 the current window object. Draw the points there, then move them then undraw them. -- Creating mouse-draggable points. ----------------------------------- ;;; create three points, with radius 6, 7 and 12, labelled 'a', 'b', ;;; 'c' on win1 win1 -> rc_current_window_object; rc_mousepic(win1); vars p1 = rc_new_live_point(0, 0, 6, 'a'), p2 = rc_new_live_point(0, 50, 7, 'b'), p3 = rc_new_live_point(0, -50, 12, 'c'), ; Note that these points can be dragged with the left mouse button. Move them around and print them, repeatedly, seeing how their coordinates change: p1,p2,p3 => We can also move them under program control. Mark and load the following: false -> popradians; vars ang; for ang from 0 by 5 to 360*4 do rc_move_to(p1, 100*cos(ang), 100*sin(ang), true); rc_move_to(p2, 80*cos(2*ang), 80*sin(2*ang), true); rc_move_to(p3, 50*cos(3*ang), 50*sin(3*ang), true); syssleep(1); endfor; Make win2 current and create some points on that window. Get rid of the two windows. rc_kill_window_object(win1); rc_kill_window_object(win2); For more things you can do with point data structures see HELP * RC_POINT We now show how a user definable class of draggable objects can be created. -- Define a class of draggable objects -------------------------------- Use the mixins provided by rc_mousepic and rc_linepic to make a class of objects that can be dragged. Create two windows so that we can draw different pictures on them. uses rclib; uses rc_window_object; vars win1 = rc_new_window_object(200, 40, 300, 250, true, 'win1'), win2 = rc_new_window_object(510, 40, 300, 250, true, 'win2'); ;;; Make sure this is compiled uses rc_mousepic; ;;; Now define a class of draggable objects define :class dragpic; ;;; this class inherits from three different "mixins" is rc_keysensitive rc_selectable rc_linepic_movable; ;;; default names for instances are dragpic1, dragpic2, etc. slot pic_name = gensym("dragpic"); enddefine; Define a print_instance method to simplify printing instances of this class. Use the format (See HELP * PRINTF) define :method print_instance(p:dragpic); printf('', [%pic_name(p), rc_coords(p) %]) enddefine; Create two instances of the class dragpic defined above. The first contains a blue square a blue rectangle and a red text string string. The second has a more complex shape, including a circle. define :instance drag1:dragpic; pic_name = "drag1"; rc_picx = 100; rc_picy = 50; rc_pic_lines = [WIDTH 2 COLOUR 'black' [SQUARE {-25 25 50}] [CLOSED {-30 20} {30 20} {30 -20} {-30 -20}] ]; rc_pic_strings = [[FONT '9x15bold' COLOUR 'red' {-22 -5 'drag1'}]]; enddefine; Now draw drag1 on win1 win1 -> rc_current_window_object; rc_draw_linepic(drag1); That should draw two overlapping black rectangles, with the word "drag1" printed in red. You should be able to make it disappear with this command (which works only for movable objects): rc_undraw_linepic(drag1); ;;; draw it again rc_draw_linepic(drag1); Note: re-drawing while it is visible can make it alternately appear and disappear. If rc_undraw_linepic does not work see HELP * RCLIB_PROBLEMS -- -- Create another instance of dragpic define :instance drag2:dragpic; pic_name = "drag2"; rc_picx = 100; rc_picy = -50; rc_pic_lines = [WIDTH 2 COLOUR 'blue' [CLOSED {-30 20} {30 20} {30 -20} {-30 -20}] [CIRCLE {0 15 10}] [SQUARE {-10 -15 20}] ]; rc_pic_strings = [[FONT '8x13bold' COLOUR 'brown' {-20 -10 'drag2'}]]; enddefine; Print the picture objects: drag1 => drag2 => Print out more details: datalist(drag1) ==> datalist(drag2) ==> Now draw drag2 on win2 ;;; First make win2 the current window win2 -> rc_current_window_object; rc_draw_linepic(drag2); That should draw, in blue, a large rectangle, with its upper line crossed by a circle and its lower line by a square. In the middle is the word "drag2" in brown. Again, drawing and re-drawing should make the picture appear and disappear. Note that because of the use of an algorithm that makes moving objects easy, where bits of a picture overlap the colour may be surprising, e.g. where the circle overlaps the line. This will not happen with static pictures, which are drawn differently. (See HELP * RCLIB_PROBLEMS) -- Demonstrating program-controlled movement -------------------------- The objects drag1 and drag2 can be moved by program command, using rc_move_to and rc_move_by. (Some details are explained later.) Use absolute locations (rc_move_to) win1 -> rc_current_window_object; rc_move_to(drag1, 60, -75, true); ;;; true means show the motion rc_move_to(drag1, 75, -50, true); ;;; true means show the motion win2 -> rc_current_window_object; rc_move_to(drag2, 110, 30, true); rc_move_to(drag2, -100, -50, true); Use relative locations (rc_move_by) win1 -> rc_current_window_object; repeat 10 times rc_move_by(drag1, -10, 5, true) endrepeat; win2 -> rc_current_window_object; repeat 10 times rc_move_by(drag2, 0, 8, true) endrepeat; Check the new locations drag1 => drag2 => NB if the objects did not move properly, e.g. if they left a "trail" showing previous locations, see previous comment about Glinefunction -- The objects can also be dragged by mouse --------------------------- Before objects can be selected or dragged, we have to make the windows mouse sensitive: rc_mousepic(win1); rc_mousepic(win2); Now make drag1 known to one window and drag2 to the other, and then try dragging them around with the left mouse button: rc_add_pic_to_window(drag1, win1, true); rc_add_pic_to_window(drag2, win2, true); (The second argument (true or false) determines whether the picture should be added to the front or the back of the current list of pictures associated with the current window). Check that the objects are now known, by printing out the list of known picture objects for each window. rc_window_contents(win1) ==> rc_window_contents(win2) ==> Each object has a default "sensitive" area, a square 10 by 10, centred on the objects internal coordinate frame. rc_mouse_limit(drag1) => rc_mouse_limit(drag2) => The default value of the rc_mouse_limit slot for new instances of rc_selectable is held in this global variable rc_select_distance Initially this has as its value a vector defining a 10x10 square. To change the default to a 20x20 square, do {-20 -20 20 20} -> rc_select_distance; That will change the default sensitive area for newly created picture objects, not for existing objets. For larger objects the sensitive area can be expanded automatically. E.g. this will enlarge the "sensitive" area for drag2, to include its enclosing rectangle: rc_create_mouse_limit(drag2); rc_mouse_limit(drag2) => There is more information on rc_mouse_limit below. In particular its value can be a procedure. Try dragging the above objects to different locations, using mouse button 1. Check the coordinates before and after dragging, using these commands: rc_coords(drag1) => rc_coords(drag2) => If you try to drag the mouse too fast you may "lose" the object. Also you have to ensure that you click on the sensitive area to "select" the object. Once an object has been selected you no longer have to be accurate in re-selecting it for dragging. If you depress the SHIFT key in a blank part of the window, then if you depress mouse button 1, the previously selected object will "catch up" with the mouse. How this happens should be clear from the definition of the method rc_button_1_drag, for rc_window_object objects. See LIB * RC_MOUSEPIC/rc_button_1_drag Without the shift key, dragging works only with the mouse starting on the object, whereas with the shift key the last selected object is forcibly moved to wherever the mouse is being dragged. The relevant user-modifiable methods are shown below. They can be redefined to produce different behaviour, either for all selectable objects or for a new user-defined class. You can also select an object by clicking on it with button 1, then move the mouse cursor to another location, then put the shift key down, then click on mouse button 1. The previously selected object will jump to the new location. This can be faster than dragging all the way, for a complex object. Here's an even more complex draggable object, including coloured circular blobs, drawn using the library procedure rc_draw_blob. define :instance drag3:dragpic; pic_name = "drag3"; rc_picx = 120; rc_picy = 60; rc_pic_lines = [WIDTH 3 COLOUR 'black' [CLOSED {-30 20} {30 20} {30 -20} {-30 -20}] [CIRCLE {0 15 10}] [WIDTH 2 SQUARE {-10 -10 15}] ;;; give rc_draw_blob four sets of inputs, to draw ;;; a circular blob at each order [rc_draw_blob {-35 25 20 'red'} {35 25 20 'red'} {35 -25 20 'blue'} {-35 -25 20 'blue'}] ]; rc_pic_strings = [[FONT '8x13bold' COLOUR 'blue' {-20 -10 'drag3'}]]; enddefine; Draw it win2 -> rc_current_window_object; rc_draw_linepic(drag3); Make it mouse sensitive rc_add_pic_to_window(drag3, win2, true); Now try dragging it. The global variable rc_fast_drag has a default value of true. This speeds up dragging but can also make it more discontinuous. To see the effect of this variable on the dragging of a complex object do false -> rc_fast_drag; Then try dragging the object drag3. Then try again after restoring the default. true -> rc_fast_drag; (On a very fast machine you may not notice any difference!) If an object is added to two windows then dragging it in one can ruin the picture in the other when you try to drag it there. Try adding drag3 to win1 and then dragging it in both. rc_add_pic_to_window(drag3, win1, true); win2 -> rc_current_window_object; rc_undraw_linepic(drag3); win1 -> rc_current_window_object; rc_draw_linepic(drag3); The problem is that at present each picture has only one set of window coordinates, so it can be in only one window, and it can easily get "confused" if drawn and moved in two different windows. (A static object drawn in the same place in both should give no problem.) Remove drag3 from win2; rc_remove_pic_from_window(drag3, win2); rc_redraw_window_object(win2); rc_redraw_window_object(win1); A copy of drag1 can be added to win2 (copydata is needed to ensure a fully recursive copy): rc_add_pic_to_window(copydata(drag1), win2, true); rc_redraw_window_object(win2); The new copy of drag1 in win2 can be moved independently of the copy in win1. In fact a copy can be made in the same window, though the old one will have to be moved to make both visible, as drawing the new one over the old one makes both invisible! rc_current_window_object => rc_add_pic_to_window(copydata(drag1), win1, true); rc_redraw_window_object(win1); rc_move_by(drag1, -5, -5, true); Notice how the order of items printed out by this command changes depending on which picture you select with the mouse immediately before giving this command. rc_window_contents(win1) => -- Testing the picture selection algorithm ---------------------------- Check which of the pictures is selected by various coordinates The items selectable at the location are returned, and the number of items. If none are, then just 0 is returned. Note how the objects drag1 and drag3 are printed: drag1=> drag3=> win1 -> rc_current_window_object; rc_move_to(drag1, 0, 60, true); rc_move_to(drag3, -30, -60, true); The pictures are not close together, so they are unlikely both to be selected via any one location. We can see which pictures would be selected by clicking mouse button 1 at different locations. rc_pictures_selected(win1, 0, 55, false) => rc_pictures_selected(win1, 100, 55, false) => rc_pictures_selected(win1, -35, -55, false) => But if they are moved closer together ... rc_move_to(drag1, 30, 60, true); rc_move_to(drag3, 25, 55, true); The pictures are now close together, so they may be selected simultaneously. rc_pictures_selected(win1, 25, 55, false) => rc_pictures_selected(win1, 15, 55, false) => rc_pictures_selected(win1, 40, 75, false) => Notice that this procedure returns the selected objects and a number saying how many were selected. If the fourth argument is true, instead of false, then only the first object is returned, and the number 1. rc_pictures_selected(win1, 15, 55, true) => -- Making rc_mouse_limit a procedure ---------------------------------- So far we have seen that the value of the rc_mouse_limit slot for a picture object may be either an integer, representing the size of a square surrounding the local origin of the picture, or a vector giving coordinates of a rectangle in terms of local picture coordints. To allow greater generality, the value of the slot can be a procedure taking five arguments and returning a boolean. limit_procedure(x, y, picx, picy, pic ) -> boolean; Where x, y are mouse cursor coordinates on the window picx, picy, are the picture object coordinates on the window, and pic is the picture object. We give as an example a procedure for checking that the mouse is withing a particular distance of the centre. define dragdist(x, y, picx, picy, pic, dist ) -> boolean; ;;; This will be partially applied to an integer to ;;; produce a checking procedure. (x - picx)**2 + (y - picy)**2 < dist*dist -> boolean; enddefine; ;;; Try changing drag1 to have a sensitive radius of 75 in picture ;;; units, using dragdist. dragdist(%75%) -> rc_mouse_limit(drag1); Now try dragging the object drag1. It should be possible to pick it up from a great distance. By making the number very large you can make it selectable from anywhere on the window. E.g. dragdist(%1000%) -> rc_mouse_limit(drag1); If this is done to the object that is already the first in the list of objects known to the screen then accessing the other objects with the mouse can be impossible. Before continuing with the next example, get rid of the two windows. rc_kill_window_object(win1); and do the same for win2. -- Example: a toy painting easel -------------------------------------- An example of the use of these facilities is to be found in the PAINTING_DEMO library. To try it out do this uses rclib Then EITHER compile this loadrcdemo painting_demo OR, in VED ENTER rcdemo painting_demo And compile the file with the ENTER l1 command. Then give this command: painting_go(); Then use the left button to select a colour on the left (default black), and select a desired brush at the top. The selected brush must be dragged into the painting area. As you drag a brush it does nothing unless you depress SHIFT, in which case it draws a trail, but only in the main picture area. Release the mouse button to return brush to brush holder, using magical invisible elastic. Change the colour and draw some more. Use the white colour to erase bits of the picture. When finished do this to get rid of the window: rc_kill_window_object(the_easel); Or click on the "Dismiss" button. You can clear the picture by clicking on "Restart" if you wish to continue drawing. You change the size of the window before you click on RESTART. The code for this demonstration can be examined if you give the command ENTER rcdemo painting_demo You can control the initial size and location of the picture with a command of the form: start_easel(screenx, screeny, width, height); e.g. the following: start_easel(500,20,350,330); (It may adjust your width and height to accommodate the brush rack at the top and the paint pots on the left.) Adjusting the size of the window after creating it may produce unexpected effects. -- -- Exercise on extending the painting demo To extend the easel with more colours and more types of brushes, do ENTER rcdemo painting_demo then edit the lists of colours and brushes and make appropriate changes. You may have to redefine the start_easel procedure,e.g. to make it produce several columns of colours, and more than one row of brushes. -- How the objects were made draggable: method definitions ------------ Clicking with a mouse, moving the mouse, dragging (i.e. moving with a mouse button down) are all events that are handled by user-definable methods. LIB RC_MOUSEPIC defines some default event handling methods for draggable objects, and methods for handling mouse events at blank locations in the window. These event handling methods are what produced the dragging behaviour, described above. The methods defined in the library are copied in TEACH * RC_MOUSEPIC for convenience. Further information about event handling is provided in HELP * RC_LINEPIC/'Event handlers'. -- Constrained movers ------------------------------------------------- A first draft library for creating constrained movers (e.g. sliders) is provided in LIB * RC_CONSTRAINED_MOVER. You can try it out by doing uses rc_constrained_mover Then try variants of the following. Create a new window (possibly destroying old ones first): vars movewin = rc_new_window_object(400, 40, 400, 300, true); 'movewin' -> rc_window_title(movewin); rc_mousepic(movewin); ;;; Define a class of objects that can only move horizontally. define :class hor_dragpic; is rc_horiz_constrained_mover rc_selectable; ;;; default name dragpic1, dragpic2, etc. slot pic_name = gensym("horiz"); enddefine; ;;; and an instance: define :instance hor1:hor_dragpic; rc_picx = 100; rc_picy = 50; rc_pic_lines = [WIDTH 2 COLOUR 'black' [SQUARE {-25 25 50}] [CLOSED {-30 20} {30 20} {30 -20} {-30 -20}] ]; rc_pic_strings = [[FONT '9x15bold' COLOUR 'yellow' {-22 -5 'horiz'}]]; enddefine; ;;; Draw it and test it, including trying to drag it around the window. movewin -> rc_current_window_object; rc_draw_linepic(hor1); rc_add_pic_to_window(hor1, movewin, true); rc_move_to(hor1, 200,50, true); ;;; Try making it move diagonally: repeat 60 times rc_move_by(hor1, -2,-2, true) endrepeat; hor1=> ;;; create a class of objects that can move only vertically define :class vert_dragpic; is rc_selectable rc_vert_constrained_mover; ;;; default name dragpic1, dragpic2, etc. slot pic_name = gensym("vert"); enddefine; ;;; and an instance define :instance vert1:vert_dragpic; rc_picx = 100; rc_picy = 50; rc_pic_lines = [WIDTH 2 COLOUR 'blue' [SQUARE {-25 25 50}] [CLOSED {-30 20} {30 20} {30 -20} {-30 -20}] ]; rc_pic_strings = [[FONT '9x15bold' COLOUR 'red' {-22 -5 'vert'}]]; enddefine; ;;; display it, and try dragging it about: movewin -> rc_current_window_object; rc_draw_linepic(vert1); rc_add_pic_to_window(vert1, movewin, true); rc_move_to(vert1, 200,50, true); ;;; try moving it diagonally: repeat 60 times rc_move_by(vert1, -2,-2, true) endrepeat; vert1=> ;;; create a class of objects constrained to move in the line joining ;;; two points (e.g. for building sliders): define :class ptcons_dragpic; is rc_selectable rc_point_constrained_mover; slot pic_name = gensym("ptcons"); enddefine; ;;; and an instance define :instance ptcons1:ptcons_dragpic; rc_picx = -90; rc_picy = 90; ;;; these are the two constraining end points rc_pic_end1 = conspair(-90,90); rc_pic_end2 = conspair(90,-90); rc_pic_lines = [WIDTH 2 COLOUR 'black' [SQUARE {-25 25 50}] [CLOSED {-30 20} {30 20} {30 -20} {-30 -20}] ]; rc_pic_strings = [[FONT '9x15bold' COLOUR 'red' {-22 -5 'ptcons'}]]; enddefine; ;;; draw the object, make it draggable, and try moving it around movewin -> rc_current_window_object; rc_draw_linepic(ptcons1); rc_add_pic_to_window(ptcons1, movewin, true); rc_move_to(ptcons1, 200,50, true); ;;; Try moving it in an arbitray direction: repeat 60 times rc_move_by(ptcons1, -2,5, true) endrepeat; It should only move diagonally between the two points specified, whether dragged or moved by the mouse. You should now have three objects each constrained to move in a different direction from the others. Get rid of the window rc_kill_window_object(movewin); A particular application of this class is the creation of sliders, which have a movable button whose locatation is linked to the value of a variable. See LIB * RC_SLIDER for details. Some examples of the use of sliders, and additional information, can be found in TEACH * RCLIB_DEMO.P/sliders LIB * RC_POLYPANEL HELP * RC_CONTROL_PANEL -- A more complex example: rc_blocks ---------------------------------- The standard Poplog distribution includes a demonstration program that shows a simulated robot that uses a sentence parser integrated with a semantic interpreter and a planner that simulates a subset of the mechanisms in Winograd's famous SHRDLU program (circa 1971, described in many text books on AI written in the 1970s and 1980s, e.g. Margaret Boden: Artificial Intelligence and Natural Man.) The program is LIB * MSBLOCKS ("MS" stands for "Mental schemata" the course at Sussex university for which this was originally developed. There are two teach files TEACH * MSBLOCKS and TEACH * MSDEMO That program uses the Ved window to show simple graphics, e.g. using a rectangle of occurrences "g" to represent a green block. There is now a new version, based on mechanisms described above, which has full graphics, with real coloured pictures moving around. It also shows the parse trees graphically, using the rc_showtree program produced by Riccardo Poli on the basis of LIB SHOWTREE. To run the demonstration do uses rclib uses rc_blocks ENTER blocks Alternatively, try typing this to the shell in an Xterm window pop11 +gblocks %x If a saved image has been created, that will start it and make it run an Xved window in which you can type commands, alongside the graphical window showing the simulated world manipulated by the robot. When finished either type "bye" to the program or click on the QUIT panel with the left mouse button. -- Using lib rc_buttons.p to create a VED control panel ------------- Discard previous windows if necessary: rc_kill_window_object(win1); rc_kill_window_object(win2); See HELP * RC_BUTTONS for a demonstration of button creation in a graphic window, using the mechanisms described in this file. It also includes various sorts of menus and display panels with messages to inform the user. -- Non-draggable objects ---------------------------------------------- Not all objects need to be movable or draggable. It is possible to use the following mixins defined in LIB * RC_LINEPIC, for simpler types of objects mixin rc_linepic For objects that cannot be moved mixin rc_linepic_movable For objects that can be moved mixin rc_rotatable For objects that can be moved and rotated -- A class of static objects ------------------------------------------ For static objects, the rc_linepic mixin provides all the relevant machinery. We can define a class using it. define :class rc_static; is rc_linepic; ;;; Note: not rc_linepic_movable, nor selectable slot pic_name = gensym("static"); slot rc_pic_lines = [ ;;; Use "WIDTH" to specify line thickness ;;; One sub-picture forming a closed four-point polygon [WIDTH 3 CLOSED {-20 20} {20 20} {20 -20} {-20 -20}] ]; enddefine; Compile that (using ENTER lcp, or ESC c in VED). Note that instances of this class will inherit additional slots from the rc_linepic specification, namely rc_picx, rc_picy, and rc_pic_strings. These are used below in creating instances. Also because the pic_name slot value is defined using "=", not "==" the command gensym("static") will be run each time an instance is created, producing a new default name for each new instance. ( See TEACH * SLOT_DEFAULTS, REF * OBJECTCLASS/'Field Specifications' ) -- A class of movable objects ----------------------------------------- Some objects are movable without being draggable. Define a subclass with the properties of the rc_linepic_movable mixin included. This automatically inherits from rc_linepic (though not from the class rc_static defined above). In addition methods for moving pictures from one location to another become available. Also re-drawing an object in the same place will make it disappear (unless Glinefunction is given a different value). Instances of the class rc_move defined below will show a cross made of two line segments, by default. The default can be overridden by a different specification for the rc_pic_lines slot. The standard line width for rc_graphic (value 0) is overridden, by the WIDTH specification in the picture. define :class rc_mover; is rc_linepic_movable; ;;; Note: not rc_keysensitive rc_selectable slot pic_name = gensym("mover"); slot rc_pic_lines = [ WIDTH 2 [{-20 20} {20 -20} ] [{-20 -20} {20 20}]]; enddefine; -- Make some instances ------------------------------------------------ We can make instances of both the rc_static and rc_mover classes defined above. define :instance stat1:rc_static; rc_picx = 100; rc_picy = 50; rc_pic_strings = [FONT '8x13bold' {-17 -5 'stat1'}]; enddefine; define :instance move1:rc_mover; ;;; This will inherit the default picture rc_picx = 0; rc_picy = 0; ;;; But add some strings rc_pic_strings = [FONT '8x13bold' {0 20 'a'}{20 0 'b'}{0 -20 'c'} {-20 0 'd'}]; enddefine; define :instance move2:rc_mover; ;;; Override the default rc_mover picture. ;;; Include a circle of linewidth 2 and colour blue ;;; and a bigger one of linewith 3 and colour red rc_picx = 0; rc_picy = -150; rc_pic_lines = [ ;;; red circle at 0,0 radius 20 [WIDTH 3 COLOUR 'red' CIRCLE {0 0 20} ] ;;; blue circle at 0,20 radius 15 [WIDTH 2 COLOUR 'blue' CIRCLE {0 20 15}] ]; rc_pic_strings = [[FONT '6x13bold' {-16 -5 'move2'}]]; enddefine; Print out the instances stat1 => move1 => move2 => The printing is somewhat verbose. Define a print_instance method for rc_linepic to show only the name and coordinates: define :method print_instance(p:rc_linepic); printf('', [%pic_name(p), rc_coords(p) %]) enddefine; stat1 => move1 => move2 => You can still get full information by using datalist and printing the list of contents, thus: datalist(move2) ==> -- Drawing an instance of rc_static ----------------------------------- Get rid of the previous picture if necessary: rc_kill_window_object(buttonwin); Create a new window: vars win1 = rc_new_window_object(500, 40, 400, 350, true); Draw stat1 rc_draw_linepic(stat1); Because this is an instance of rc_static, redrawing will not make the picture go away: rc_draw_linepic(stat1); and this will not work rc_undraw_linepic(stat1); ;;; MISHAP - Method "rc_undraw_linepic" failed ;;; INVOLVING: ;;; DOING : mishap fail_generic(I,G) rc_undraw_linepic .... -- -- Changing the default colour used Check default print colour rc_foreground(rc_window)=> This will by be an integer, e.g. 1 for black or 0 for black, or the other way round depending on the terminal. Try changing the colour and redrawing: 'red' -> rc_foreground(rc_window); rc_draw_linepic(stat1); 'blue' -> rc_foreground(rc_window); rc_draw_linepic(stat1); 'black' -> rc_foreground(rc_window); rc_draw_linepic(stat1); rc_foreground(rc_window) => Since neither rc_static nor stat1 specifies a colour, the colour used depends on the current foreground. -- Drawing instances of rc_mover -------------------------------------- Movable objects can be drawn using the same method: rc_draw_linepic(move1); rc_draw_linepic(move2); As these are movable, re-drawing will make them disappear rc_draw_linepic(move1); rc_draw_linepic(move2); also rc_undraw_linepic can be used on them. Look back at the definitions of rc_mover and move1 and move2 to understand what happens, and why move2 has different coloured parts. Try moving move1, first using absolute, then relative coordinates. Give both commands repeatedly. Why does only one of the cause repeated changes? rc_move_to(move1, 30, 50, true); rc_move_by(move1, -5, -5, true); ;;; repeat a few times Put it at the centre of the window rc_move_to(move1, 0, 0, true); NB see previous comment about changing Glinefunction if moving does not work. -- The appearance of overlapping objects Note the unintuitive changes of colour when a movable object passes over another object. This is due to the simple drawing algorithm used, which is designed to make it easy to "uncover" a temporarily covered object. If a non movable object is drawn over another object it does not have this "transparent" effect. (Examples of non-movable objects are given below, using the class rc_static.) However, if a static object is drawn over a movable object and then the movable object is moved, the appearance of the static object will be permanently "damaged". You can experiment with instances of rc_static and rc_mover (or dragpic defined above_ to observe this effect. To avoid the effect make sure that all static objects are drawn first. Then as movable objects slide over them the changes in appearance will be only temporary. -- Making moving pictures --------------------------------------------- Here is a procedure to demonstrate motion of pictures under program control. define test_moves(pic, drawmode); ;;; Drawmode can be true, false, or "trail" -- given as ;;; the third argument to rc_move_by ;;; repeatedly move and draw pic repeat 15 times ;;; move up right rc_move_by(pic, 5, 5, drawmode); endrepeat; repeat 15 times ;;; move right rc_move_by(pic,5,0, drawmode); endrepeat; repeat 10 times ;;; move down rc_move_by(pic,0,-5,drawmode); endrepeat; repeat 10 times ;;; move down left rc_move_by(pic,-5,-5, drawmode); endrepeat; repeat 20 times ;;; move left rc_move_by(pic,-5,0,drawmode); endrepeat; enddefine; Clear the screen in the current picture; rc_clear_window_object(win1); Inform the pictures that they are now undrawn. rc_undraw_all([^move1 ^move2]); rc_draw_linepic(move1); rc_move_to(move1, 0, 20, true); rc_draw_linepic(move2); rc_move_to(move2, -30, -40, true); Now try moving move1 and move2 around without leaving a trail: test_moves(move1, true); test_moves(move2, true); Moving and drawing move2 is slower because of the colour switching. -- Demonstrating motion in "trail" mode ------------------------------- Now try moving move1 and move2 around in "trail" mode rc_move_to(move1, 0, 20, true); rc_move_to(move2, -60, 40, true); repeat 3 times test_moves(move1, "trail"); endrepeat; repeat 3 times test_moves(move2, "trail"); endrepeat; Drawing in "trail" mode is a bit faster because it does not have to continually undraw. You can get rid of the previous window with the command: rc_kill_window_object(win1); -- Predefined types of sub-pictures: RECT and SQUARE There are special cases of drawable objects recognized by the drawing methods. CIRCLE was one, shown above. RECT and SQUARE are others. define :instance rects:rc_mover; pic_name = "rects"; rc_picx = 0; rc_picy = 0; rc_pic_lines = [ ;;; A rectangle centre -25, 25, width 60 height 40 [RECT {-25 25 80 40}] ;;; two squares [SQUARE {-20 20 15} {5 -5 40}] ;;; A thick green line [WIDTH 3 COLOUR 'green' {-15 -10} {15 -10}] ]; rc_pic_strings = [[FONT '9x15bold' {5 5 'rects'}]] enddefine; rects => rc_start(); Draw it rc_draw_linepic(rects); This is an instance of rc_mover, and can be moved repeat 60 times rc_move_by(rects, -2, -2, true) endrepeat; For examples using oblongs see below. -- Rotatable objects -------------------------------------------------- The rc_rotatable mixin is used for creating objects that can be rotated. They have two extra slots, rc_axis, and rc_oldaxis. The one that is important for users, is rc_axis, which defaults to 0, representing the original orientation of the picture. Changing that to a new number represents a rotation of the object. Increasing rc_axis values represent rotations counter clockwise. The rc_oldaxis slot is used by system procedures for drawing rotating objects. Warning: some drawing procedures directly access external X mechanisms (such as rc_draw_rectangle, which uses XpwDrawRectangle). These cannot take account of rotations of the rc_graphic coordinate frame, so components based on them will not rotate when a picture rotates (though their start-point within the picture will rotate). Picture components created from vectors produced by rc_drawline will rotate as expected, though drawing them will be slower. This is illustrated below. ;;; A class, based on the rc_rotatable mixin define :class rc_rotator; is rc_rotatable; slot rc_axis = 0; slot pic_name = "rot0"; enddefine; ;;; Make a new printing procedure for the class, showing the axis ;;; as well as coordinates define :method print_instance(p:rc_rotator); printf('', [%pic_name(p), rc_coords(p), rc_axis(p) %]) enddefine; ;;; Make an object in that class consisting of a line with a circle near ;;; one end. define :instance rp1:rc_rotator; pic_name = "rp1"; rc_picx = 50; rc_picy = 100; rc_pic_lines = [WIDTH 2 [{5 5} {30 30}][COLOUR 'pink' CIRCLE {25 25 5}]]; enddefine; ;;; A rotatable arrow shape define :instance rp2:rc_rotator; pic_name = "rp2"; rc_picx = 100; rc_picy = 50; ;;; Make an arrow with a blue head rc_pic_lines = [WIDTH 3 [{0 0} {30 0}][COLOUR 'blue' {25 8}{30 0}{25 -8}]]; enddefine; ;;; demonstrate rotatbale objects rp1 => rp2 => rc_start(); ;;; Draw the rotatable instances rc_draw_linepic(rp1); rc_draw_linepic(rp2); ;;; re-drawing will make them alternately come and go ;;; Rotatable objects can be moved rc_move_by(rp1, -10, -10, true); rc_move_by(rp2, 0, 10, true); ;;; And rotated ;;; first undraw rp1 rc_draw_linepic(rp1); 0 -> rc_axis(rp1); rc_draw_linepic(rp1); 30 -> rc_axis(rp1); rc_draw_linepic(rp1); -45 -> rc_axis(rp1); rc_draw_linepic(rp1); 45 -> rc_axis(rp1); rc_draw_linepic(rp1); -90 -> rc_axis(rp1); rc_draw_linepic(rp1); -135 -> rc_axis(rp1); rc_draw_linepic(rp1); ;;; clear and restart rc_start(); rc_undraw_all([^rp1 ^rp2]); rc_draw_linepic(rp1); rc_draw_linepic(rp2); ;;; Or using rc_set_axis to cause automatic undrawing when moving rc_set_axis(rp1, 90, true); rc_set_axis(rp1, 135, true); rc_set_axis(rp1, 0, true); vars x; for x from 0 by 10 to 360 do rc_set_axis(rp1, x, true) endfor; ;;; Rotating can also be done in "trail" mode vars x; for x from 0 by 10 to 360 do rc_set_axis(rp1, x, "trail") endfor; ;;; Then do it again for x from 0 by 10 to 360 do rc_set_axis(rp1, x, "trail") endfor; ;;; Or by using rc_turn_by to do relative rotation, along with ;;; relative motion rc_turn_by(rp1, 10, true);rc_move_by(rp1, 5,5,true); rc_move_to(rp2,150,150,true); repeat 36 times rc_turn_by(rp2, 10, true);rc_move_by(rp2,-5,-5,true); ;;; use syssleep to slow it down syssleep(10); ;;; sleep for 10/100 of a second endrepeat; ;;; Bring it back to the original location repeat 36 times rc_turn_by(rp2, 10, true);rc_move_by(rp2,5,5,true); syssleep(5); endrepeat; ;;; Rotating with a trail ;;; Prepare rp1 for a demonstration rc_move_to(rp1,50,150,true); rc_set_axis(rp1,0,true); ;;; Mark these two loops and then do them both repeatedly. repeat 36 times rc_turn_by(rp1, 10, true);rc_move_by(rp1, -5, -5, "trail"); endrepeat; repeat 36 times rc_turn_by(rp1, 10, true);rc_move_by(rp1, 5, 5, "trail"); endrepeat; ;;; Try that with different initial angles rc_set_axis(rp1,90,true); ;;; then try the above loops rc_set_axis(rp1,180,true); ;;; ditto Try it also with rp2 rc_set_axis(rp2,90,true); ;;; then try the above loops repeat 36 times rc_turn_by(rp2, 10, true);rc_move_by(rp2, -5, -5, "trail"); endrepeat; repeat 36 times rc_turn_by(rp2, 10, true);rc_move_by(rp2, 5, 5, "trail"); endrepeat; rc_set_axis(rp2,180,true); ;;; ditto rc_set_axis(rp2,45,true); ;;; ditto -- Static, movable and rotatable pictures ----------------------------- -- -- Classes based on rc_linepic The rc_linepic library defines these mixins rc_linepic, rc_linepic_movable, rc_rotatable Since we cannot create instances of mixins directly, we have to attach them to a class. To illustrate this, we defined above the following classes 1. rc_static This inherits slots and methods from rc_linepic mixin. 2. rc_mover This inherits from the rc_linepic_movable mixin (and therefore also from rc_linepic) 3. rc_rotator This inherits from the rc_rotatable mixin Below we show how to define a new mixin rc_thick, which allows lines and pictures to have a thickness, and we'll show how to extend the drawing methods to take account of the thickness. In terms of that we'll define another class 4. rc_thickpic This inherits from rc_rotator and the rc_thick mixin. -- -- Classes based on rc_mousepic and rc_window_object We have also illustrated classes based on rc_mousepic. The library introduces the following: Mixins: rc_selectable rc_keysensitive Class: rc_window_object, based on rc_selectable rc_keysensitive; We've used those mixins and the associated methods to introduce the class: 5. dragpic This inherits from these mixins: rc_keysensitive rc_selectable rc_linepic_movable. We showed how to make instances of this respond to mouse actions, such as dragging. This depends on creating an instance of rc_window_object associated with the current rc_graphic window. -- -- Class rc_window_object Class: rc_window_object This is what is created by rc_new_window_object. Each instance represents a movable Pop-11 graphic window. It may also be given an associated instance of rc_window_object using the procedure rc_mousepic. -- -- Class rc_button Class: rc_button is rc_linepic, rc_selectable; Buttons can be drawn and made mouse sensitive. At present they are not movable, but it would be easy to define a movable subclass (e.g. by analogy with the definition of class dragpic, above.) -- -- Features of these classes We gave some of the classes a default pictorial representation. Instances of these classes can have different pictorial representations, overriding the defaults. An important feature of this library is that within the specification for each object the coordinates are all relative to the notional centre of the object. Thus if different instances of a class are located at different "centres" in the picture, their coordinates will be automatically adjusted (using the mechanisms of the RC_GRAPHIC library). Similarly if an object is moved from one location to another, there is no need to update the coordinates specifying the locations of its components: when the picture is redrawn in its new location they are automatically interpreted relative to the new centre. The same applies to text strings within an object. Each string is specified by a (relative) start location and the string to be printed starting there. As the location of the string is always relative to the object's coordinate frame, the string will move automatically on the graphic window with the rest of the object. Some objects can be rotated simply by rotating the object's coordinate frame. When the whole object is redrawn the components will also be redrawn rotated. Components made up of lines and circles can be rotated. However, text strings and some of the graphical objects created using X drawing routines directly, cannot be rotated, though the point from which they are drawn will be rotated. Methods for partially overcoming this are mentioned in some of the examples below. -- Undrawing: rc_undrawn and rc_undraw_all ---------------------------- If the window is cleared, e.g. using rc_start(), or rc_clear_window_object, or if the current window is replaced by another, then we have to tell the movable pictures that they are undrawn. We can use rc_undrawn. E.g. rc_start(); rc_undrawn(move1); rc_undrawn(move2); rc_draw_linepic(move1); rc_draw_linepic(move2); We can make several pictures undrawn in a single command: rc_start(); rc_undraw_all([^move1 ^move2]); Note that neither rc_undrawn nor rc_undraw_all will remove pictures from the screen. They should be used when a new window has been created or the window is clear. -- Drawing special figures -------------------------------------------- So far we have shown how to define drawable objects whose pictures are made of open or closed polygons, circles, rectangles, squares and print strings. The CIRCLE, RECTANGLE and SQUARE are examples defined in the rc_linepic library. It is also possible to provide user-defined picture types. -- -- Drawing circles, using CIRCLE As illustrated above, use the format [CIRCLE {x y radius}] to indicate a circle. The vector following the word CIRCLE should have three numbers, two specifying the centre of the circle (in coordinates relative to the centre of the object), and one for the radius. If several circles are to be drawn use one vector for each circle. This picture has two circles as well as a closed polygon: define :instance funny:rc_mover; pic_name = 'funny'; rc_picx = 100; rc_picy = -50; rc_pic_lines = [ [CLOSED {-20 6} {-15 35} {15 35} {20 6}] [CIRCLE {0 -5 20} {0 -5 10}] ]; rc_pic_strings = [{-15 -8 'funny'}]; enddefine; funny => rc_start(); rc_draw_linepic(funny); ;;; The relative bounds of the picture include the extremities of the ;;; circle. rc_linepic_bounds(funny, false) => ;;; as do the absolute bounds rc_linepic_bounds(funny, true) => repeat 20 times rc_move_by(funny, -5, 5, true); endrepeat; ;;; now with trail repeat 20 times rc_move_by(funny, -5, -5, "trail"); endrepeat; repeat 20 times rc_move_by(funny, 5, -5, "trail"); endrepeat; repeat 40 times rc_move_by(funny, 0, 5, "trail"); endrepeat; ;;; Now repeat the last three lines. ;;; Clear the picture rc_start(); -- -- Drawing a rectangle or square, using RECT, or SQUARE As shown above it is possible to draw a rectangle or square by using the CLOSED polygon option and specifying the four corners. A more compact, and faster drawing, option is provided. The key-words "RECT" and "SQUARE" followed by one or more vectors of numbers can be used to specify a set of rectangles or squares whose lines are parallel to the x axis and y axis. (Later we'll show how to use RRECT and RSQUARE to produce rotatable rectangles and squares). define :instance rects:rc_mover; pic_name = "rects"; rc_picx = 30; rc_picy = 50; rc_pic_lines = [ ;;; A rectangle centre -25, 25, width 80 height 40 [WIDTH 3 RECT {-25 25 80 40}] ;;; two squares [SQUARE {-20 20 15} {5 -5 40}] ;;; A thick red line line [WIDTH 5 COLOUR 'red' {-15 -10} {15 -10}] ]; rc_pic_strings = [[FONT '9x15bold' COLOUR 'green' {5 5 'rects'}]] enddefine; rects => rc_start(); ;;; draw it rc_draw_linepic(rects); rc_linepic_bounds(rects, false) => rc_linepic_bounds(rects, true) => ;;; This is an instance of rc_mover, and can be moved rc_move_by(rects, 10, 15, true); repeat 60 times rc_move_by(rects, -2, -2, true) endrepeat; -- -- Other figures It is possible to include other figures provided that there are drawing procedures that respect the relative coordinate system of the RC_GRAPHIC library. All that is needed is to have a list starting with the name of the procedure followed by one or more vectors containing arguments for the procedure. For example the rc_linepic library includes a procedure rc_draw_ob(x, y, width, height, radius1, radius2); for drawing oblongs, ie rounded rectangles. It requires six numbers. The first two give the location of the top left hand corner, the others the width, height and the two radii of curvature of the corners. (The procedure rc_draw_oblong in LIB RC_GRAPHIC uses only the last four arguments and draws from the current turtle location.) Here is a picture containing two such pictures inside a third. define :instance ob1:rc_mover; pic_name = "ob1"; rc_picx = 100; rc_picy = 100; rc_pic_lines = [ WIDTH 2 [rc_draw_ob ;;; two small oblongs, above and below the centre {-15 15 30 15 3 3} {-15 -5 30 12 3 3} ;;; and a bigger one enclosing them {-30 20 60 45 10 10}] ]; rc_pic_strings = [[FONT '8x13bold' COLOUR 'blue' {-10 5 'ob1'}]] enddefine; ob1 => rc_start(); ;;; draw it rc_draw_linepic(ob1); ;;; This is an instance of rc_mover, and can be moved rc_move_by(ob1, -10, -15, true); repeat 20 times rc_move_by(ob1, -5, -5, true) endrepeat; However, moving something like this can be slow! -- WIDTH: Pictures with variable line widths -------------------------- It is possible to use the word "WIDTH" followed by an integer, at the beginning of the list held in the rc_pic_lines slot of an object, or at the beginning of one of the sub-lists. The integer will then be used to determine the width of lines or circles specified by the rest of the list which it starts. For example, here is a picture containing rectangles of two thicknesses and circles of two thicknesses. define :instance thick1:rc_mover; pic_name = "thick1"; rc_picx = 100; rc_picy = 100; rc_pic_lines = ;;; default thickness for all the figures is 2 [WIDTH 2 [WIDTH 5 CLOSED {-35 35} {35 35} {35 -35} {-35 -35}] [RECT {10 -10 20 15}] [WIDTH 4 CIRCLE {0 35 19}] [CIRCLE {0 35 12}] ]; rc_pic_strings = [{-17 -5 'thick1'}]; enddefine; rc_start(); rc_draw_linepic(thick1); How that appears will in part depend on the font used. On a black and white image, the points where the different lines overlap come out as white. This is because of the use of "xor" for drawing everything. This may be thought to be a disadvantage. However, it is a necessary consequence of the simplicity of implementation of this package. Moving a complex object can be slow: repeat 60 times rc_move_by(thick1, -3, -2, true) endrepeat; Moving in trail mode is a little faster repeat 60 times rc_move_by(thick1, 3, 2, "trail") endrepeat; This explains why it is occasionally useful to use the "false" option for the moving procedures. The object will be moved without updating the screen. After a succession of moves inside the computer the resulting state can be shown on the screen, though care must be taken to prevent the picture becoming inconsistent Another example: a version of ob1 with thicker lines: define :instance ob2:rc_mover; pic_name = "ob2"; rc_picx = 100; rc_picy = 100; rc_pic_lines = [ [WIDTH 4 rc_draw_ob ;;; two small ones, above and below the centre {-15 15 30 18 3 3} {-15 -8 30 12 3 3} ] ;;; and a bigger thicker one enclosing them [WIDTH 7 rc_draw_ob {-30 25 60 55 10 10}] ]; rc_pic_strings = [[FONT '6x13bold' COLOUR 'green' {-11 3 'ob2'}]] enddefine; rc_start(); Draw it. The appearance may not be perfect, depending on the X utilities you are using. rc_draw_linepic(ob2); rc_move_by(ob2, -10, -5, true); -- Pictures with dashed and other line styles ------------------------- Just as the keyword WIDTH can be combined with a number to specify a width to be used for part of a picture, so can the keyword STYLE be followed by a linestyle specification. For available styles see HELP * RC_GRAPHIC/rc_linestyle At least the following should be available LineSolid ( = 0 -- the default) LineOnOffDash ( = 1 ) LineDoubleDash ( = 2 ) The keyword STYLE can occur on its own or AFTER the keyword WIDTH. If followed by one of the above symbolic names don't forget to use "^" to get the value. Here is an example of a picture using three different styles. define :instance ob3:rc_mover; pic_name = "ob3"; rc_picx = 100; rc_picy = 50; rc_pic_lines = [ ;;; A solid oblong above the centre [WIDTH 2 STYLE ^LineSolid COLOUR 'red' rc_draw_ob {-15 15 30 15 3 3}] ;;; A dashed rectangle below [STYLE ^LineOnOffDash RECT {-15 -3 30 12 } ] ;;; A bigger thicker double-dashed oblong [WIDTH 3 STYLE ^LineDoubleDash rc_draw_ob {-25 20 50 40 10 10}] ;;; A simple horizontal line, default style and thickness [{-30 -30} { 30 -30}] ]; rc_pic_strings = [[FONT '6x13bold' {-10 5 'ob3'}]] enddefine; ob3 => rc_start(); rc_draw_linepic(ob3); rc_move_to(ob3, 100, 50, true); rc_move_by(ob3, 0, -5, true); ;;; Moving such a thing can be very slow repeat 20 times rc_move_by(ob3, -3, -2, true) endrepeat; ;;; repeatedly redrawing it in the same place can have an interesting ;;; effect, because of the delays in drawing and erasing components. repeat 20 times rc_draw_linepic(ob3) endrepeat; -- Pictures with COLOUR specifications -------------------------------- In addition to the use of "WIDTH" and "STYLE" pictures or sub-pictures may use "COLOUR", as illustrated above to indicate a particular colour. Any combination of these three specifications can be used, but they must be in this order WIDTH STYLE COLOUR I.e. width, if present must be specified first, and colour last. If an angle is specified as in the next section, that must come before any of these. -- Drawing a sub-picture with a different scale XSCALE, YSCALE If a particular picture or sub-picture is to have a different scale then you can use XSCALE and/or YSCALE each followed by a positive or negative number as part of a picture or sub-picture specification. We can use the previous class of movable selectable pictures to illustrate this vars win1 = rc_new_window_object(500, 40, 300, 250, true); rc_mousepic(win1); define :class dragpic; ;;; this class inherits from three different "mixins" is rc_keysensitive rc_selectable rc_linepic_movable; enddefine; define :instance circ1:dragpic; rc_picx = 100; rc_picy = 50; ;;; two circles. The outer 1 blue, radius 40, the ;;; inner one red stretched by 2 horizontally and by ;;; -1.5 vertically rc_pic_lines = [WIDTH 2 COLOUR 'blue' [COLOUR 'red' XSCALE 2 YSCALE -1.5 CIRCLE {0 0 15}] CIRCLE {0 0 40} ]; rc_pic_strings = [FONT '8x13' COLOUR 'red' {-17 -5 'circ1'}]; enddefine; rc_draw_linepic(circ1); rc_move_by(circ1, -80,-80,true); ;;; now try the above again with the SCALE line commented out. -- Drawing part of a picture at an angle ------------------------------ It is possible to have a part of a picture drawn at a different orientation from the rest of the picture. That is done by using the keyword ANGLE at the beginning of the specification of that sub-picture. For example, here is a picture that has a circle based on its centre, and an arrow that will be drawn at different angles depending on the number following "ANGLE". define :instance ang1:rc_mover; pic_name = "ang1"; rc_picx = 100; rc_picy = 150; rc_pic_lines = [ ;;; a circle [CIRCLE {0 0 15}] ;;; Make an arrow [ANGLE 45 WIDTH 2 {0 0} {30 0}] [ANGLE 45 WIDTH 3 COLOUR 'blue' {25 8}{30 0}{25 -8}]]; enddefine; ang1 => rc_start(); rc_draw_linepic(ang1); It is possible to repeatedly change the number after ANGLE and re-draw, to get the effect of a rotating arrow, as follows: get the rc_pic_lines information out and keep changing the angle between drawing (rather nasty): rc_start(); rc_undrawn(ang1); vars ang; vars shaft = rc_pic_lines(ang1)(2), barb = rc_pic_lines(ang1)(3); for ang from 0 by 5 to 360*2 do rc_draw_linepic(ang1); ;;; show it rc_draw_linepic(ang1); ;;; remove it ;;; change the angle ang -> shaft(2); ang -> barb(2); endfor; rc_draw_linepic(ang1); -- A simple move handler for windows ---------------------------------- Get rid of the previous picture: rc_destroy(); Start a mew window and make it mouse sensitive: vars win1 = rc_new_window_object(500, 40, 300, 250, true); rc_mousepic(win1); Here is example of how you can re-define one of the default handlers yourself, to see what effect they have. This is the mouse motion event handler: define :method rc_move_mouse(w:rc_window_object, x, y, modifiers); ;;; select Ved's output file for printing, but prevent the ;;; file being writeable vededit('output.p', vedhelpdefaults); vedendfile(); ;;; Make printing go into the output buffer dlocal cucharout = vedcharinsert; ['Mouse moved at' ^x ^y in window] => if strmember (`c`, modifiers) then 'The Control button was depressed' => endif; enddefine; Move the mouse over the window. Move it a little, then let go, then move it, then move it back into the VED window. Each motion event should be recorded. The coordinates of the mouse are given relative to the rc_graphic coordinate frame as it was when the rc_mousepic procedure was applied to rc_window. An alternative method will move the ang1 picture defined above to the current mouse cursor location. rc_undrawn(ang1); define :method rc_move_mouse(w:rc_window_object, x, y, modifiers); rc_move_to(ang1, x, y, true); enddefine; If you have two different live windows and you have that method defined, then the same thing will be drawn on both windows. However, the picture will not remember its coordinates when you move from one window to another. So the method should keep track of which windows it has drawn on and where the last location was at which it drew on each window. That could be done using a property, mapping rc_window to a location. After experimenting with the motion event handler, disable the method by commenting out the print instruction and recompiling. Or compile this: define :method rc_move_mouse(w:rc_window_object, x, y, modifiers); ;;; do nothing enddefine; Check that moving the mouse over the window now does nothing. -- Detecting keyboard events: rc_handle_keypress ---------------------- The method rc_handle_keypress can be similarly defined to reveal keyboard events. It has an extra argument, the code for the key involved, which will be positive if the key is pressed, negative if it is released. If the key is an alphanumeric or symbol key the code will be the ascii code for the symbol otherwise one of the codes showin in HELP * RC_KEYCODES. The following can be used to find out the key code mappings on your keyboard. define :method rc_handle_keypress(w:rc_window_object, x, y, modifier, key); ;;; select Ved's output file for printing, but prevent the ;;; file being writeable vededit('output.p', vedhelpdefaults); vedendfile(); ;;; Make printing go into the output buffer dlocal cucharout = vedcharinsert; [ %if key >= 0 then 'Key pressed at' else 'key released at' endif% ^x ^y : key ^key modifier: ^modifier] ==> enddefine; Experiment with that. In particular move the mouse cursor into the graphic window then press various keys, including the space bar, function keys, shift, ENTER, etc. After these experiments redefine the method to do nothing. define :method rc_handle_keypress(w:rc_window_object, x, y, modifier, key); enddefine; -- Key code mappings for rc_handle_keypress --------------------------- The actual mappings between the keys pressed and the key codes given as final argument to rc_handle_keypress may vary from one system to another. By defining a procedure like the above you can experiment to find out the actual mappings. The results of such an experiment can be see in HELP * RC_KEYCODES It would be possible to use a property to record such associations, if needed. -- Defining additional event handlers --------------------------------- The default methods which do nothing are defined as follows. ;;; uncomment the print commands for testing define :method rc_button_2_down(pic:rc_selectable, x, y, modifiers); ;;; [button 2 down ^x ^y ^modifiers] => enddefine; define :method rc_button_3_down(pic:rc_selectable, x, y, modifiers); ;;;[button 3 down ^x ^y ^pic] => enddefine; define :method rc_button_1_up(pic:rc_selectable, x, y, modifiers); ;;; [button 1 up ^x ^y ^pic] => enddefine; define :method rc_button_2_up(pic:rc_selectable, x, y, modifiers); ;;; [button 2 up ^x ^y ^pic] => enddefine; ;;; etc. etc. ;;; and these for dragging, moving and handling key presses define :method rc_button_2_drag(pic:rc_selectable, x, y, modifiers); ;;; [button 2 drag ^x ^y ^pic ^modifiers] => enddefine; define :method rc_button_2_drag(pic:rc_window_object, x, y, modifiers); ;;; [button 2 drag ^x ^y ^modifiers] => enddefine; define :method rc_button_3_drag(pic:rc_selectable, x, y, modifiers); ;;; [button 3 drag pic ^pic ^x ^y ^modifiers] => enddefine; define :method rc_button_3_drag(pic:rc_window_object, x, y, modifiers); ;;; [button 3 drag ^x ^y nothing ^modifiers] => enddefine; define :method rc_move_mouse(pic:rc_selectable, x, y, modifiers); ;;; [move mouse ^x ^y ^pic ^modifiers] => enddefine; define :method rc_handle_keypress(pic:rc_selectable, x, y, modifiers, key); ;;; [keypress ^x ^y ^modifiers key ^key ] => enddefine; Notice that if you wish to distinguish actions on a selected object and actions on an empty space in the window, then define the empty space method using the following format, adding a "key" argument if it is a keypress handler. define :method (pic:rc_window_object, x, y, modifiers); ........ enddefine; You can try experimenting with different events. E.g. try making mouse button three with the shift key down make the selected picture move vertically 50 units (using rc_move by), and make it move verttically down 50 units if the control key is depressed. define :method rc_button_3_down(pic:rc_selectable, x, y, modifiers); ;;; translate this to pop11 if shift key is depressed then move down elseif control key is depressed then else ... endif enddefine; You could also try making an object rotate by clicking on it with the control button down, but only if it is an rc_rotatable object. E.g. create an instance of rc_rotator to check it out. -- MORE ON ROTATABLE OBJECTS ------------------------------------------ There are further complications regarding rotating objects that may be worth pointing out. -- -- How to make a printed string rotate If a rotatable object includes a print string, the string will not rotate when the object does, though its start point will. define :instance rp3:rc_rotator; pic_name = "rp3"; rc_picx = 0; rc_picy = 0; ;;; Make an arrow rc_pic_lines = [[{0 0} {30 0}][{25 8}{30 0}{25 -8}]]; ;;; And a string rc_pic_strings = [{0 -20 'arrow'}] enddefine; rc_start(); Draw it rc_move_to(rp3, 0, 50, true); Rotate it a few times. Repeat this command several times. rc_turn_by(rp3, 20, true) The result is not very satisfactory A partial solution is to break the string into substrings, as follows: define :instance rp4:rc_rotator; pic_name = "rp4"; rc_picx = 0; rc_picy = 0; ;;; Make an arrow rc_pic_lines = [[{0 0} {30 0}][{25 8}{30 0}{25 -8}]]; ;;; And a string rc_pic_strings = [[FONT '6x13bold' {0 -20 'a'} {7 -20 'r'} {14 -20 'r'} {21 -20 'o'} {28 -20 'w'}]]; ;;; For the 6x13 font, I incremented the x coordinate in steps of 7. enddefine; rc_start(); Draw it rc_move_to(rp4, 0, -20, true); Rotate it a few times. Repeat this command several times. rc_turn_by(rp4, 30, true) For some orientations the result may be acceptable, and not others. Creating a font out of vectors, subject to the same rotations as lines, would be another solution, but tedious. -- Allowing squares or rectangles to rotate Previously we saw how to include a RECT or SQUARE figure. For rotation, use RRECT and RSQUARE. The difference will now be demonstrated define :instance rp5:rc_rotator; pic_name = "rp5"; rc_picx = 0; rc_picy = 0; rc_pic_lines = [ ;;; A rectangle of thickness 2, non-rotatable [WIDTH 2 RECT {-25 25 50 45}] ;;; A rectangle of thickness 4, rotatable [WIDTH 4 RRECT {-35 35 75 65}] ;;; A line of current default thickness [{-15 -10} {15 -10}] ]; enddefine; rc_start(); Draw it rc_draw_linepic(rp5); Check that moving works rc_move_to(rp5, -75, -75, true); rc_move_by(rp5, 30, 30, true); Rotate it a few times. Repeat this command several times. rc_turn_by(rp5, -30, true); repeat 12 times rc_turn_by(rp5, 30, true) endrepeat; It will be seen that the smaller RECT figure does not rotate properly, whereas the larger RRECT figure does, as does the line. Use RECT to draw non-rotatable rectangles, and RRECT to draw rotatable ones. The latter are considerably less efficient. Similarly SQUARE and RSQUARE can be used, except that they require one less number than RECT and RRECT define :instance rp6:rc_rotator; pic_name = "rp6"; rc_picx = 0; rc_picy = 0; rc_pic_lines = [ ;;; an ordinary closed polygon [CLOSED {-10 15} {10 10} {10 -10}{-10 -15}] ;;; A non rotatable square [WIDTH 2 SQUARE {-20 20 40}] ;;; A rotatable square [WIDTH 4 RSQUARE {-30 30 60}] ]; enddefine; rc_start(); Draw it and move it rc_draw_linepic(rp6); rc_move_to(rp6, -75, -75, true); rc_move_by(rp6, 20, 30, true); rc_move_to(rp6, 30, 40, true); Rotate it rc_turn_by(rp6, 60, true); repeat 12 times rc_turn_by(rp6, -30, true) endrepeat; Note: the library procedures used for drawing rotatable and non-rotatable squares and rectangles defined in LIB RC_LINEPIC are: non-rotatable define rc_draw_rect(x, y, width, height); define rc_draw_square(x, y, side); rotatable define rc_draw_Rrect(x, y, width, height); define rc_draw_Rsquare(x, y, side); However, if you use these procedures directly to draw things you will not have an object inside Pop-11 corresponding to the picture drawn. So moving or interrogating the picture will not be possible. -- -- Part of a rotatable object may be offset by an angle The next figure is similar to rp6 except that the RSQUARE is tilted at an angle of 30 degrees. Using this is often much easier than computing coordinates of the corners. define :instance rp7:rc_rotator; pic_name = "rp7"; rc_picx = 0; rc_picy = 60; rc_pic_lines = [ ;;; an ordinary closed polygon [CLOSED {-10 15} {10 10} {10 -10}{-10 -15}] ;;; A non rotatable square [WIDTH 2 SQUARE {-20 20 40}] ;;; A rotatable square [ANGLE 30 WIDTH 4 RSQUARE {-30 30 60}] ]; enddefine; rc_start(); rc_draw_linepic(rp7); rc_move_to(rp7, -80, 30, true); rc_move_by(rp7, 20, 40, true); Rotate it. While the finger is being rotated, the outer RSQUARE figure, like the smallest polygon, maintains its angular offset relative to the rest, whereas the other SQUARE is not rotated. rc_turn_by(rp7, 60, true); repeat 36 times rc_turn_by(rp7, -10, true) endrepeat; -- Giving a class of objects a non-standard line-thickness ------------ This section shows how to use the facilities of objectclass to use existing methods in a new context. So far all the objects have been drawn with lines of the same default thickness. To have a different thickness we had to include a thickness specification explicitly in the sub-pictures. It is possible to define a type of object that is automatically drawn with lines of a different thickness. First we define a mixin and re-define the standard methods for it. define :mixin rc_thick; slot rc_thickness = 0; enddefine; ;;; These methods all need to be redefined define :method rc_move_to(pic:rc_thick, newx, newy, draw); dlocal rc_linewidth = rc_thickness(pic); call_next_method(pic, newx, newy, draw) enddefine; define :method rc_move_by(pic:rc_thick, dx, dy, draw); dlocal rc_linewidth = rc_thickness(pic); call_next_method(pic, dx, dy, draw) enddefine; define :method rc_set_axis(pic:rc_thick, ang, draw); dlocal rc_linewidth = rc_thickness(pic); call_next_method(pic, ang, draw) enddefine; define :method rc_turn_by(pic:rc_thick, ang, draw); dlocal rc_linewidth = rc_thickness(pic); call_next_method(pic, ang, draw) enddefine; ;;; Now define a class that inherits from rc_thick and rc_rotator. define :class rc_thickpic; is rc_thick rc_rotator; ;;; no new slots needed enddefine; Create an instance made of a triangle and a circle define :instance thick2:rc_thickpic; pic_name = "thick2"; rc_picx = 50; rc_picy = 50; rc_thickness = 3; rc_pic_lines = [[CLOSED {-20 -10}{0 15}{20 -10}][CIRCLE {0 10 10}]] enddefine; This will inherit the printing method from rc_rotator. thick2 => ** We can now move it rc_start(); rc_move_to(thick2, 100, 100, true); Try moving and rotating the object. repeat 10 times rc_move_by(thick2, -5, -5, true); endrepeat; repeat 36 times rc_turn_by(thick2, 10, true); endrepeat; repeat 36 times rc_turn_by(thick2, 10, true); rc_move_by(thick2, -3, 3, true) endrepeat; Note that rc_draw_linepic has not been redefined appropriately for this class. This is left as an exercise. Define the method and test it on this. rc_draw_linepic(thick2); -- A demonstration of moving pictures the "ant" demo ------------------ This section describes another demonstration, but may be skipped if you merely want to learn more about details. The file $poplocal/local/rclib/demo/rc_ant_demo.p contains a demonstration program illustrating moving objects. If you compile that file loadrcdemo rc_ant_demo OR load $poplocal/local/rclib/demo/rc_ant_demo.p OR ENTER rcdemo rc_ant_demo ENTER l1 then give a command something like this, where the number specifies the initial number of ants to be shown. rc_ant_demo(10); This will create a window with a sub-window containing moving ants. If there are too few they may move so fast that you cannot see them. In that case increase the number by clicking on the up arrow button. You can decrease the number by clicking on the decrement button. You can stop the program by clicking on stop. Watch the behaviour when the ants get together. To examine the code do ENTER rcdemo rc_ant_demo or ENTER pved $poplocal/local/rclib/demo/rc_ant_demo.p You can then rename the file, alter it, etc. -- -- Exercises on extending the ant demo. You could try the following. Add a button that makes the demonstration "pause". I.e. it stops the program but doesn't destroy the window. You should then also have a "restart" button. Try extending the definition of the ant class so that if you click on an ant it prints out information about its current location and heading. Try making the ant room mouse sensitive so that if you click on it a new ant is created at that point and added to the list of ants. -- Related documentation ---------------------------------------------- More on rc_linepic and rc_mousepic HELP * RC_LINEPIC Summarises the information presented here HELP * RCLIB Gives a broader overview TEACH * RC_INTRO, * RC_BUTTONS, * RC_MOUSEPIC Extend the information provided here SHOWLIB * RC_LINEPIC Inspect the library for creating, drawing and moving pictorial objects, under program control. SHOWLIB * RC_CONSTRAINED_MOVER Facilities for constrained movers SHOWLIB * RC_MOUSEPIC Inspect the library for making objects sensitive to mouse and keyboard actions. SHOWLIB * RC_WINDOW_OBJECT SHOWLIB * RC_BUTTONS General information about rc_graphic TEACH * RC_GRAPHIC HELP * RC_GRAPHIC At Birmingham the following "rapid" introduction is available: TEACH * GSTART ftp://ftp.cs.bham.ac.uk/pub/dist/poplog/teach/gstart RC_GRAPHIC Utilities (Warning some of these are local to Sussex and Birmingham) HELP * RC_BACKGROUND Shows how to change the background colour in an rc_graphic window. HELP * RC_WINDOW_COORDS For accessing and updating location of Xgraphic window HELP * RC_WINDOW_DIMENSIONS For accessing and updating the window dimensions TEACH * RC_ARRAY HELP * RCI_SHOW Part of the popvision library available from Sussex. For displaying images in rc_graphic windows. TEACH * RC_GRAPHPLOT Drawing graphs of many kinds Some of the utilities listed here, with associated documentation, including RC_LINEPIC and RC_MOUSEPIC may be fetched from the Poplog ftp directory at Birmingham ftp://ftp.cs.bham.ac.uk/pub/dist/poplog in the subdirectory rclib, also available as compressed tar file. --- $poplocal/local/rclib/teach/rc_linepic --- The University of Birmingham 1997. All rights reserved. ------------