Find bogue on Github

BOGUE BOGUE, the ocaml GUI

About

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.)

Disclaimer

Getting started

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 () =
  Bogue.make [] [Layout.resident (Widget.label "Hello world")]
  |> Bogue.run;
  Bogue.quit ()

After compiling and running (or executing in the toplevel), it should open a tiny window with “Hello world”, like this:

Screenshot of the Hello world example. You can see the Bogue icon and the decorations added by my KDE window manager.

Screenshot of the Hello world example. You can see the Bogue icon and the decorations added by my KDE window manager.

See the other example for more details on how to compile or run in the toplevel.

Documentation and examples

The API is available here.

If you’re new to Bogue, I advise you to read the following paragraphs about the general principles.

Minimal example

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.

The 50 included boguex examples

Once 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.

The input examples

These 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.

The randomize app

Screenshot of the Randomize app.

Screenshot of the Randomize app.

Click 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.

The demo example

Includes sliding tabs, check boxes, radio buttons, text-input connected to a text display, slider/progress bar, popup, image. See the video. Source code will be available soon.

bounce, an electro-magnetic game!

Screenshot of the Bounce game.

Screenshot of the Bounce 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.

General Principles

BOGUE is built around usual GUI notions (widgets, events, callbacks) but has some peculiarities that are good to know.

Widgets and Layouts

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 and connections

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: a tree structure

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.).

The main loop

BOGUE has three ways of functioning (and they can be mixed):

  1. 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.

  2. 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.

  3. 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.

A simple example

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!”.

The standard method (events and callbacks)

examples/input

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.

  1. 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
  1. 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
  1. 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
  1. 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
  1. 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…

The “immediate” method

examples/input-immediate

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).

  1. 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
  1. We define the action to be executed at each frame display (of course it’s not a pure function, it uses the variables input and label defined above):
let before_display () =
  let text = W.get_text input in
  W.set_text label ("Hello " ^ text ^ "!") in
  1. We make the board and run it. Done!
let board = Bogue.make [] [layout] in
Bogue.run ~before_display board

The “embedded” method

examples/embed

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.

  1. use one of the methods above to create your board

  2. use Bogue.make_windows to either create news windows for the GUI, or use already existing SDL windows.

  3. 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.

Find bogue on Github