BOGUE is a desktop GUI library (graphical user interface) for ocaml, which is
It can be used for desktop applications, for games, or simply for debugging programs (modifying variables on-the-fly, printing output, etc.)
This is an alpha release. There are known bugs, and it has not been tested for large and complex applications. Use at your own risk!
On the other hand, if you are not scared by this dislaimer, please try BOGUE! We need your feedback to make this library better!
Distributed under ISC license.
OK, go install it and try the minimal example. But then come back here to read a little bit about the general principles, it should help.
BOGUE is built around usual GUI notions (widgets, events, callbacks) but has some particularities that are good to know.
BOGUE has three ways of functioning (and they can be mixed):
it waits for events, draws the graphics, execute predefined actions when the user interacts (for instance, click on a button), and does this forever, until we decide to quit.
instead of using events to trigger an action, you can directly read data from any widget. For instance, continuously read what the user is typing in English to propose in real-time a translation in esperanto showed in another widget.
you have written a mainloop for your game, and sometimes you want to show GUI elements without stopping your loop. You just need to call BOGUE's "one_step" function at each frame, as you need.
BOGUE is not object oriented (it uses standard ocaml modules) but the feeling is not too different. There are mainly two types of objects: widgets and layout. Widgets are small graphics elements (buttons, images, etc.) and they can be combined into a Layout. Roughly speaking:
It's convenient to alias these modules in your program:
open Bogue module W = Widget module L = Layout
Widgets use directly the SDL2 library to draw themselves. Here is the list of implemented widgets:
Widgets also carry the logic of the GUI. They respond to events and they can be connected to another widget.
In some sense, a layout is like a house, or a room in a house, and a widget is a resident (inhabitant) of the house, or an object in a room. Objects can be connected: for instance your thermostat is connected to your heating system. If some event (like a heat wave) makes your thermostat react, then it should tell the heating system to do something.
More prosaically, if you click on a checkbox widget, you may want to change the text in a label widget (which can be located in another room (layout).
BOGUE uses this vocabulary of connections, but if you wish, they can be treated as simple callbacks (actions). The user can create connections either for the main program or to be run in a separate thread (which should be the default as soon at the action to be executed could take a long time to execute --- more than the frame rate which is about 17ms).
Layouts are created by the user to combine several widgets together. Of course the library also provides a number of predefined layouts, see below.
There are two types of layouts:
The geometry of rooms inside a house (= children layouts inside a parent layout) can be arbitrary, but the two main useful ones are:
[ [room#1] [room#2] ... [room#n] ]
[ [room#1] [room#2] ... [room#n] ]
Here is what we want to program:
We let the user enter her/his name on top of the window, for instance "Audrey", and simultaneously there is a large greeting message in the center of the window saying "Hello Audrey!".
It's not the most natural way of programming if one is not used to event-driven GUIs (like GTK+), but it's a powerful method (and also quite efficient, for large programs).
The program will look like the following.
The action: given a TextInput widget and a Label widget, we want to update the Label each time the user presses a key in the TextInput. Here is the action:
let action input label _ = let text = W.get_text input in W.set_text label ("Hello " ^ text ^ "!") in
The widgets: we need a TextInput and a Label:
let input = W.text_input ~max_size:200 ~prompt:"Enter your name" () in let label = W.label ~size:40 "Hello!" in
We create a connection between them, reacting to the key-pressed events:
let c = W.connect input label action Sdl.Event.[text_input; key_down] in
We arrange the widgets in a layout (a tower with two residents):
let layout = L.tower [L.resident ~w:400 input; L.resident ~w:400 ~h:200 label] in
It remains to create the board and run it. That's it!
let board = Bogue.make [c] [layout] in Bogue.run board
Note that the order 1. and 2. can be swapped. In more complex examples, you cannot always separate all these steps like this, you may need to mix them, for instance because you want a widget action to modify a layout on the fly...
For simple programs like our example, when the action is fast and will not block the interface, one can use the immediate mode, which is easier to program and debug (no event, no callback).
We define the two widgets and the layout as in steps 2 and 4 in the standard method above:
let input = W.text_input ~max_size:200 ~prompt:"Enter your name" () in let label = W.label ~size:40 "Hello!" in let layout = L.tower [L.resident ~w:400 input; L.resident ~w:400 ~h:200 label] in
We define the action to be executed at each frame display (of course it's not a pure function, it uses the variables
label defined above):
let before_display () = let text = W.get_text input in W.set_text label ("Hello " ^ text ^ "!") in
We make the board and run it. Done!
let board = Bogue.make  [layout] in Bogue.run ~before_display board
This is only useful if you already have an application with an event/display loop (like a game) and you want to add some GUI on top of this.
use one of the methods above to create your board
Bogue.make_windows to either create news windows for the GUI, or use already existing SDL windows.
anytime you want to show the GUI, just call the
Bogue.one_step function in your loop, in general after all other renderings and just before
Sdl.render_present. When the GUI is displayed, be sure to disable all event handling functions that could interfere with BOGUE.
See the file
Embedded.md for more details.
Of course. It's here. It's not really complete yet (some functions that are used internally but might be useful enough to be made public are not shown there), please ask if a feature is missing.