GNOME Markup Language

Why GML?

Because Ubuntu showed us that QML and RAD is the way forward, with a rich declarative language and small snippets of code. But we don't want to abandon GNOME technologies and create a Frankenstein monster like Ubuntu does (with apps loading Qt and glib/gobject at the same time).

A GML example

This is the UI of gnome-weather. This file would be shipped as data/window.gml and then included in the GResource.

   1 // -*- mode: js; indent-tabs-mode: nil; c-basic-offset: 4 -*-
   2 
   3 // Comments are supported (unlike in JSON)
   4 
   5 // Types are actual references, not GType names
   6 { Type: Gtk.ApplicationWindow,
   7   Id: 'main-window',
   8   // Template definitions will not be built (much
   9   // like <exposed-object> in GtkBuilder)
  10   Template: true,
  11   visible: true,
  12   can_focus: false,
  13 
  14   Children: [
  15       { Type: Gtk.Grid,
  16         Id: 'main-panel',
  17         visible: true,
  18         can_focus: false,
  19 
  20         // Enums are written with their full name
  21         orientation: Gtk.Orientation.VERTICAL,
  22 
  23         Children: [
  24             { Type: Gd.HeaderBar,
  25               Id: 'header-bar',
  26               visible: true,
  27               vexpand: false,
  28 
  29               Children: [
  30                   { Type: Gd.HeaderSimpleButton,
  31                     Id: 'new-button',
  32                     visible: true,
  33                     can_focus: false,
  34                     label: _("New"),
  35 
  36                     // Signals begin with @, and can either be
  37                     // strings or full functions
  38                     '@clicked': '_newLocation',
  39 
  40                     // Child properties are prefixed with !
  41                     '!pack-type': Gtk.PackType.START,
  42                   },
  43 
  44                   { Type: Gd.HeaderSimpleButton,
  45                     Id: 'world-button',
  46                     visible: true,
  47                     can_focus: false,
  48                     label: _("World Weather"),
  49 
  50                     '@clicked': '_goWorld',
  51 
  52                     '!pack-type': Gtk.PackType.START,
  53                   },
  54 
  55                   { Type: Gd.HeaderSimpleButton,
  56                     Id: 'select-button',
  57                     visible: true,
  58                     can_focus: false,
  59                     symbolic_icon_name: 'object-select-symbolic',
  60 
  61                     // 'this', in the context of a signal, is the
  62                     // scope object which is passed when the GML is loaded
  63                     '@clicked': function() {
  64                         // Objects can be referred to like in GtkBuilder
  65                         $('world-view').selection_mode = true;
  66                     },
  67 
  68                     '!pack-type': Gtk.PackType.END,
  69                   },
  70 
  71                   { Type: Gd.HeaderSimpleButton,
  72                     Id: 'done-button',
  73                     StyleClass: 'suggested-action',
  74                     visible: true,
  75                     no_show_all: true,
  76                     can_focus: false,
  77                     label: _("Done"),
  78 
  79                     '@clicked': function() {
  80                         $('world-view').selection_mode = false;
  81                     },
  82 
  83                     '!pack-type': Gtk.PackType.END,
  84                   },
  85 
  86                   { Type: Gd.HeaderSimpleButton,
  87                     Id: 'refresh-button',
  88                     visible: true,
  89                     can_focus: false,
  90                     symbolic_icon_name: 'view-refresh-symbolic',
  91 
  92                     '@clicked': 'update',
  93 
  94                     '!pack-type': Gtk.PackType.END,
  95                   },
  96               ]
  97 
  98               '!left-attach': 0,
  99               '!top-attach': 0,
 100               '!width': 1,
 101               '!height': 1,
 102             }
 103         ]
 104       },
 105 
 106       { Type: Gtk.Overlay,
 107         Id: 'main-overlay',
 108 
 109         Children: [
 110             { Type: Gd.Stack,
 111               Id: 'main-stack',
 112               transition_type: Gd.StackTransitionType.CROSSFADE,
 113 
 114               Children: [
 115                   // Non GI modules can be references (if exposed when
 116                   // loading the GML)
 117                   // Also, JS classes can be built (which is not possible
 118                   // with regular g_object_new())
 119                   { Type: City.WeatherView,
 120                     Id: 'city-view',
 121                     hexpand: true,
 122                     vexpand: true,
 123                   },
 124 
 125                   { Type: Gd.MainView,
 126                     Id: 'main-view',
 127                     view_type: Gd.MainViewType.ICON,
 128                     // Objects (not just GObjects) can be exposed from the loader
 129                     model: $('model'),
 130 
 131                     '@item-activated': '_itemActivated',
 132                     '@selection-mode-request': function(view) {
 133                         view.selection_mode = true;
 134                     },
 135 
 136                     '@notify::selection-mode': function(view) {
 137                         let mode = view.selection_mode;
 138                         let header = $('header-bar');
 139 
 140                         if (mode) {
 141                             header.get_style_context().add_class('selection-mode');
 142                             header.set_title(_("Click on locations to select them"));
 143                         } else {
 144                             header.get_style_context().remove_class('selection-mode');
 145                             header.set_title(null);
 146                         }
 147                     },
 148 
 149                     // Property bindings can be created
 150                     '->[invert]selection-mode': 'new-button::visible',
 151                     '->[invert]selection-mode': 'select-button::visible',
 152                     '->[sync_create]selection-mode': 'done-button::visible',
 153                   },
 154               ]
 155             }
 156         ],
 157 
 158         // Children of a different type can be created
 159         'Children::overlay': [
 160             { Type: Window.SelectionToolbar,
 161               Id: 'selection-toolbar'
 162             },
 163         }
 164       }
 165   ]
 166 }

The above would then be constructed as

   1 const MainWindow = new Lang.Class({
   2     Name: 'MainWindow',
   3     Extends: Gtk.ApplicationWindow,
   4 
   5     _init: function() {
   6          this.parent();
   7 
   8          this._model = ...;
   9          let gml = GML.load('/org/gnome/weather/window.gml',
  10                             { 'Gtk': Gtk, 'Gd': Gd,
  11                               'City': imports.city,
  12                               'Window': module.exports });
  13          gml.extend(this, 'main-window',
  14                     { model: this._model });
  15     }
  16 });

Implementation

Not yet.

I first want some feedback on the idea, before I write the actual code.

GiovanniCampagna/Experiments/GML (last edited 2013-03-09 15:38:16 by GiovanniCampagna)