In this chapter, we will discuss the more advanced features of Terrapin Logo. As a “casual” user, you will probably not need to read this section; if you want to write an app that uses controls like buttons, or if you want to work with mouse or touch events, this chapter is for you.
When you publish your app or save your workspace, Logo saves the current panel layout along with your workspace. You may also alter a panel’s LAYOUT property from within a Logo program to move or resize a panel.
The LAYOUT property is a four-element list. These are values for the left and top corner, the width and the size. The values are percentage values ranging from 0 to 100, and they refer to the size of your app window. A value of 50, for example, means that the offset or size is half of the window width or height.
See this example:
GPROP “LISTENER “LAYOUT Result: [0 60 100 40]
The Listener panel’s left border is at 0% (the left border of the window). The top is 60% of the total height. The width is 100%, making the panel occupy the entire width of the window, and the height is 40% making it stretch all the way down to the bottom of the window (60% + 40% = 100%).
Using percentage values as panel values makes panels adapt very easily to different screen layouts.
The STATE property controls the display mode of a panel. It is one of four values: NORMAL, MAXIMIZED, MINIMIZED, or HIDDEN.
If you want to have a list of all layouts, read the value of the the
:LAYOUT Result: [GRAPHICS [[0 0 100 60] NORMAL] LISTENER [[0 60 100 40] NORMAL] TOOLBOX [[80 0 20 25] HIDDEN] FILES [[80 25 20 25] HIDDEN] HELP [[70 50 30 50] HIDDEN] EDITOR [[10 10 50 50] HIDDEN] DEBUGGER [[0 5 100 70] HIDDEN]]
You can store that value elsewhere and set LAYOUT to that value later:
MAKE “MY.LAYOUT :LAYOUT …do something with a different layout… MAKE “LAYOUT :MY.LAYOUT
Actually, some Logo commands use the LAYOUT’ property. Look at this code:
TO FULLSCREEN PPROP "LISTENER "STATE "NORMAL PPROP "GRAPHICS "STATE "MAXIMIZED END TO SPLITSCREEN PPROP "LISTENER "STATE "NORMAL PPROP "GRAPHICS "STATE "NORMAL END TO TEXTSCREEN PPROP "LISTENER "STATE "MAXIMIZED PPROP "GRAPHICS "STATE "NORMAL END
The Initial Layout
After Logo loads and initializes, the classroom version attempts to load an INIT.LGO file from the classroom server, which could change the layout. Also, Logo tries to auto-load any layout that Logo has saved previously using the AUtosave settings in the Settings dialog.
As soon as Logo has finished its initialization phase and just before the Listener is ready to accept user input, Logo saves the layout found so far into the global :INITIAL.LAYOUT variable. Users can return to Logo’s initial panel layout in one of the following ways:
- Enter the command
MAKE “LAYOUT :INITIAL.LAYOUT
- Select the Window menu item “Initial Layout”
- By clicking the Initial Layout toolbar icon
The Default Layout
Logo’s default panel layout is available in the global :DEFAULT.LAYOUT variable. This variable is read-only, but you can always use that variable to reset the layout to the default.
- Enter the command
MAKE “LAYOUT :DEFAULT.LAYOUT
- Select the Window menu item “Original Logo Layout”
The Icon Bar
There are two ways to show or hide the icon bar programmatically. The first
is to read and alter the contents of the :LAYOUT variable.
The second is to access the
ICONBAR property list directly. This list
has a property
STATE that can be set to
When you create a user interface with a few controls, the controls would usually move when the Graphics panel is resized. Often, this is not desirable, because you want the controls to stay in one place.
Every widget, including bitmaps, turtles, and controls, has an
property. This property takes a list of two words (actually, you can
also use a single word to only set a single anchor). Its purpose is to
anchor the widget so it keeps a constant distance to an edge of the
Graphics panel. We have these words:
LEFT: The widget keeps a constant distance to the left edge, meaning that it does not move horizontally.
RIGHT: The widget keeps a constant distance to the right edge; it moves horizontally when the panel is resized.
CENTER: This is the default; the widget is anchored to the center of the panel.
TOP: The widget keeps a constant distance to the top edge, meaning that it does not move vertically.
BOTTOM: The widget keeps a constant distance to the bottom edge; it moves vertically when the panel is resized.
MIDDLE: This is the default; the widget is anchored to the center of the panel.
This is especially convenient for controls, who like to stay at the same
position regardless of the size of the Graphics panel. If you anchor a
[LEFT TOP], for example, it will stay at the same position
and not move if the panel is resized. An anchor of
would cause the control to move with a constant distance to the right
and bottom edges, so if it would be in the panel’s lower right corner,
it would stay there.
You would still have to position the controls initially, and figure out where to place them by looking at the size of the control, and the size of the Graphics panel.
This is how you get a control’s size:
GPROP "MYCONTROL "SIZE
And this is how you get the size of the Graphics panel:
GPROP "GRAPHICS "DRAWSIZE
So what if anchoring does not meet your needs? Well, you can also define an WHEN handler that is called whenever the size of the Graphics panel changes. Here is how do do this:
WHEN [GRAPHICS DRAWSIZE] [my list of commands]
But please keep in mind that the list of commands should be short; the event handler may be called very often if you resize the Graphics panel!
A Logo grid is a special control that lets you arrange widgets in a grid. This makes a grid a very powerful feature, because it is not necessary to re-align controls because of changes to the display size when you store them in a grid. Note that a grid cell can only take a single widget.
A grid is a
GRID widget, which you create either be
dragging the grid symbol from the Toolbox’ “Controls” panel to the
Graphics panel, or with the help of the NEW
or DECLARE commands. Once you have
created a grid, you can access its individual cells with special grid
commands. Initially, each grid cell contains a
STATICTEXT widget whose
text you can set via its
By default, a
GRID widget has a size of 3×3 cells. You can change the
number of rows by setting the grid’s
ROWS property, and you can set
the value of its
COLUMNS property to set the number of columns. You
can also use the SETGRIDDIMS command to
change the dimensions of the grid. Changing the grid’s dimensions erases
the contents of the grid, and destroys any widgets that have been stored
into the grid. A grid adjusts itself to the largest items of a row or
column. You can use the FILLGRID command
to quickly fill a grid with text content.
A grid cell has “coordinates” that range from 0 to the number of rows or columns minus one. Thus, the top left cell has the coordinates 0, 0, the next cell to the right has 1, 0, and so on. This has been modeled after Logo arrays, who have a similar way to access array elements. For a 3×3 grid, this is how you would address each element:
|0 0||0 1||0 2|
|1 0||1 1||1 2|
|2 0||2 1||2 2|
You would put “X” into the middle cell by
GSETTEXT “GRID 1 1 “X which
would result in…
Then someone would put “O” into the lower right cell by
“GRID 2 2 “O which would result in:
…and so on. If an element has not been defined yet, it contains an empty word.
If you prefer to start grid indexing from 1 on, you can change the
ARRAYBASE to the value
1 (or any other value that you prefer as the lowest index). This
settings also affects arrays. So, after issuing tthis command:
PPROP "PREFS "ARRAYBASE 1
You would address the grid as follows:
|1 1||1 2||1 3|
|2 1||2 2||2 3|
|3 1||3 2||3 3|
It is intentional that a grid is closely modeled after arrays. This makes it possible to quickly exchange data between an array and a grid. You can use the array to perform calculations, and then display the results by using a combination of the LISTARRAY and FILLGRID commands.
Initially, a grid has visible cell borders; you can change the color of
the borders by setting the grid’s
COLOR property to a different color,
or you can make it invisible by using a color with an alpha value of 0,
like, for example,
[0 0 0 0]. The grid’s
BORDER property controls
the grid’s outer border in the same way.
To avoid the clobbering of object names with the names of all
STATICTEXT widgets in a grid, all grid cells have a special name,
which is the name of the grid, a ‘#’ character, the column number,
another ‘#’ character and the row number. To set the “X” character in
the center cell of our grid, simply use this command:
PPROP "GRID#1#1 "TEXT "HELLO
You can change the properties of the entire grid, or a row or column with the GPPROP command, which makes it easy to apply styles to, for example, a header row.
For this chapter, we will stay with an
0, and a grid name of
“GRID. Therefore, start over with the
DRAW command,; then, select the “Controls”
part of the Toolbox panel, and drag and drop a Grid control to the
Graphics panel. This control should have the name “GRID”.
A grid has a few extra properties of its own.
||Returns or sets the number of grid columns. Note that if you set the value, the grid is recreated, causing the contents to get lost, and any widgets to be released.|
||This property sets the minimum size of each grid cell in pixels. The first list item is the width, and the second item is the height of each cell. If you use values that are ⇐ 0, the table aligns it cell width or height to match the widest item in a column, or the tallest item in a row. This is the default.|
||a color||Defines the color of the lines that separate each grid cell.|
||Returns or sets the number of grid rows. Note that if you set the value, the grid is recreated, causing the contents to get lost, and any widgets to be released.|
Try, for example to apply a cell size of 50 by 50 pixels:
PPROP "GRID "CELLSIZE [50 50]
Now, go back to the original values:
PPROP "GRID "CELLSIZE [-1 -1]
Let us create a nice table with row and column headers, and numbers that are right aligned.
Let us try to create a table of numbers. Here is the data to fill in:
FILLGRID "GRID [ [|| ONE TWO] [FIRST 0.1 -234.56] [LAST 123 500]]
|The “||” creates an empty word. We want all numbers to be right-aligned:|
Now, we will use the GPPROP command to style
the table and the headers. This command, if used without brackets,
applies a property to all cells of the entire table. If used with
brackets, you can add in the column and row numbers. As with the
CELLSIZE property, using a value less than the lowest possible index
causes the cell to ignore it. If you, for example, want to set a column,
use -1 for the row and vice versa.
GPPROP "GRID "ALIGN [RIGHT]
We want the column headers to be blue and centered:
(GPPROP "GRID "ALIGN [CENTER] -1 0) (GPPROP "GRID "COLOR "BLUE -1 0)
Finally, we want the row headers to be green, and also centered:
(GPPROP “GRID “ALIGN [CENTER] 0 -1)
(GPPROP "GRID "COLOR "GREEN 0 -1)
Here we are! Your grid should now look like this:
Once the grid has been styled, you can change its data, and the styles will remain. Try some other values:
FILLGRID "GRID [ [|| ONE TWO] [FIRST 100 200] [LAST 300 400]]
Can you see how powerful the FILLARRAY command can be?
You can also set or get the text of a single cell. These commands are essentially the same:
GGETTEXT "GRID 1 1 GPROP GGET "GRID 1 1 "TEXT GPROP "GRID#1#1 "TEXT
Whoa! What is this? GPROP “GRID#1#1 “TEXT ? Remember the paragraph
about special names above? This is such a special name. The name
GRID#1#1 is essentially a shortcut for the grid cell of “GRID, column
1 and row 1. You can use this special name whereever you can enter an
object name, as for example with the LIST or
Maybe it is time to look up the available grid commands? This page has the details.
Click My Cell
Would it not be great to know when the user has clicked or touched a
value? No problem! Every cell contains a widget, and every widget
RUN property. If you set that property to a runlist, Logo
executes that runlist every time the widget has been clicked or touched.
Grids and Widgets
A grid is perfect if you want to align all kinds of controls properly without having to calculate each control’s position. Logo lets you store any widget into a grid’s cell, including bitmaps, turtles, and of course, controls. There is a caveat, however: when you erase the grid, or replace the grid’s cell with something else, the widget is erased.
The GSET command lets you store a widget into a grid. Try, for example, this:
GSET "GRID 1 1 0
And your turtle (its name is “0”, remember?) suddenly appears inside the grid! Unfortunately, the turtle cannot move; it can rotate or change its size. It can even STAMP or FILL, but it cannot draw lines. The same happens to all widgets that you store into a grid; they cannot move anymore.
Now, let us start over. Note that your turtle is gone once the gird is gone, so it is a good idea to start over:
ERASE "GRID DRAW
Drag and drop a new grid. Now, drag and drop the checkboxes and a button as well. Then, issue these commands:
GSET "GRID 0 0 "CHECKBOX GSET "GRID 1 0 "CHECKBOX.1 GSET "GRID 2 0 "CHECKBOX.2 GSET "GRID 2 2 "BUTTON
As you may have expected, we have a neatly arranged array of controls.
For this special use case, we do not need the grid’s borders. To get rid
of the border, simply assign a transparent black color to its
property. Remember that a color value is a three- or four-element list,
where the last element is the opacity value. A value of 0 means fully
PPROP "GRID "BORDER [0 0 0 0]
Now, change the text of the controls:
PPROP "CHECKBOX "TEXT "|Try me| PPROP "CHECKBOX.1 "TEXT "|You can also try me| PPROP "CHECKBOX.2 "TEXT "|Or you can add this, too| PPROP "BUTTON "TEXT "|Done|
And you have a great-looking dialog!
You may have noticed that the widget names stay even if they become part
of the grid. Actually, you can use both the real and the special names
to access the widget. To talk to the button, for example, you can use
“GRID#2#2. This lets you connect your program
logic to names that you have defined even if you decide to rearrange the
controls at a later time. See the DECLARE
command for more information about how to declare named widgets.
The new .MACRO and .DEFMACRO commands let you define macros. A macro is almost a Logo procedure, with one significant difference: A macro is supposed to output a list of commands that Logo runs after the macro ends.
OK, this is complicated stuff. Let’s try a really simple example:
.MACRO MY.PRINT :WHAT OUTPUT LIST "PRINT :WHAT END
MY.PRINT 5 would output the command
[PRINT 5], which Logo runs
afterwards to print
But why do we need such stuff?
Let us assume that you define a new loop command that runs a runlist which you supply as input to your new loop command; something like this:
TO MYLOOP :runlist ; omitting the control code... RUN :runlist END
Now, use this command within some other procedure:
TO SOME.PROCEDURE :N ... MYLOOP [IF :N = 0 STOP] ... END
What would now happen if Logo executed the STOP
command? You would probably want your procedure to exit. But in the real
MYLOOP is a procedure,
MYLOOP would exit instead of
SOME.PROCEDURE, which is definitely not what you have intended!
And this is where macros have their advantage. Since a macro outputs a
list which Logo then runs within the calling procedure,
STOP would not exit
SOME.PROCEDURE, as you have intended (please note that the simple
example above outputs the list that you fed into
MYLOOP, so the
STOP command is part of that list).
Let us look at a real-world example. Here is your own version of the REPEAT command:
TO MY.REPEAT :num :instructions IF :num = 0 [STOP] RUN :instructions MY.REPEAT :num - 1 :instructions END
And here is the procedure that uses this command:
TO GUESS PRINT [GUESS THE SECRET WORD. YOU HAVE 3 TRIES] MY.REPEAT 3 [IF READWORD = "SECRET [PRINT "CONGRATS! STOP]] PR "SORRY! END
IF you ran this procedure, you would always have 3 tries even if you
guessed the secret word, because the STOP
command would end
It would work if you rewrote
MY.REPEAT to be a macro:
.MACRO MY.REPEAT :NUM :INSTRUCTIONS IF :NUM <= 0 OUTPUT  OUTPUT SENTENCE :INSTRUCTIONS(LIST "MY.REPEAT :NUM - 1 :INSTRUCTIONS) END
MACROEXPAND [MY.REPEAT 3 [IF READWORD = "SECRET [PRINT "CONGRATS! STOP]]] Result: [IF READWORD = "SECRET [PRINT "CONGRATS! STOP] MY.REPEAT 2 [IF READWORD = "SECRET [PRINT "CONGRATS! STOP]]]
After applying some color to the result to improve its readability:
[ IF READWORD = “SECRET [PRINT “CONGRATS! STOP] MY.REPEAT 2 [IF
READWORD = “SECRET [PRINT “CONGRATS! STOP]] ]
You can see, the first part is the
:INSTRUCTIONS runlist for
MY.REPEAT followed by a new call of
:NUM set to one
less along with the
:INSTRUCTIONS runlist. And, this entire runlist is
evaluated within the context of the
GUESS procedure so that the
STOP command would exit
GUESS as intended.
Keep in mind that any macro output is run immediately in the context of whatever called the macro. What an example of dynamic code generation!
Logo programs can run in the background, meaning that the Listener is free to do other things while a program is executing. You can, for example, send a turtle across the screen, while running another Logo program at the same time.
Consider this procedure:
TO CREEP ASK 1 [ SETPC 2 ST PD SETSPEED 0.1 SETH HEADING + (RANDOM 60) - 30 FORWARD RANDOM 20 ] CREEP END
CREEP lets the turtle creep across the screen forever, until you click
the Stop button.
Now if you want the turtle to creep without blocking the Listener, you use the LAUNCH command to launch the procedure:
Logo starts running the procedure, but the Listener prompt returns at once, and you can enter more commands while the turtle creeps across the screen.
Creepy, huh? (No pun intended)
You can, of course, also click the Stop button, or you could use the HALT command without inputs to stop all background programs:
> LAUNCH “CREEP Result: 84 > HALT 84 >
Be very careful with background programs! All Logo programs share the same environment. A background program should not change any global names so other Logo programs can continue running.
Changing global variables can have grave consequences. Imagine a Logo
program writing to
:STANDARD.OUTPUT to create a
file. At the same time, a Logo background program creates another file
and changes :STANDARD.OUTPUT to
write to that file, and all of a sudden, all Logo programs write to the
new file at the same time! Or just try to
TELL a different set of turtles while the
CREEP procedure runs in the background. All of a sudden, a
different set of turtles will start to creep.
By the way: Logo WHEN Something Changes for details.
Debugging background programs
Having multiple programs running at the same time can be difficult to debug. WHenever you enter the debugger, it displays the currently active program, which may not be your main program.
All programs (the main program as well as every background programs) run in their own Logo engine; every engine has its own ID, which is a positive integer. The main engine always has the Id #0. The debugger displays the currently active engine.
While debugging, you can use the global variable:LOGOENGINE to figure out which engine you are debugging (the debugger displays the engine number as well). You can switch between engines just by setting the variable to a different value:
MAKE "LOGOENGINE 5
If there is no engine 5, Logo throws an error. Use the LAUNCHED command to see a list of active engine numbers.
Finally, the BACKTRACE command displays the stack of active procedures for any engine, including the time when the engine was launched.