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.