Amulet is a user interface development environment that makes it easier to create highly interactive, direct manipulation user interfaces in C++ for Microsoft Windows, Unix X/11, or the Macintosh. This tutorial introduces the reader to the basic concepts of Amulet. After reading this tutorial and trying the examples with a C++ compiler, the reader will have a basic understanding of: the prototype-instance system of objects in Amulet; how to create windows and display graphical objects inside them; how to constrain the positions of objects to each other using formulas; how to use interactors to define behaviors on objects (such as selecting objects with the mouse); how to collect objects together into groups; how to use the Amulet widgets; and how to use some of the debugging tools in Amulet.
In this tutorial, you will be introduced to the most commonly used parts of Amulet: the ORE Object system, Opal graphical object, Interactors, Command Objects, and Widgets. It includes code examples that can you can type in and compile yourself, along with discussions of Amulet programming techniques.
2.1.2 Copy the Tutorial Starter Program
Throughout this tutorial, you will be typing and compiling code to observe its behavior. A starter program is installed with Amulet in the directory amulet/samples/tutorial/
in Unix, in amulet:samples\tutorial\
in Windows, and amulet:samples:tutorial
in Macintosh. By following the instructions in this tutorial, you will iteratively edit and recompile this program in your local area while learning about Amulet.tutorial
directory and its contents into your local filespace; on the Macintosh, duplicate the tutorial
folder within the amulet:samples
folder. You will edit this copy of the tutorial files while going through the tutorial, and not the original copy. Your copy of the directory should contain the files Makefile
and tutorial.cc
, if you're on a Unix platform, the files tutorial.cpp
, tutorial.mak
and tutorial.dsp
if you're on the PC, or the files tutorial.cc
, tutorial68K.proj
, and tutorialpPC.proj
if you're on a Macintosh. make
on the command line. On the PC, open workspace tutorial.mak
(MSVC++ 4.x) or tutorial.dsp
(MSVC++ 5.0) with Microsoft Developer Studio and configure the project as described in Section 1.4.3.4.1. On the Macintosh, open tutorial***.proj
with CodeWarrior (where ***
is either 68K
or PPC
). Next, build the project.
You should now be able to execute the
tutorial
program, which creates an empty window in the upper-left corner of the screen. Exit the program by placing the mouse in the Amulet window (and clicking in the window to make it active, if necessary) and typing the Amulet escape sequence, . The meta key is the diamond key on Sun keyboards, the EXTEND-CHAR
key on HPs, the Command (Apple) key on Macintosh keyboards, and the ALT
key on PC keyboards. On other Unix keyboards, it is generally whatever is used for ``meta'' by Emacs and the window manager.tutorial
executable, there may be a problem with the way that Amulet was installed at your site. Consult Section 1.4 for detailed instructions about installing Amulet.
2.2 The Prototype-Instance System
Amulet provides a prototype-instance object system built on top of the C++ class-object hierarchy. C++ classes are defined at compile time, and the amount and type of data stored in a C++ object cannot change at run time. The C++ class is an abstract description of how to make an object, but contains no data by itself. In Amulet, every object is ``real,'' and there is no underlying abstract class that describes an Amulet object at compile time. The prototype for an object in Amulet is another object, not an abstract class as in C++. All Amulet objects have the C++ type Am_Object
.
2.2.1 Objects and Slots
The properties of an Amulet object are stored in its slots, which are similar to a class's member variables in C++. A rectangle's slots contain values for its position (left, top), size (width, height), line-style, filling-style, and so on. In the following code, a rectangle is created and some of its slots are set with new values (it is not necessary to type in this code, it is just for discussion):Am_Object my_rect = Am_Rectangle.Create (
``my_rect''
)
.Set (Am_LEFT, 20)
.Set (Am_TOP, 20)
.Set (Am_LINE_STYLE, Am_Black)
.Set (Am_FILL_STYLE, Am_Red);
int my_left = my_rect.Get (Am_LEFT); // my_left has value 20
The Set
operation sets the values of the objects' slots, and the corresponding Get
operation retrieves them. Set
takes a slot key, such as Am_LEFT
, and a new value to store in the slot. Get
takes a slot key and returns the value stored in the slot. A slot key is an index into the set of slots in an object.Am_
'' prefix, declared in the header file standard_slots.h
. Slot keys that you create for your own use need to be explicitly declared with special Amulet functions, as in:Am_Slot_Key MY_SLOT = Am_Register_Slot_Name ("MY_SLOT");
The Add
operation is used to add new slots to an object that do not appear in the object's prototype. Like Set
, Add
takes a slot key and a value to store in the slot.my_rect.Add (MY_SLOT, 43);
There are many examples of setting and retrieving slot values throughout this tutorial.2.2.2 Dynamic Typing
Another difference between Amulet objects and C++ classes involves the type restrictions of the values being stored. In C++ classes, you are restricted to declaring member variables of a specific type, and you can only store data of that type in the variables. In contrast, Amulet uses dynamic typing, where the type of a slot is determined by the value currently stored in it. Any slot can hold any type of data, and a slot's type can change whenever a new value is set into the slot.Set
and Get
operators. There are versions of Set
and Get
that handle most simple C++ types including int
, float
, double
, char
, and bool
. They also handle more general types like strings, Amulet objects, functions, and void*
. Other types are encapsulated in a type called Am_Wrapper
, which allows C++ data structures to be stored in slots.int i = obj.Get(Am_LEFT);
Amulet looks in the object obj
and tries to find its slot Am_LEFT
. If the slot exists, and its value is an integer, it is assigned to i
. If the value there is not an integer, however, this causes an error. If you do not know what type a slot contains, you can get the slot into a generic Am_Value
type, which is the C++ class that Amulet uses to store slot values, or ask the slot what type it contains using obj.Get_Slot_Type
(see Section 3.4).
2.2.3 Inheritance
When instances of an Amulet object are created, an inheritance link is established between the prototype and the instance. Inheritance allows instances to use slots in their prototypes without setting those slots in the instances themselves. For example, if we set the fill style of a rectangle to be gray, and then we create an instance of that rectangle, then the instance will also have a gray fill style.Am_Graphical_Object
, that all graphical objects are instances of. Figure 1 shows some of the objects in Amulet and how they fit into the inheritance hierarchy. Objects shown in bold are used by all Amulet programmers, while the others are internal, and intended to be accessed only by advanced users. The Am_Map
and Am_Group
objects are both special types of aggregates, and they inherit most of their properties from the Am_Aggregate
prototype object. Some of their slots are inherited from Am_Graphical_Object
through Am_Aggregate
. The Widgets (the Amulet gadgets) are not pictured in this hierarchy, but most of them are instances of the Am_Aggregate
object
tutorial.cc
(Unix and Mac) or tutorial.cpp
(PC) in your local area. Open the file. You should see:.
#include <amulet.h>
If you have not already compiled this file, do so now. In UNIX, invoke
main (void)
{
Am_Initialize ();
Am_Object my_win = Am_Window.Create (``my_win'')
.Set (Am_LEFT, 20)
.Set (Am_TOP, 50);
Am_Screen.Add_Part (my_win);
/* ************************************************************ */
/* During the Tutorial, do not add or edit text below this line */
/* ************************************************************ */
Am_Main_Event_Loop ();
Am_Cleanup ();
}
make
in your tutorial/
directory to generate the tutorial
binary. On the PC and Macintosh, select ``Build'' from the ``Project'' menu. Execute tutorial
to create a window in the upper-left corner of the screen.tutorial
program creates an object called my_win
, which is an instance of Am_Window
. A value of 20 was installed in its Am_LEFT
slot and 50 in its Am_TOP
slot. These values are reflected in the position of the window on the screen.Inspector
to examine the slots and values of the window. Move the mouse over the window and press the F1
key. The Amulet Inspector
will pop up a window that displays the slots and values of my_win
, as shown in Figure 2. You will see many slots displayed, some of which are internal and not intended for external use. The slots with ``~'' in their names are internal slots. You can hide these internal slots by selecting the menu option View: Hide Internal Slots
. Some of the other slots are ``advanced'' and should not be needed by most programmers. Chapter 11, Summary of Exported Objects and Slots, lists the primary exported slots of the main Amulet objects.
By default, object slots are sorted alphabetically by name in the Inspector. To turn this option off, choose
View: Stop Sorting By Name
from the Inspector's menu.Am_LEFT
and Am_TOP
slots of my_win
shown in the Inspector
contain the expected values. The Am_WIDTH
and Am_HEIGHT
slots contain values that were not set by the tutorial
program. These values were inherited from the prototype. They were defined in the Am_Window
object when it was created, and now my_win
inherits those values from Am_Window
as if you had set those slots directly into my_win
. The Inspector
shows they are inherited by displaying the slots in blue. Slots with local values are displayed in black. You can use the menu command View: Hide Inherited Slots
to hide all of my_win
's inherited slots.. You could also choose the Inspector's
Objects: Quit Application
menu item.my_win
using Set
, the function that sets the values of slots. Edit the source code, and add the following lines immediately after the definition of my_win
:my_win.Set (Am_WIDTH, 200)
Notice that we can cascade the calls to
.Set (Am_HEIGHT, 400);
Set
without placing semi-colons at the end of each line. Set
makes this possible by returning the object that is being changed, so that the return value of Set
can be used without intermediate binding. After compiling and executing the file, and hitting F1
to invoke the Inspector
, you can see that we have successfully overridden the Am_WIDTH
and Am_HEIGHT
slots in my_win
with our local values. If you move and resize the window from the window manager, the values in the inspector should change to reflect these changes as well.Set
is Get
, which retrieves values from slots. The Inspector
uses Get
on my_win
to obtain the values to print in the Inspector
window. We can use Get
directly by typing the following code into the source code, after the definition of my_win
:int left = my_win.Get (Am_LEFT);
int width = my_win.Get (Am_WIDTH);
cout << "left == " << left << endl;
cout << "width == " << width << endl;
Delete the code we used to set the left, top, width, and height of the window, and see what values are printed by the cout
statement when the program is run. You can see that Am_LEFT
defaults to 0, and Am_WIDTH
defaults to 100 if you don't set the slots explicitly.Get
to retrieve the value of a slot, the object first checks to see if it has a local value for that slot. If there is no value for the slot in the object, then the object looks to its prototype to see if it has a value for the slot. This search continues until either a value for the slot is found or the root object is reached. When no inherited or local value for the slot is found, an error is raised. This might occur if you are asking for a slot from the wrong object, or if you forget to add the slot to the object.
2.2.4 Instances
All of the objects displayed in a window are instances of other objects. In tutorial
, my_win
is an instance of Am_Window
. Let's create several instances of graphical objects and add them to my_win
. First, make sure that your window is large enough, at least 200x200. Change your definition of my_win
to look something like this:Am_Object my_win = Am_Window.Create ("my_win")
Now we can create several graphical objects and add them to the window. Type the following code into the
.Set (Am_LEFT, 20)
.Set (Am_TOP, 50)
.Set (Am_WIDTH, 200)
.Set (Am_HEIGHT, 200);
Am_Screen.Add_Part (my_win); // Puts my_win
on the screen
tutorial
program after the definition of my_win
, then recompile and execute tutorial
. Am_Object my_arc = Am_Arc.Create ("my_arc")
The circle, text, and rectangle will be displayed in the window. You can position the mouse over any of the objects and hit
.Set (Am_LEFT, 10)
.Set (Am_TOP, 10);
Am_Object my_text = Am_Text.Create ("my_text")
.Set (Am_LEFT, 80)
.Set (Am_TOP, 30)
.Set (Am_TEXT, "This is my_text");
Am_Object my_rect = Am_Rectangle.Create ("my_rect")
.Set (Am_LEFT, 10)
.Set (Am_TOP, 100)
.Set (Am_WIDTH, 180)
.Set (Am_HEIGHT, 80)
.Set (Am_FILL_STYLE, Am_Red);
my_win.Add_Part (my_arc)
.Add_Part (my_text)
.Add_Part (my_rect);
F1
to display the slots of the object in the Inspector
. If you hit F1
while the mouse is over the background of the window, you will raise the Inspector
for the window itself. While inspecting my_win
, you can see at the bottom of the Inspector
display that the new objects have been added as parts of the window.
2.2.5 Prototypes
When programming in Amulet, inheritance among objects can eliminate a lot of duplicated code. If we want to create several objects that look similar, we could create each of them from scratch and copy all the values that we need into each object. However, inheritance allows us to define these objects more efficiently, by creating several similar objects as instances of a single prototype.tutorial
, make sure it will create a window of size at least 200x
200.
Let's consider the design for the rectangles. The first thing to notice is that all of the rectangles have the same width and height. We will create a prototype rectangle which has a width of 40 and a height of 20, and then we will create three instances of that rectangle. To create the prototype rectangle, type the following.
Am_Object proto_rect = Am_Rectangle.Create ("proto_rect")
This rectangle will not appear anywhere, because it will not be added to the window. We will create three instances of this prototype rectangle, which will be displayed. Since the prototype has the correct values for the width and height, we only need to specify the left, top, and fill styles of our instances.
.Set (Am_WIDTH, 40)
.Set (Am_HEIGHT, 20);
Am_Object r1 = proto_rect.Create ("r1")
When you recompile and execute, you can see that the instances
.Set (Am_LEFT, 20)
.Set (Am_TOP, 20)
.Set (Am_FILL_STYLE, Am_White);
Am_Object r2 = proto_rect.Create ("r2")
.Set (Am_LEFT, 40)
.Set (Am_TOP, 30)
.Set (Am_FILL_STYLE, Am_Opaque_Gray_Stipple);
Am_Object r3 = proto_rect.Create ("r3")
.Set (Am_LEFT, 60)
.Set (Am_TOP, 40)
.Set (Am_FILL_STYLE, Am_Black);
my_win.Add_Part(r1)
.Add_Part(r2)
.Add_Part(r3);
r1
, r2
, and r3
have inherited their width and height from proto_rect
. You may wish to use the Inspector
to verify this. With these three rectangles still in the window, we are ready to look at another important use of inheritance by changing values in the prototype.proto_rect.
You can do this by inspecting one of the three rectangles in the window, and using the right mouse button to click on <proto_rect>
on the ``Instance of <proto_rect>
'' line. Other ways to inspect this object include double left clicking on the object name, and choosing the Objects: Inspect Object
menu item, or choosing Objects: Inspect Object Named...
and typing ``proto_rect
'' into the dialog box.proto_rect
, the contents of the Inspector
window will be replaced by the slots and values of proto_rect
. You can bring up the new object in its own inspector window by holding down the shift key while clicking the right mouse button over proto_rect,
or by double clicking on the object name and choosing Objects: Inspect In New Window
.^f
'' means control-f
).
^f
or rightarrow
forward one character
^b
or leftarrow
backward one character
^a
go to beginning of line
^e
go to end of line
^h
, DELETE
, BACKSPACE d
elete previous character
^w
, ^DELETE
, ^BACKSPACE
delete previous word
^d
delete next character
^u
delete entire string
^k
kill (or delete) rest of line
^y
, INSERT
insert the contents of the cut buffer into the string at the^c c
opy the current string into the cut buffer
^g
aborts editing and returns the string to the way it wasleftdown
(inside the string)
move the cursor to the specified point
Inspector
window, change the width of proto_rect
to 30 and change its height to 40. The result should look like the rectangles in Figure 4. Just by changing the values in the prototype rectangle, we were able to change the appearance of all its instances. This is because the three instances inherit their width and height from the prototype, even when the prototype changes.
r3
(the black rectangle) up in the Inspector
, and change the value of its width slot to 100.
The rectangle
r3
now has its own value for its Am_WIDTH
slot, and no longer inherits it from proto_rect
. If you change the width of the prototype again, the width of r3
will not be affected. However, the width of r1
and r2
will change with the prototype, because they still inherit the values for their Am_WIDTH
slots. This shows how inheritance can be used flexibly to make specific exceptions to the prototype object.2.2.6 Default Values
Because of inheritance, all instances of Amulet prototype objects have reasonable default values when they are created. As we saw in Section 2.2.4, the Am_Window
object has its own Am_WIDTH
value. If an instance of it is created without an explicitly defined width, the width of the instance will be inherited from the prototype. This inherited value can be considered a default value for slots in an instance. Section 11 contains a complete list of Amulet objects and the default values of their slots.
2.2.7 Destroying Objects
After objects have fulfilled their purpose, it is appropriate to destroy them. All objects occupy space in memory, and continue to do so until explicitly destroyed (or the program terminates). A Destroy()
method is defined on all objects, so at any point in a program you can do obj.Destroy()
to destroy obj.2.2.8 Unnamed Objects
Sometimes you will want to create objects that do not have a particular name. Or, you might not care what the name of an object is, so you'd rather not bother thinking of a name. For example, you may want to write a function that returns a rectangle, but it will be called repeatedly and should not return multiple objects with the same name. In this case, you should allow Amulet to generate a unique name for you.Create
, we invoke it with no parameters. Am_Object obj;
When no name string is supplied to
for (int i=0; i<10; i++) {
obj = Am_Rectangle.Create()
.Set (Am_LEFT, i*10)
.Set (Am_TOP, i*10);
my_win.Add_Part (obj);
}
Create
, Amulet generates a unique name for the object being created. In this case, something like <Am_Rectangle_5>.
This name has a unique number as a suffix that prevents it from being confused with other rectangles in Amulet.2.3 Graphical Objects
2.3.1 Lines, Rectangles, and Circles
The Opal module provides different graphical shapes including circles, rectangles, roundtangles, lines, text, bitmaps, and polygons. Each graphical object has special slots that determine its appearance, which are fully documented in chapter 4, Opal Graphics System and summarized in chapter 11, Summary of Exported Objects and Slots. Examples of creating instances of graphical objects appear throughout this tutorial.
2.3.2 Groups
In order to put a large number of objects into a window, we might create all of the objects and then add them, one at a time, to the window. However, this is usually not how we organize the objects conceptually. If we were to create a sophisticated interface with tool palettes, icons with labels, and feedback objects, we would not want to add each line and rectangle directly to the window. Instead, we would think of creating each palette from its composite rectangles, then creating the labeled icons, and then adding each assembled group to the window.Am_Group
object. Any graphical object can be part of a group - lines, circles, rectangles, widgets, and even other groups (note: Am_Window
is not considered a graphical object, even though it does appear on the screen). Usually all the parts of a group are related in some way, like all the selectable icons in a tool palette.Add_Part()
and Remove_Part()
methods are used to add and remove parts. You can optionally provide a slot key (a slot name, such as MY_PART
) in an Add_Part()
call. If a slot key is provided, then in addition to becoming a part of the group, the new part will be stored in that slot of the group. The Set_Part()
method is used to change the part that is stored in a named slot. Parts with slot keys are always instantiated when instances of an existing group are created, and parts without a key are instantiated unless you specify otherwise. It is often convenient to provide slot keys for parts so that functions and formulas can easily access these objects in their groups.Remove_Slot()
and then added back using Add
or Add_Part
, whichever is appropriate. Top_Trill_Box
and Bottom_Trill_Box
are both groups, each with two parts. And, of course, the top-level Scroll_Bar
node is a group.
This group hierarchy should not be confused with the inheritance hierarchy discussed earlier. Parts of a group do not inherit values from their owners. Relationships among groups and their parts must be explicitly defined using constraints, a concept which will be discussed shortly in this tutorial.
2.3.3 Am_Group
Am_Group
and Am_Map
are used to form groups of other objects. They both define their own coordinate system, so that their parts are offset from the origin of the group.// Declared at the top-level, outside of
The main()
// You may install new slots in any object, but if they are not pre-defined Amulet slots,
// starting with the ``Am_'' prefix, then you must define them separately at the top-level.
// See Section 2.2.1
Am_Slot_Key ARC_PART = Am_Register_Slot_Name ("ARC_PART");
Am_Slot_Key RECT_PART = Am_Register_Slot_Name ("RECT_PART");
...
// Defined inside of main()
Am_Object my_group = Am_Group.Create ("my_group")
.Set (Am_LEFT, 20)
.Set (Am_TOP, 20)
.Set (Am_WIDTH, 100)
.Set (Am_HEIGHT, 100)
.Add_Part(ARC_PART, Am_Arc.Create ("my_circle")
.Set (Am_WIDTH, 100)
.Set (Am_HEIGHT, 100))
.Add_Part(RECT_PART, Am_Rectangle.Create ("my_rect")
.Set (Am_WIDTH, 100)
.Set (Am_HEIGHT, 100)
.Set (Am_FILL_STYLE, Am_No_Style));
// Instances of my_group
Am_Object my_group2 = my_group.Create ("my_group2")
.Set (Am_LEFT, 150);
Am_Object my_group3 = my_group.Create ("my_group3")
.Set (Am_TOP, 150);
// Don't forget to add the graphical objects to the window!
my_win.Add_Part (my_group)
.Add_Part (my_group2)
.Add_Part (my_group3);
Add_Part()
method works like Add.
It takes an optional slot key, and an object to install in the group. In addition to making the object an official part of the group, it is installed in the given slot in the group, if a slot key is supplied. The objects my_circle
and my_rect
are stored in slots ARC_PART
and RECT_PART
of my_group
. The slots ARC_PART
and RECT_PART
are pointer slots because they point to other objects. These slots provide immediate access to these objects through my_group
, which is useful when defining constraints among the objects. Once installed, the parts can be retrieved by name from the group with the methods Get()
and Get_Object()
.my_group
is created, its parts are duplicated in the new group. Groups my_group2
and my_group3
have the same structure as my_group
, but at different positions. You can explicitly specify that a part should not be duplicated in instances of its owner by providing a second boolean parameter to Add_Part
without a slot key. Object.Add_Part(my_part,
false)
will add my_part
as a part to Object
, but my_part
will not be instantiated as a part of instances of Object
.2.3.4 Am_Map
A map is a kind of group that has many similar parts, all generated from a single prototype. In an Am_Map
, a single object is defined to be an item-prototype, and instances of this object are generated according to a set of items. See chapter 4, Opal Graphics System, for details and examples of maps.
2.3.5 Windows
Any object must be added to a window in order for it to be shown on the screen. Or, the object must be added to a group that, in turn, has been added to a window. All objects in a window are continually redrawn as necessary while the Am_Main_Event_Loop()
is running (see Section 2.5.6).
As shown in previous examples, objects are added to windows using the
Add_Part()
method. Subwindows can also be attached to windows using Add_Part()
, using exactly the same syntax for adding groups or other graphical objects.2.4 Constraints
In the course of putting objects in a window, it is often desirable to define relationships among the objects. You might want the tops of several objects to be aligned, or you might want a set of circles to have the same center, or you may want an object to change color if it is selected. Constraints are used in Amulet to define these relationships among objects.2.4.1 Formulas
A formula is an explicit definition of how to calculate the value for a slot. If we want to constrain the top of one object to be the same as another, then we define a formula, and put it in the Am_TOP
slot of the dependent object. With constraints, the value of one slot always depends on the value of one or more other slots, and we say the formula in that slot has dependencies on the other slots.
2.4.2 Declaring and Defining Formulas
There are several macros that are used to define formulas. These macros expand to conventional function definitions, but with special context information that Amulet uses to keep track of the constraint's dependencies. The particular macro you should use to define your formula depends on the type of the value to be returned from the formula.
Am_Define_Formula (return_type, formula_name)
-- General purpose: returns specified return_type
Am_Define_No_Self_Formula (return_type, function_name)
-- General purpose: returns specified return_type
. Used when the formula does not reference the special self
variable, so compiler warnings are avoided.
Am_Define_Value_List_Formula (formula_name)
-- Return type is Am_Value_List
Am_Define_Object_Formula (formula_name)
-- Return type is Am_Object
Am_Define_Style_Formula (formula_name)
-- Return type is Am_Style
Am_Define_Font_Formula (formula_name)
-- Return type is Am_Font
Am_Define_Point_List_Formula (formula_name)
-- Return type is Am_Point_List
Am_Define_Image_Formula (formula_name)
-- Return type is Am_Image_Array
Am_Define_Cursor_Formula (formula_name)
-- Return type is Am_Cursor
Am_Formula
. For example:
// inside my_file.h:
extern Am_Formula my_formula; // my_formula is defined in my_file.cc using Am_Define_Formula()
The constraints in the following examples will reference global values, and it is essential that the object variables and formulas be defined at the top-level of the program, outside of
main()
. Create the window and the first box with the following code.// Defined at the top-level, outside of main()
We are now ready to create the other objects that are aligned with
Am_Object my_win, white_rect, gray_rect, black_arc;
...
// Defined inside main()
// Create the window and display it on the screen
my_win = Am_Window.Create ("my_win")
.Set (Am_LEFT, 20)
.Set (Am_TOP, 50)
.Set (Am_WIDTH, 260)
.Set (Am_HEIGHT, 100);
Am_Screen.Add_Part (my_win);
// Create the white rectangle
white_rect = Am_Rectangle.Create ("white_rect")
.Set (Am_LEFT, 20)
.Set (Am_TOP, 30)
.Set (Am_WIDTH, 60)
.Set (Am_HEIGHT, 40)
.Set (Am_FILL_STYLE, Am_White);
// Add the rectangle to the window
my_win.Add_Part (white_rect);
white_rect
. We could simply create another rectangle and a circle that each have their top at 30, but this would lead to extra work if we ever wanted to change the top of all the objects, since each object's Am_TOP
slot would have to be changed individually. If we instead define a constraint that depends on the top of white_rect
, then whenever the top of white_rect
changes, the top of the other objects will automatically change, too. white_rect
as follows:// Define this at the top-level, outside of main()
Without specifying an absolute position for the top of the gray rectangle, we have constrained it to always have the same top as the white rectangle. The formula in the
Am_Define_No_Self_Formula (int, top_of_white_rect) {
// The formula is named top_of_white_rect, and returns an int
return white_rect.Get (Am_TOP);
}
...
// Define this inside main()
, after white_rect
gray_rect = Am_Rectangle.Create ("gray_rect")
.Set (Am_LEFT, 110)
.Set (Am_TOP, top_of_white_rect)
.Set (Am_WIDTH, 60)
.Set (Am_HEIGHT, 40)
.Set (Am_FILL_STYLE, Am_Gray_Stipple);
my_win.Add_Part (gray_rect);
Am_TOP
slot of the gray rectangle was defined using the macro Am_Define_No_Self_Formula
. Like the other Am_Define_Formula
macros, the Am_Define_No_Self_Formula
macro helps to define a function to be used as a constraint. The formula is named top_of_white_rect
, and returns an int
.Inspector
on white_rect
by hitting F1
while the mouse is positioned over the white rectangle. Change the top of white_rect
and notice how the gray rectangle stays aligned with its top. This shows that the formula in gray_rect
is being re-evaluated whenever the value it depends on changes.gray_rect.Set(Am_TOP, Am_From_Object(white_rect, Am_TOP);
Warning: Note that the constraint references white_rect
as a global variable. It is important that the global variable white_rect
be set before the constraint is first evaluated, because otherwise a NULL object will get dereferenced. Since you have no control over when the constraint is evaluated, it is a good idea to make sure that you assign the object into the global variable before the constraint is set into the slot of the other object, as is the case in these examples.// Define this at the top-level, outside of main()
At this point, you may want to inspect the white rectangle again and change its top just to make sure the black circle follows the gray rectangle.
Am_Define_Formula (int, top_of_gray_rect) {
return gray_rect.Get (Am_TOP);
}
...
// Define this inside main(), after gray_rect
black_arc = Am_Arc.Create ("black_arc")
.Set (Am_LEFT, 200)
.Set (Am_TOP, top_of_gray_rect)
.Set (Am_WIDTH, 40)
.Set (Am_HEIGHT, 40)
.Set (Am_FILL_STYLE, Am_Black);
my_win.Add_Part (black_arc);
2.4.4 Values and constraints in slots
What happens if you set the Am_TOP
of the gray rectangle now? The default for most slots, including the Am_TOP
slot of Am_Rectangle
, is that the new value replaces any formula in the slot. Bring up the gray rectangle in the inspector. Notice that the inspector tells you there is a constraint in the rectangle's Am_TOP
slot. Change the Am_TOP
of the gray rectangle by editing the value in the inspector window. You should see the grey rectangle move in the application window. Also, the inspector should no longer show a constraint in the rectangle's Am_TOP
slot. The rectangle's position will not be recalculated by the constraint if white_rect
moves, because the formula that was in the slot has been destroyed and replaced with a constant value.
2.4.5 Constraints in Groups
As mentioned in Section 2.3.3, parts can be stored in pointer slots of their group, making it easier for the parts to reference each other. Additionally, the owner is set in each part as they are added to a group. In this section, we will examine how pointer slots and the owner slot can be used to communicate among parts of a group.
These examples also go over the difference between directly referencing objects, as was done above by using a global variable containing the object name, and referencing objects indirectly, by getting the objects out of slots of other objects. It is generally better to reference objects indirectly, as will be described next, because using global variables means that the constraints can only be used to refer to one object, whereas indirection allows you to re-use the constraint in multiple places referring to different objects. Another problem with global variables is that you must insure that the variable is set before the first time the constraint is evaluated.
// Declared at the top-level, outside of main()
Am_Slot_Key ARC_PART = Am_Register_Slot_Name ("ARC_PART");
Am_Slot_Key RECT_PART = Am_Register_Slot_Name ("RECT_PART");
//self is an Am_Object parameter to all formulas that holds the object the constraint is in.
// The Am_Define_Formula macro expands to define self and some other necessary variables.
Am_Define_Formula (int, owner_width) {
return self.Get_Owner().Get(Am_WIDTH);
}
Am_Define_Formula (int, owner_height) {
return self.Get_Owner().Get(Am_HEIGHT);
}
...
// Defined inside of main()
Am_Object my_group = Am_Group.Create ("my_group")
.Set (Am_LEFT, 20)
.Set (Am_TOP, 20)
.Set (Am_WIDTH, 100)
.Set (Am_HEIGHT, 100)
.Add_Part(ARC_PART, Am_Arc.Create ("my_circle")
.Set (Am_WIDTH, owner_width)
.Set (Am_HEIGHT,owner_height))
.Add_Part(RECT_PART, Am_Rectangle.Create ("my_rect")
.Set (Am_WIDTH, owner_width)
.Set (Am_HEIGHT, owner_height)
.Set (Am_FILL_STYLE, Am_No_Style));
my_win.Add_Part(my_group);
Both parts of my_group
get their position and dimensions from the top-level slots in my_group
. The reference to my_group
from the arc
is through the Get_Owner()
function, which links the part to its group. The special variable self
is used in the formulas to reference slots within the object that the formula is installed on. The arc's left and top are relative to the origin of my_group
, so as it inherits a position of (0,0) from the Am_Arc
prototype, it will appear at (20,20) in the window. Am_Define_Formula (int, arc_width) {
return self.Get_Owner().Get(ARC_PART).Get(Am_WIDTH);
}
Am_Define_Formula (int, arc_height) {
return self.Get_Owner().Get(ARC_PART).Get(Am_HEIGHT);
}
2.4.6 Common Formula Shortcuts
There are many constraints which are used very commonly, such as getting a slot value from the object's owner, or getting the value directly from another slot in the same object. There are some built in functions in Amulet to make these common constraints easier to use.
Am_Same_As (Am_Slot_Key key);
// this slot gets its value from slot key
in this object.
Am_From_Owner (Am_Slot_Key key);
// this slot gets its value from slot key
in this object's owner.
Am_From_Part (Am_Slot_Key part, Am_Slot_Key key);
// this slot gets its value from slot key
in the part of this object stored in slot part
.
Am_From_Sibling (Am_Slot_Key sibling, Am_Slot_Key key);
// this slot gets its value from slot key
in the object stored in owner's sibling
slot.
Am_From_Object (Am_Object object, Am_Slot_Key key)
// This slot gets its value from the specified slot (key) of the specified (constant) object.
Am_Define_Formula()
calls:
// Defined inside of main()
Am_Object my_group = Am_Group.Create (``my_group'')
.Set (Am_LEFT, 20)
.Set (Am_TOP, 20)
.Set (Am_WIDTH, 100)
.Set (Am_HEIGHT, 100)
.Add_Part(ARC_PART, Am_Arc.Create (``my_circle'')
.Set (Am_WIDTH, Am_From_Owner (Am_WIDTH))
.Set (Am_HEIGHT, Am_From_Owner (Am_HEIGHT)))
.Add_Part(RECT_PART, Am_Rectangle.Create (``my_rect'')
.Set (Am_WIDTH, Am_From_Sibling (ARC_PART, Am_WIDTH))
.Set (Am_HEIGHT, Am_From_Sibling (ARC_PART, Am_HEIGHT))
.Set (Am_FILL_STYLE, Am_No_Style));
Interactors are described in detail in chapter 5, Interactors and Command Objects for Handling Input, and a summary of interactors can be found in the object summary, Section 11.7. It is important to note that all of the widgets (Section 2.6 and chapter 7, Widgets) come with their interactors already attached. You do not need to create interactors for the widgets.
Interactors communicate with graphical objects by setting slots in the objects in response to mouse movements and keyboard keystrokes. Interactors generate side effects in the objects that they operate on. For example, the
Am_Move_Grow_Interactor
sets the left, top, width, and height slots of objects. The Am_Choice_Interactor
sets the Am_SELECTED
and Am_INTERIM_SELECTED
slots to indicate when an object is currently being operated on. You might define formulas that depend on these special slots, causing the appearance of the objects (i.e., the graphics of the interface) to change in response to the mouse. The examples in the following sections show how you can use interactors this way.
Figure 9 shows the general data flow when input events occur: the user hits a keyboard key or a mouse event, which is passed to the window manager. The Gem layer of Amulet converts it into a machine-independent form and passes it to the Interactors which finds the right interactor object to handle the event. Each interactor has an embedded command object that causes the appropriate action to take place. If this interactor is part of a widget, then the command object in the interactor calls the widget's command object. Eventually, some graphics will be modified in the Opal layer, which is automatically transformed into drawing calls at the Gem level, and then to the window manager.
In this section we will see some examples of how to change graphics in conjunction with interactors. Section 2.7.2 describes how to use an important debugging function for interactors called Am_Set_Inter_Trace()
. Although this tutorial only gives examples of using the Am_One_Shot_Interactor
and Am_Move_Grow_Interactor
, there are examples of interactors in the sample applications and test programs included with the Amulet files. See for example, samples/space.cc
in your Amulet source files. Instructions for compiling and running the samples are in the Overview chapter.
2.5.1 Kinds of Interactors
The design of the interactors is based on the observation that there are only a few kinds of behaviors that are typically used in graphical user interfaces. Below is a list of the available interactors.
Am_Choice_Interactor
- This is used to choose one or more of a set of objects. The user is allowed to move around over the objects (getting interim feedback) until the correct item is found, and then there will often be final feedback to show the final selection. The Am_Choice_Interactor
can be used for selecting among a set of buttons or menu items, or choosing among the objects dynamically created in a graphics editor.
Am_One_Shot_Interactor
- This is used whenever you want something to happen immediately, for example when a mouse button is pressed over an object, or when a particular keyboard key is hit. Like the Am_Choice_Interactor
, the Am_One_Shot_Interactor
can be used to select among a set of objects, but it will not provide interim feedback--the one where you initially press will be the final selection. The Am_One_Shot_Interactor
is also useful in situations where you are not selecting an object, such as when you want to get a single keyboard key.
Am_Move_Grow_Interactor
- This is useful in all cases where you want a graphical object to be moved or changed size with the mouse. It can be used for moving and growing objects in a graphics editor.
Am_New_Points_Interactor
- This interactor is used to enter new points, such as when creating new objects. For example, you might use this to allow the user to drag out a rubber-band rectangle for defining where a new rectangle should go.
Am_Text_Edit_Interactor
- This supports editing the text string of a text object. It supports a flexible key translation table mechanism so that the programmer can easily modify and add editing functions. The built-in mechanisms support basic text editing behaviors.
Am_Gesture_Interactor
- This interactor supports free-hand gestures, such as drawing an X over an object to delete it, or encircling a set of objects to be selected. An interactive gesture training program called Agate is provided to create new gestures for your program to use. See Section 5.3.5.6 for more information on Agate and the gesture interactor.
Add_Part()
.
// Defined at the top-level, outside of
main()
Am_Define_Style_Formula (compute_fill) {
// Sometimes you need to cast the value returned from Get,
// since a slot can contain any type of object.
The slot
if ((bool) self.Get (Am_SELECTED))
return Am_Black;
else
return Am_White;
}
...
// Defined inside main()
Am_Object changing_rect = Am_Rectangle.Create ("changing_rect")
.Set (Am_LEFT, 20)
.Set (Am_TOP, 30)
.Set (Am_WIDTH, 60)
.Set (Am_HEIGHT, 40)
.Add (Am_SELECTED, false) // Slot not present in prototype -- use Add not Set
.Set (Am_FILL_STYLE, compute_fill);
my_win.Add_Part (changing_rect);
Am_SELECTED
is not present in the Am_Rectangle
prototype, so it must be initialized using Add() rather than Set() or a run-time error will occur.compute_fill
formula, you can see that if the Am_SELECTED
slot in changing_rect
were set to true
, then its color would turn to black. You can test this by bringing up the Inspector
on changing_rect, and changing the value of the slot to 1. Setting the Am_SELECTED
slot is one of the side effects of the Am_One_Shot_Interactor
. The following code defines an interactor which will set the Am_SELECTED
slot of an object, and attaches it to changing_rect
. Am_Object color_inter = Am_One_Shot_Interactor.Create ("color_inter");
Now you can click on the rectangle repeatedly and it will change from white to black, and back again. From this observation, and knowing how we defined the
changing_rect.Add_Part (color_inter);
compute_fill
formula of changing_rect
, you can conclude that the Am_One_Shot_Interactor
is setting (and clearing) the Am_SELECTED
slot of the object. This is one of the functions of this type of interactor.2.5.3 The Am_Move_Grow_Interactor
From the previous example, you can see that it is easy to change the graphics in the window using the mouse. We are now going to define several more objects in the window and create an interactor to move and grow them. The following code creates a prototype circle and several instances of it. Am_Object moving_circle = Am_Arc.Create ("moving_circle")
.Set (Am_WIDTH, 40)
.Set (Am_HEIGHT, 40)
.Set (Am_FILL_STYLE, Am_No_Style);
Am_Object objs_group = Am_Group.Create ("objs_group")
.Set (Am_WIDTH, Am_Width_Of_Parts)
.Set (Am_HEIGHT, Am_Height_Of_Parts)
.Add_Part (moving_circle.Create())
.Add_Part (moving_circle.Create().Set (Am_LEFT, 50))
.Add_Part (moving_circle.Create().Set (Am_LEFT, 100));
my_win.Add_Part(objs_group);
The predefined constraints, Am_Width_Of_Parts
and Am_Height_Of_Parts
, compute the size of a group based on the size of its parts.Am_Move_Grow_Interactor
which will cause the moving circles to change position. The following interactor, when added to objs_group
, works on all the parts of that group.Am_Object objs_mover = Am_Move_Grow_Interactor.Create ("objs_mover");
By default, interactors try to figure out which graphical object they're supposed to manipulate. If the interactor is attached to a group-like object (
objs_group.Add_Part(objs_mover);
Am_Window
, Am_Screen
, Am_Group
or Am_Scrolling_Group
), it looks for a part of that object to act on. Otherwise, it tests whether the mouse is directly in the object the interactor is attached to. You can change this default when you want to specify exactly what objects the interactor should operate on, by setting the interactor's Am_START_WHERE_TEST
slot. Other methods for the Am_START_WHERE_TEST
are described in Section 5.3.3.2.1, or you can write your own start-where-test procedure to return the appropriate object.
Compile and run tutorial again. Now you can drag the circles around using the left mouse button. The interactor activates when you push the left button down inside any of the parts of
objs_group
. As long as you hold the button down, it moves the objects around by setting their left and top slots.objs_mover
interactor. To do this, first bring up the inspector window on any of the objects on the screen. Then choose the Objects: Inspect Object Named... option from the menu, type in objs_mover
, and hit return (or click Okay). Click in the value field of the objs_mover
's Am_GROWING
slot and change the value to 1
. Now dragging the circles will cause them to change size rather than move.2.5.4 A Feedback Object with the Am_Move_Grow_Interactor
Now let's add a feedback object to the window that will work with the moving circles. In this case, the feedback object will appear whenever we click on and try to drag a circle. The mouse will drag the feedback object, and then the real circle will move to the final position when the mouse is released.feedback_circle
object defined below will have its left, top, and visible slots set by the interactor. Given our moving_circle
prototype, the feedback object is easy to define:Am_Object feedback_circle = moving_circle.Create ("feedback_circle")
The
.Set (Am_LINE_STYLE, Am_Line_8)
.Set (Am_VISIBLE, false);
my_win.Add_Part (feedback_circle);
// The definition of the interactor, with feedback object
Am_Object objs_mover = Am_Move_Grow_Interactor.Create ("objs_mover")
.Set (Am_START_WHERE_TEST, Am_Inter_In_Part)
.Set (Am_GROWING, true) // Makes the circles grow instead of move
.Set (Am_FEEDBACK_OBJECT, feedback_circle);
objs_group.Add_Part (objs_mover);
// Don't forget to add feedback_circle and objs_mover to the right owners!
Am_VISIBLE
slot of feedback_circle
is set to false
, because we do not want it visible unless it is being used by objs_mover
. The interactor will set the Am_VISIBLE
slot to true
and false
when appropriate. Now when you move or grow the circles with the mouse, the feedback object will follow the mouse, instead of the real circle following it directly.2.5.5 Command Objects
All interactors and widgets have command objects associated with them stored as their Am_COMMAND
part. Command objects contain functions that determine what the interactor will do as it operates. For example, you can store a function in a command object that will be executed as the interactor runs in order to cause side-effects in your program. See Section 5.6 for more information on command objects inside interactors.
You can also store methods in a command object to support undo, help, and selective enabling of operations. There is a library of pre-defined command objects, so you can often use a command object from the library without writing any code. Section 7.4, Supplied Command Objects, describes the predefined command objects. See example1
and space
for sample code that uses command objects.
Most interactors do three different things. As they run, they directly modify the associated graphical objects or feedback objects (like setting the
Am_SELECTED
slot). When they're finished running, they set their Am_VALUE
slot and the Am_VALUE
slot of their attached command object, and finally they call the Am_DO_METHOD
of the attached command object.Am_VALUE
slot of the interactor or its command object, you can establish a constraint from your object to either of these slots. If you want to make the interactor do a certain action only after it's finished running, it's best to build a custom command object. This is similar to providing a callback for the interactor to call when it's finished running. Both of these methods of using the results of an interaction are described in Section 2.6.
2.5.6 The Am_Main_Event_Loop
In order for interactors to perceive input from the mouse and keyboard, the main event loop must be running. This loop constantly checks to see if there is an event, and processes it if there is one. The automatic redrawing of graphics also relies on the main-event-loop. Exposure events, which occur when one window is uncovered or exposed, cause Amulet to refresh the window by redrawing the objects in the exposed area. main().
Am_Main_Event_Loop()
should be called, followed by Am_Cleanup()
, which destroys the resources Amulet allocated. Your program will continue to run until Amulet perceives the escape sequence, which by default is META_SHIFT_F1
. Typically, your program will have some sort of Quit button. Its do method should call Am_Exit_Main_Event_Loop()
, which will cause the main event loop to terminate.2.6 Widgets
The Amulet Widgets are a set of ready made gadgets that can be added directly to a window or a group just like other graphical objects. You do not have to define separate interactors to operate the gadgets, they already have their own interactors. They have slots that can be set to customize their appearance and behavior. The Amulet Widgets are common interface building objects such as scroll bars, menus, buttons and editable text fields. Section 11.8 summarizes the widget objects, and chapter 7, Widgets, discusses them all in detail.
The Widgets are available in several versions, simulating the look-and-feel of the standard widgets available in the Motif, Windows, and Macintosh toolkits. These widgets work on all platforms. Examples that use widgets can be found in several Amulet demos, including samples/example/example1
and samples/space
. See Section 1.5 for information about the sample applications and test programs.
// Declared at the top-level, outside of
main()
Am_Object color_buttons, color_rect;
// Declared at the top-level, outside of main()
Am_Define_Style_Formula (color_from_panel) {
Am_String s = color_buttons.Get (Am_VALUE);
if ((const char*)s) {
if (strcmp(s, "Red") == 0) return Am_Red;
else if (strcmp(s, "Blue") == 0) return Am_Blue;
else if (strcmp(s, "Green") == 0) return Am_Green;
else if (strcmp(s, "Yellow") == 0) return Am_Yellow;
else if (strcmp(s, "Orange") == 0) return Am_Orange;
else return Am_White;
}
else return Am_White;
}...
// Defined inside main()
color_buttons = Am_Radio_Button_Panel.Create("color_buttons")
.Set (Am_LEFT, 10)
.Set (Am_TOP, 10)
.Set (Am_ITEMS, Am_Value_List () // An Am_Value_List supports an arbitrary list
.Add("Red") // of dynamically typed values
Now let's create the scroll bar to change the position of the rectangle. We could define a formula that depends on the value of the scroll bar. Instead, let's use the
.Add("Blue")
.Add("Green")
.Add("Yellow")
.Add("Orange"))
.Set (Am_FILL_STYLE, Am_Motif_Gray);
// Defined inside main()
color_rect = Am_Rectangle.Create("color_rect")
.Set(Am_LEFT, 100)
.Set(Am_TOP, 50)
.Set(Am_WIDTH, 50)
.Set(Am_HEIGHT, 50)
.Set(Am_FILL_STYLE, color_from_panel);
my_win.Add_Part (color_buttons)
.Add_Part (color_rect);
Am_DO_METHOD
of the scroll bar's command object to call a function each time the widget is operated.// Defined at the top-level, outside of
main()
Am_Object my_scrollbar;
// Defined at the top-level, outside of main()
Am_Define_Method(Am_Object_Method, void, my_scrollbar_do, (Am_Object cmd))
{
int value = cmd.Get(Am_VALUE);
color_rect.Set (Am_TOP, 20 + value);
}
...
// Defined inside main()
my_scrollbar = Am_Vertical_Scroll_Bar.Create ("my_scrollbar")
.Set (Am_LEFT, 250)
.Set (Am_TOP, 10)
.Set (Am_SMALL_INCREMENT, 5)
.Set (Am_LARGE_INCREMENT, 20)
.Set (Am_VALUE_1, 0)
.Set (Am_VALUE_2, 100);
my_scrollbar.Get_Object(Am_COMMAND).Set(Am_DO_METHOD, my_scrollbar_do);
my_win.Add_Part (my_scrollbar);
Am_Define_Method
is a macro that defines a method of an Amulet object. The first parameter is the type of the method being defined. Do methods are of type Am_Object_Method
, meaning they take one parameter, an Amulet object, and have return type void
. The second parameter to Am_Define_Method
is the return type of the method, and then comes the method's name. Last is the method's parameter list, with an extra set of parentheses. For more information about Amulet method type declaration and method definition, see Section 3.4.9.
Compile and run the tutorial. The radio buttons will control the color of the rectangle, and the scrollbar will control its position on the screen.
2.7 Debugging
2.7.1 The Inspector
The Inspector is an important tool for examining properties of objects. As long as you compile with the DEBUG switch set on in your makefile or project, you will get all of the inspector code. The inspector will be automatically initialized when you call Am_Initialize()
. While running your program, press the F1
key over an object to inspect it.F1
key, or hitting it does not seem to do anything, you can start the Inspector
from your program by calling the function Am_Inspect(obj)
with the object you want to inspect as its argument. See Chapter 10, Debugging and the Inspector for details. The method obj.Text_Inspect()
prints the object's slots and values to stdout
instead of popping up an interactive window, and is sometimes useful in a debugging environment such as gdb
.
By default, the
Inspector
shows all of an object's inherited and local slots, sorted by name, with the inherited slots shown in blue, and the local slots shown in black. You can hide inherited slots by choosing the menu item View: Hide Inherited Slots
. You can hide an object's internal slots (those you shouldn't modify) with the menu item View: Hide Internal Slots
.View: Hide Parts
command. You can show instances of the object being displayed by choosing the View: Show Instances
menu item.Inspector
window, clicking the right mouse button over a value that is an object will inspect that object in the same inspector window. To display an object in a new window, hold down the SHIFT
key while pressing the right mouse button over its name in the Inspector
window. You can specify the name of an object to inspect by using the Objects: Inspect Object Named...
option. When you are finished with the Inspector
, you can choose the Objects: Done
menu item to make the current Inspector
window disappear, or choose Objects: Done All
if you want all of the inspector windows to be destroyed.View:Manual Refresh
. To refresh the display in manual refresh mode, choose Objects: Refresh Display
.Objects: Flash Object
. This will cause the bounding box of the object to blink several times so you can see where it is. If you wouldn't be able to see the object flash (it's not attached to a window, it's invisible, etc.), Amulet tries to figure out why not, and prints a message to cerr
describing why it thinks the object could not flash.2.7.2 Tracing Interactors
The interactors and widgets provide a number of mechanisms to help programmers debug their interactions. The primary one is a tracing mechanism that supports printing to standard output (cout
) whenever an ``interesting'' interactor event happens. Amulet supplies many options for controlling when printout occurs, as described below (full details are in the Interactors chapter). You can either set these parameters in your code and recompile, or they can be dynamically changed as your application is running, using the Interactors
menu of the Inspector window.
Trace This Interactor
When an interactor object is selected by double clicking its name, this option will start tracing the selected interactor.
Trace Interactor Named...
This option brings up a dialog box prompting the user for the name of an interactor to start tracing.
Trace All Interactors
This starts tracing on all interactors in the application.
Trace Next Interactor To Run
If you don't know the name of the interactor you want to trace, this is often a useful choice. Tracing is turned on for the next interactor which starts running.
Trace Input Events
This prints out incoming input events, but not what happens as a result of the events. When you turn on any other tracing, Amulet automatically traces input events.
Trace Interactor Set Slots
This option prints a message whenever an interactor sets any slot of any object. It is useful for determining why an object's slot is being set during a particular interaction.
Trace Interactor Priorities
Changes to interactors' priority levels are printed.
Short Trace Interactors
This prints out only the names of the interactors which run, and is good for getting a general idea of what's going on in a program without all of the details.
samples/examples/example1.cc
, is a simple editor for creating objects. It demonstrates:
samples/examples/example2.cc
, uses dialog boxes and the Am_Text_Input_Widget
. It demonstrates:
Am_Tab_To_Next_Widget_Interactor
to allow the user to tab among the fields.