EX11

EX11 is an Erlang interface to X windows. With EX11 you can easily program complex GUIs. EX11 models all widgets as concurrent processes - this results in extrememly compact GUI programs which are very simple to program and understand.

Tutorial

To see how the widget library is called I'll start with a very simple example. I'm going to make what looks like a "window".

Creating a draggable window

A window can be simply made by composing two primitive widgets, a DragBlob and a Rectangle. The code do do this is:

DragBar   = swDragBox  :make(Win, XX, YY,    260, 10, 1,?blue),
Rectangle = swRectangle:make(Win, XX, YY+16, 260, 200,1,?white),
DragBar ! {onMove, fun(X, Y) ->
			     Rectangle ! raise,
			     Rectangle ! {setXY, X, Y+16}
		     end}

This code make something that looks pretty much like a regular window, only I have separated the border from the frame so that you can clearly see that there are two separate widgets.

The line of code:

DragBar ! Msg

Sends an asynchronous message to the DragBar. The message is:

{onMove, Fun}

This means "when the Dragbar is moved to the position X,Y evaluate the function Fun(X, Y)".

Fun is the function:

fun(X, Y) ->
     Rectangle ! raise,
     Rectangle ! {setXY, X, Y+16}
end

This sends two messages to Rectangle. The message raise pops the window to the top of the display stack, in case it was obscured. The message {setXY, X, Y} moves the window to the position X, Y.

That's all that's needed - three lines of code. Now when you drab the blue bar the frame will obediently follow all motions of the border bar.

By reducing the gap between the border and the frame the compound object will look and behave like a regular window.


The drag frame example needs a little extra code to get it started. We need to connect to the X server and create a top-level window:

start() -> spawn(fun win/0).

win() ->
    Display = xStart("localhost"),
    Win     = swTopLevel:make(Display, 400, 250, ?bg),
    ... code as before ...
    loop().

loop() ->
    receive
       Any ->
	    io:format("received:~p~n",[Any]),
	    loop()
    end.

All windows must be controlled by their own processes - thus we spawn off a process to control the window. The process goes into an infinite loop (don't worry about this for now) - If this process dies then any widgets, windows etc. created by the widget will be destroyed.

The complete code for this is in example19.erl

Creating a shell

We can make a shell by combining three primitive widgets. swScrollbar, swText and swEntry.

The code to do make this looks like this:

Win   = swTopLevel:make(Display, Width, Ht, ?bg),
Text  = swText:make(Win, 10, 10, Width-50, Ht-70, ?AntiqueWhite, 
                    {file, "shell.txt"}),	 
Scroll  = swScrollbar:make(Win, Width-30, 10, 20, Max=Ht - 72,0,
			   ?blue,?white),
Text  ! {set,100000},
Entry = swEntry:make(Win, 10, Ht-40, Width-20, "Enter stuff here > "),
Entry ! {onReturn,
           fun(Str) ->
	     Text  ! {addStr,Str},
	     Entry ! {set, ">"}
	   end}

Firstly we create an entry a text display widget and a scrollbar.

Then we send a Text ! {set,100000} to the text widget. {set, N} means skip N lines at the start of the text that is being displayed and show my the rest of the file. Setting a large number (like 100000) means skip 100000 lines and then display the rest of the file - Executing this has the effect of always displaying the "end" of the file.

I have enabled the onReturn event in the entry. When a carriage return is pressed in the entry, the code:

fun(Str) ->
    Text  ! {addStr,Str},
    Entry ! {set, ">"}
end.

Is evaluated, where Str is the content of the entry. An {addStr, Str} message to the Text widget appends Str to the end of the file. A Entry ! {set, ">"} message to the entry sets the text in the entry to >.

So far this code will track any data that is sent to the text widget. If the user adjusts the scrollbar to see "earlier" data in the file a {set, N} message is sent to the text widget.

If the latest additions to the text are "off screen" they will not be seen.

To achieved this effect we have to "join" the scrollbar and the text widget. This is done as follows:

join(Text, ScrollBar, Max) ->
    {MaxHt, MaxLines} = rpc(Text, size),
    A = (MaxLines -  MaxHt)/Max,
    ScrollBar ! {onMove,
		 fun(I) ->
			 if 
			     I >= Max ->
				 Text ! {set, 1000000};
			     true ->
				 Text ! {set, trunc(A*I)}
			 end
		 end}.

When the scrollbar is moved to position I, then Fun(I) is evaluated. I is an integer in the range 1 to Max. Where Max is the longest dimension of the scrollbar. This integer from 1 to max is used to calculate the offset in the text window. When I is >= Max we want to set the offset in the text widget to a very large value. MaxHt is the number of screen lines of the text widget and MaxLines is the total number of lines in the text widget.

This code is actually buggy since as we add new code to the text widget we should increase MaxLines by one every time we add a new line..

The complete code for this is in example10.erl.

The Standard Widgets

  • swButton - a 3-D button.
  • swDragBox - a draggable, makes a blob which can be dragged round the screen.
  • swEntry - a editable text entry.
  • swFlashButton - a "flash" button, not 3D used in pop up menues etc.
  • swLabel - text widget widget.
  • swProgressBar - progress bar.
  • swRectangle - a colored rectangle. Can be used as a container frame.
  • swScrollbar - a scrollbar.
  • swText - text wisplay widget.
  • swTopLevel - top level window.

    swButton

    A 3-D button.

    Creation: make(Parent, X, Y, Width, Ht, Color, Str)

    Private protocol:

    Implementation: swButton.erl

    swDragBox

    - a draggable, makes a blob which can be dragged round the screen.

    Creation: make(Parent, X, Y, Width, Height, Border, Color)

    Private protocol:

    Implementation: swDragBox.erl

    swEntry

    - a editable text entry.

    Creation: make(Parent, X, Y, Width, Str)

    Private protocol:

    Implementation: swEntry.erl

    swFlashButton

    - a "flash" button, not 3D used in pop up menues etc.

    Creation: make(Parent, X, Y, Width, Ht, Border, Color1, Color2, Str)

    Private protocol:

    Implementation: swFlashButton.erl

    swLabel

    - text widget widget.

    Creation: make(Parent, X, Y, Width, Ht, Border, Color, Str)

    Private protocol:

    Implementation: swLabel.erl

    swProgressBar

    - progress bar.

    Creation: Parent, X, Y, Width, Ht, Border, Color1, Color2)

    Private protocol:

    Implementation: swProgressBar.erl

    swRectangle

    - a colored rectangle. Can be used as a container frame.

    Creation: make(Parent, X, Y, Width, Ht, Border, Color)

    Private protocol:
    None - this is merely a container - it dows however, respond to the generic prototcol

    Implementation: swRectangle.erl

    swScrollbar

    - a scrollbar.

    Creation: make(Parent, X, Y, Width, Ht, Border, Color1, Color2)

    Private protocol:

    Implementation: swScrollbar.erl

    swText

    - text wisplay widget.

    Creation: make(Parent, X, Y, Width, Ht, Color, Data) where Data is {file, F} or {str, S}.

    Private protocol:

    Implementation: swText.erl

    swTopLevel

    - top level window.

    Creation: make:(Parent, Width, Ht, Color)

    Private protocol:

    Implementation: swTopLevel.erl

    The Generic protocol

    All widgets should implement the following generic protocol. [2]

    Using the widget set (sw = "simple widgets") is very easy. Writing your own widgets is easy - modifying the X-divers is less easy.

    In the future I intend to support the XLib drivers and the generic widget framework.

    I do not intend to support the individual widgets.

    Volunteers

    Volunteers are needed to write:

    Design

    !! is an infix RPC operator. In the code this is written sw:rpc(Process, X) hopefully this operator will find itself in standard Erlang before too long :-)

    Screenshots

    Master menu:

    example0.erl


    A simple button.

    example1.erl


    A label which changes when you hit the button.

    example2.erl


    Entries. The text is swapped when you hit the button.

    example3.erl


    Entry. When you hit CR the label is changed.

    example4.erl


    Scrollbars.

    example5.erl


    Progress bar.

    example6.erl


    Text display widget.

    example8.erl


    File display.

    example9.erl


    A simple shell.

    example10.erl


    Interactive layout control.

    example11.erl


    Forms.

    example12.erl


    Draggable frames.

    example13.erl


    Draggable objects.

    example14.erl


    Pop up menus.

    example16.erl


    Animation.

    example17.erl


    Color picker.

    example18.erl

    Download

    This directory contains the latest stable release and daily (unstable) updates.

    Notes

    [1] The "mount point of a widget is a point to which sub-widgets may be attached. Usually a widget has one top-level widget, in which case this widget is the mount point. If the widget controls several top-level windows then the widget controlling process will have to choose which of these widget is an appropriate mount point.

    [2] The generic protocol is implemented in sw:generic/3