BOGUE is a desktop GUI library (graphical user interface) for ocaml, which is
ocaml
,It can be used for desktop applications, for games, or simply for debugging programs (modifying variables on-the-fly, printing output, etc.)
The API is still subject to (small) changes.
Use github’s issues if you need help.
Distributed under ISC license.
BOGUE is quite easy to use. First, install the library (and examples) with
opam install bogue
(see also README or install for more detailed instructions).
The “hello world” minimal code is as follows:
open Bogue
let () =
"Hello world"
Widget.label
|> Layout.resident
|> Bogue.of_layout |> Bogue.run
After compiling and running (or executing in the toplevel), it should open a tiny window with “Hello world”, like this:
See the other example for more details on how to compile or run in the toplevel.
If you’re new to Bogue, I advise you to read the following paragraphs about the general principles.
You can also directly try the tutorials.
There is a minimal example in the doc, which is slightly more informative than the “hello world” above, with a label and a check button.
boguex
examplesOnce you installed the bogue
package, you should be able to run the boguex
command from the terminal. It contains samples of all useful constructions (widgets and layouts). Just type
boguex -h
to get the list of available examples; there are about 50 of them! To run a particular example, for instance example #41, just type boguex 41
. (If you run boguex
without argument you will have to go through all examples, which is probably not what you want.) The source code is the file examples/example.ml
. It is very useful to quickly check how to use various constructs.
input
examplesThese two examples demonstrate two interesting ways of obtaining the same result: update a text display with the user-entered text in a separate widget. See example/input
and example/input-immediate
. See also below for an explanation of these programs.
randomize
appClick to obtain a random integer in a fancy way. This simple application shows two text-input widgets, where you can enter only numbers, and a push-button which generates random number while pressed. Source code, video.
demo
exampleIncludes sliding tabs, check boxes, radio buttons, text-input connected to a text display, slider/progress bar, popup, image. See the video. Source code can be found here.
bounce
, an electro-magnetic game!Play with gravity and magnetic field to try to control a bouncing ball (a classical charged particle with mass)! This app demonstrates how to use BOGUE’s Animated variables. No need to write a game loop! Source code will be soon here.
BOGUE is built around usual GUI notions (widgets, events, callbacks) but has some peculiarities that are good to know.
There are mainly two types of objects: widgets and layouts. 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]
]
Of course, out of this, many more complicated arrangements can be obtained. See here for a list of useful predefined layouts (menus, lists, radio buttons, tabs, etc.).
BOGUE has three ways of functioning (and they can be mixed):
Let it run the mainloop:
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.
react on “realtime” with “immediate” actions:
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.
embed BOGUE in your own mainloop:
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.
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 similar to many event-driven GUIs (like GTK+), and it’s both powerful and flexible (and also quite efficient, for large programs).
The program will look like the following.
let action input label _ =
let text = W.get_text input in
"Hello " ^ text ^ "!") in W.set_text label (
let input = W.text_input ~max_size:200 ~prompt:"Enter your name" () in
let label = W.label ~size:40 "Hello!" in
let c = W.connect input label action Sdl.Event.[text_input; key_down] in
let layout = L.tower [L.resident ~w:400 input; L.resident ~w:400 ~h:200 label] in
let board = Bogue.of_layout ~connections:[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).
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
input
and label
defined above):let before_display () =
let text = W.get_text input in
"Hello " ^ text ^ "!") in W.set_text label (
let board = Bogue.of_layout 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
use Bogue.make_sdl_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.