2. Defining a user interface with JSON

2.1. Problem

You want to create a user interface as quickly as possible; you also need to change it easily as requirements shift.

This need can arise when:

  • you are prototyping a user interface, and you need to quickly test new ideas.

  • the user interface you are building is likely to contain many elements and relationships between them.

2.2. Solution

Define the user interface in an external JSON file. Then create a ClutterScript object and load the JSON into it from the file.

This keeps the UI definition separate from the application logic and makes it easier to manage.

Note

See the introduction for the reasons why ClutterScript is a good solution, and for an overview of how JSON definitions work.

Here's an example JSON definition to put in the file:

[
  {
    "id" : "stage",
    "type" : "ClutterStage",
    "width" : 400,
    "height" : 400,
    "color" : "#333355ff",
    "children" : [ "box" ]
  },

  {
    "id" : "box",
    "type" : "ClutterBox",
    "width" : 400,
    "height" : 400,

    "layout-manager" : {
      "type" : "ClutterBinLayout",
      "x-align" : "center",
      "y-align" : "center"
    },

    "children" : [
      {
        "id" : "rectangle",
        "type" : "ClutterRectangle",
        "width" : 200,
        "height" : 200,
        "color" : "red"
      }
    ]
  }

]

In the application, load the JSON from the file with clutter_script_load_from_file(). (You can also load JSON from a string (gchar*) with clutter_script_load_from_data().)

Then retrieve objects by ID to use them in your code:

Example 8.2. Loading JSON from a file and retrieving objects defined by it

#include <stdlib.h>
#include <clutter/clutter.h>

int
main (int argc, char *argv[])
{
  ClutterActor *stage;
  ClutterScript *ui;

  gchar *filename = "script-ui.json";
  GError *error = NULL;

  if (clutter_init (&argc, &argv) != CLUTTER_INIT_SUCCESS)
    return 1;

  ui = clutter_script_new ();

  /* load a JSON file into the script */
  clutter_script_load_from_file (ui, filename, &error);

  if (error != NULL)
    {
      g_critical ("Error loading ClutterScript file %s\n%s", filename, error->message);
      g_error_free (error);
      exit (EXIT_FAILURE);
    }

  /* retrieve objects from the script */
  clutter_script_get_objects (ui,
                              "stage", &stage,
                              NULL);

  clutter_actor_show (stage);

  clutter_main ();

  return EXIT_SUCCESS;
}


Although we only retrieved the stage in the example above, clutter_script_get_objects() can retrieve multiple objects with a single call:

ClutterScript *script;
script = clutter_script_new ();

/* ...load JSON file etc. */

ClutterStage *stage;
ClutterActor *actor1;
ClutterActor *actor2;

/* use a NULL-terminated argument list of id,variable pairs */
clutter_script_get_objects (script,
                            "stage", &stage,
                            "actor1", &actor1,
                            "actor2", &actor2,
                            NULL);

You can also use clutter_script_get_object() to retrieve a single object, though you may have to cast it to the right type before use; for example:

ClutterStage *stage = CLUTTER_STAGE (clutter_script_get_object (script, "stage));

2.3. Discussion

In the sample code, the stage is part of the JSON definition. However, it doesn't have to be: it is possible to create the stage in application code; then load more components from one or more JSON definitions and attach them to the stage you constructed in code.

However, keeping most of the user interface definition in external JSON files makes it easier to change the UI without having to touch any code. If you have some user interface elements constructed in code and some in JSON, it can make refactoring more difficult.