To allow gui specifications to be independent of the used gui-system, I designed a "meta-gui", ie a generic specification to which all amool implementations of a gui-system should comply. This allows you to design your gui without needing to think about what particular gui-system you're going to use.
You find four types of objects in AlMuist's user interfaces, which are:
Concerning the gui class hierarchy:
[I'll make this enumeration a bit more detailed later]
The different gadget types are quite limited for now.. I'll add more later... Here's a list of available gadgets (note that they are not defined in guiBlock itself, but its a naming convention to which all subclasses of guiBlock must comply):
Here is a little example of a gui strcture declared with that. It defines a login window, with two fields that each have a label (note that the password field does not hide its contents, as this API does not yet allow it...)
&=myGui &=super @( some gui-system here &) && @super &=name MyGui && &=theWindow &=super @'guiBlock.window && &=name win && &=vertical &=super @'guiBlock.vgroup && &=name vertical && &=login &=super @'guiBlock.hgroup && &=name lg && &=login: &=super @'guiBlock.label && &=name ll && &=text "login:" && && &=box &=super @'guiBlock.textField && &=name lb && && && &=password &=super @'guiBlock.hgroup && &=name pg && &=password: &=super @'guiBlock.label && &=name pl && &=text "password:" && && &=box &=super @'guiBlock.textField && &=name pb && && && && && && |
This is a partial view of the class hierarchy... | ||||
myGui | |||||
theWindow | |||||
vertical | |||||
login | password | ||||
login: | box | password: | box |
And this is what you get (with swing on windows)
@login : | @vertical | |||
@password : |
The names of the amool classes for you gadgets and guiBlocks are completely free. When you put a subclass of some gadget inside your gui-hierarchy, then the superclasses will propagate all needed variables, methods, initialization code in the correct place.
For details about the name of the fields (@name, @text, ...), you should have a look at the API reference
I already explained what a notification is in the introduction, but I'll tell it again.. ;-)
Some of the objects have the ability to hold some values. For instance a string gadget holds a string value, a checkbox holds a boolean value. Now you may want to synchronize the values of two different gadgets. For instance you have a program that displays a list of objects. For each object you can open a setting window. Now you have a field in the window that allows to modify the name of the object. To have the name displayed in the objectlist synchronized with the text field in the setting window, you will put a notification.
I implemented that with the concept of input and output ports.
An input port for an object means that it can be programmatically modified, and can
therefore be the target of a notification.
An output port means that the object can trigger a notification.
Now, ports can be of several types:
You can only connect an input port to an output port if they have the same type.
A little code for a start:
&=theGroup &=super @'guiBlock.vgroup && &=sliderOne &=super @'guiBlock.slider && &=name one && &+speakTo @parent.sliderTwo && && &=sliderTwo &=super @'guiBlock.slider && &=name two && &+speakTo @parent.sliderOne && && &&
When you want an object @A to be connected to an object @B, you will simply put a pointer to the object @B in a field that has a name like @speakTo. You can put as many definitions of this symbol as you want, one for each notification starting from @A.
The superclass will then be responsible for setting up the notification (the way it is doing it depends on the particular gui-system; for Swing, your gadgets will set the guiBlock class as a (...)Listener, and put code in the corresponding method, that reads the data from the gadget, and so on)
Circularities (A speaksTo B speaksTo A, in the above example) is handled that way: when a notification is started, a flag is raised, that forbids any other notification to occur on the source. This means that changing A will lock A and change B, which will lock B and change A, and stop here because A is locked.
This was for output ports. Now, each gadget class contains an Amool class named @copyFrom, for each port. When you call that class with some argument, it will generate the code that writes the supplied arguments in the port.
Everything above was valid inside a single guiblock (on in a gui that has only one guiblock).
When you want more than one guiBlock, then first build your guiBlock hierarchy (with a standard Amool class hierarchy), and then put your gadgets inside the guiBlock, in the same way as above.
There is one subtelty with guiBlock, which is that you might want to connect a (either by notification or attachment, ie where the gadget should be located) things accross guiBlocks.
As going between several guiBlocks might require some additional code to be generated, I decided to put a severe restriction on connections that are allowed between guiBlocks: each guiBlock has two special childs, named stepUp and stepDown. these children provide some kind of gateways between guiBlocks. When you do some connection, you can do it anywhere within the source guiBlock, or to the stepUp child of the parent guiBlock, or to the stepDown child of a child guiBlock.
Then, once you got to the guiBlock you wanted to reach, give as argument to the stepUp/Down class your actual target.
A little example to make things clear:
&=mainGui (...) &=theWindow (...) &=source (...) &+speakTo @'guiBlock.subGui.stepDown( @'guiBlock.subGui.win.group.target &) && && (...) && &=subGui (...) &=target (...) && (...) && &&
I explain: We wanted the source class to speak to the target class, which happens to be in another guiBlock. As we can't refer to the target class directely, we need to refer to the stepDown child of the guiBlock (inherited from the guiblock class). Now, as argument, we can give anything that is accessible from the guiBlock. It might be another stepUp/Down class, or, like in this case, a gadget inside the guiBlock. Note that the argument is still relative to the source target, because arguments are evaluated relative to the caller, in Amool.
When you put a notification to a sub-guiBlock (like in the above example), the notification will be transfered to all existing instances of the guiBlock.
One last thing I haven't yet told you about is how you can have a guiBlock attach stuff to another guiBlock. For instance, you might want to have a menu (they do not yet exist in AlMuist's but it won't last long!) that allows to display the windows of the opened projects. The menu will obviously be part of the main user interface (as you don't want as many menus as there are projects), but its items will be part of the project guiBlock. So this means that the menu items, part of the project guiBlock, must be attached to the menu, that is part of the main guiBlock.
Each gadget defines a child named @attachedTo, that specifies the
container to which the gadget should be attached. Its default value is:
&=attachedTo @'gadget.parent &&
(which means that a gadget should be attached to the gadget defined by its
Amool parent class)
It's now easy to put a pointer to a @stepUp class, and attach the pointer wherever you want it to go... (note that you can't point to a stepDown class, as this would mean that you want to attach your gadget to all existing subBlocks, which would imply that your gadget would be attached at several places at the same time! (If it is what you want to do, then directely define it in the sub-guiBlock itself...)