GUI - Rebuilt Quick Help layout to allow multi-column key binding section.
Change-Id: Icada50c695ce60c8cbedb38d86434a842d935e77
Showing
3 changed files
with
198 additions
and
124 deletions
... | @@ -417,19 +417,23 @@ | ... | @@ -417,19 +417,23 @@ |
417 | } | 417 | } |
418 | 418 | ||
419 | function setupGlobalKeys() { | 419 | function setupGlobalKeys() { |
420 | - keyHandler.globalKeys = { | 420 | + $.extend(keyHandler, { |
421 | - slash: [quickHelp, 'Show / hide Quick Help'], | 421 | + globalKeys: { |
422 | - backSlash: [quickHelp, 'Show / hide Quick Help'], | 422 | + backSlash: [quickHelp, 'Show / hide Quick Help'], |
423 | - esc: [escapeKey, 'Dismiss dialog or cancel selections'], | 423 | + slash: [quickHelp, 'Show / hide Quick Help'], |
424 | - T: [toggleTheme, "Toggle theme"] | 424 | + esc: [escapeKey, 'Dismiss dialog or cancel selections'], |
425 | - }; | 425 | + T: [toggleTheme, "Toggle theme"] |
426 | - // Masked keys are global key handlers that always return true. | 426 | + }, |
427 | - // That is, the view will never see the event for that key. | 427 | + globalFormat: ['backSlash', 'slash', 'esc', 'T'], |
428 | - keyHandler.maskedKeys = { | 428 | + |
429 | - slash: true, | 429 | + // Masked keys are global key handlers that always return true. |
430 | - backSlash: true, | 430 | + // That is, the view will never see the event for that key. |
431 | - T: true | 431 | + maskedKeys: { |
432 | - }; | 432 | + slash: true, |
433 | + backSlash: true, | ||
434 | + T: true | ||
435 | + } | ||
436 | + }); | ||
433 | } | 437 | } |
434 | 438 | ||
435 | function quickHelp(view, key, code, ev) { | 439 | function quickHelp(view, key, code, ev) { | ... | ... |
... | @@ -26,7 +26,7 @@ | ... | @@ -26,7 +26,7 @@ |
26 | 26 | ||
27 | #quickhelp svg { | 27 | #quickhelp svg { |
28 | position: absolute; | 28 | position: absolute; |
29 | - bottom: 40px; | 29 | + top: 180px; |
30 | opacity: 1; | 30 | opacity: 1; |
31 | } | 31 | } |
32 | 32 | ||
... | @@ -36,7 +36,7 @@ | ... | @@ -36,7 +36,7 @@ |
36 | } | 36 | } |
37 | 37 | ||
38 | #quickhelp svg text.title { | 38 | #quickhelp svg text.title { |
39 | - font-size: 8pt; | 39 | + font-size: 10pt; |
40 | font-style: italic; | 40 | font-style: italic; |
41 | text-anchor: middle; | 41 | text-anchor: middle; |
42 | fill: #999; | 42 | fill: #999; |
... | @@ -46,23 +46,21 @@ | ... | @@ -46,23 +46,21 @@ |
46 | fill: white; | 46 | fill: white; |
47 | } | 47 | } |
48 | 48 | ||
49 | -#quickhelp svg g.keyItem line { | 49 | +#quickhelp svg g line.qhrowsep { |
50 | stroke: #888; | 50 | stroke: #888; |
51 | stroke-dasharray: 2 2; | 51 | stroke-dasharray: 2 2; |
52 | } | 52 | } |
53 | 53 | ||
54 | #quickhelp svg text { | 54 | #quickhelp svg text { |
55 | - font-size: 5pt; | 55 | + font-size: 7pt; |
56 | alignment-baseline: middle; | 56 | alignment-baseline: middle; |
57 | } | 57 | } |
58 | 58 | ||
59 | #quickhelp svg text.key { | 59 | #quickhelp svg text.key { |
60 | - font-size: 5pt; | ||
61 | fill: #add; | 60 | fill: #add; |
62 | } | 61 | } |
63 | 62 | ||
64 | #quickhelp svg text.desc { | 63 | #quickhelp svg text.desc { |
65 | - font-size: 5pt; | ||
66 | fill: #ddd; | 64 | fill: #ddd; |
67 | } | 65 | } |
68 | 66 | ... | ... |
... | @@ -26,29 +26,35 @@ | ... | @@ -26,29 +26,35 @@ |
26 | (function (onos){ | 26 | (function (onos){ |
27 | 'use strict'; | 27 | 'use strict'; |
28 | 28 | ||
29 | - // API's | ||
30 | - var api = onos.api; | ||
31 | - | ||
32 | // Config variables | 29 | // Config variables |
33 | var w = '100%', | 30 | var w = '100%', |
34 | h = '80%', | 31 | h = '80%', |
35 | fade = 500, | 32 | fade = 500, |
36 | vb = '-200 0 400 400'; | 33 | vb = '-200 0 400 400'; |
37 | 34 | ||
35 | + // layout configuration | ||
36 | + var pad = 10, | ||
37 | + offy = 45, | ||
38 | + sepYDelta = 20, | ||
39 | + colXDelta = 16, | ||
40 | + yTextSpc = 12, | ||
41 | + offDesc = 8; | ||
42 | + | ||
38 | // State variables | 43 | // State variables |
39 | - var data = []; | 44 | + var data = [], |
45 | + yCount; | ||
40 | 46 | ||
41 | // DOM elements and the like | 47 | // DOM elements and the like |
42 | var qhdiv = d3.select('#quickhelp'), | 48 | var qhdiv = d3.select('#quickhelp'), |
43 | svg = qhdiv.select('svg'), | 49 | svg = qhdiv.select('svg'), |
44 | - pane, | 50 | + pane, rect, items; |
45 | - rect, | ||
46 | - items, | ||
47 | - keyAgg; | ||
48 | 51 | ||
49 | // General functions | 52 | // General functions |
50 | - function isA(a) { | 53 | + function isA(a) { return $.isArray(a) ? a : null; } |
51 | - return $.isArray(a) ? a : null; | 54 | + function isS(s) { return typeof s === 'string'; } |
55 | + | ||
56 | + function cap(s) { | ||
57 | + return s.replace(/^[a-z]/, function (m) { return m.toUpperCase(); }); | ||
52 | } | 58 | } |
53 | 59 | ||
54 | var keyDisp = { | 60 | var keyDisp = { |
... | @@ -63,144 +69,212 @@ | ... | @@ -63,144 +69,212 @@ |
63 | downArrow: 'D-arrow' | 69 | downArrow: 'D-arrow' |
64 | }; | 70 | }; |
65 | 71 | ||
66 | - function cap(s) { | ||
67 | - return s.replace(/^[a-z]/, function (m) { return m.toUpperCase(); }); | ||
68 | - } | ||
69 | - | ||
70 | function mkKeyDisp(id) { | 72 | function mkKeyDisp(id) { |
71 | var v = keyDisp[id] || id; | 73 | var v = keyDisp[id] || id; |
72 | return cap(v); | 74 | return cap(v); |
73 | } | 75 | } |
74 | 76 | ||
75 | - // layout configuration | 77 | + function addSeparator(el, i) { |
76 | - var pad = 8, | 78 | + var y = sepYDelta/2 - 5; |
77 | - offy = 45, | 79 | + el.append('line') |
78 | - dy = 10, | 80 | + .attr({ 'class': 'qhrowsep', x1: 0, y1: y, x2: 0, y2: y }); |
79 | - offDesc = 8; | 81 | + } |
82 | + | ||
83 | + function addContent(el, data, ri) { | ||
84 | + var xCount = 0, | ||
85 | + clsPfx = 'qh-r' + ri + '-c'; | ||
86 | + | ||
87 | + function addColumn(el, c, i) { | ||
88 | + var cls = clsPfx + i, | ||
89 | + oy = 0, | ||
90 | + aggKey = el.append('g').attr('visibility', 'hidden'), | ||
91 | + gcol = el.append('g').attr({ | ||
92 | + 'class': cls, | ||
93 | + transform: translate(xCount, 0) | ||
94 | + }); | ||
95 | + | ||
96 | + c.forEach(function (j) { | ||
97 | + var k = j[0], | ||
98 | + v = j[1]; | ||
99 | + | ||
100 | + if (k !== '-') { | ||
101 | + aggKey.append('text').text(k); | ||
102 | + | ||
103 | + gcol.append('text').text(k) | ||
104 | + .attr({ | ||
105 | + 'class': 'key', | ||
106 | + y: oy | ||
107 | + }); | ||
108 | + gcol.append('text').text(v) | ||
109 | + .attr({ | ||
110 | + 'class': 'desc', | ||
111 | + y: oy | ||
112 | + }); | ||
113 | + } | ||
114 | + | ||
115 | + oy += yTextSpc; | ||
116 | + }); | ||
117 | + | ||
118 | + // adjust position of descriptions, based on widest key | ||
119 | + var kbox = aggKey.node().getBBox(), | ||
120 | + ox = kbox.width + offDesc; | ||
121 | + gcol.selectAll('.desc').attr('x', ox); | ||
122 | + aggKey.remove(); | ||
123 | + | ||
124 | + // now update x-offset for next column | ||
125 | + var bbox = gcol.node().getBBox(); | ||
126 | + xCount += bbox.width + colXDelta; | ||
127 | + } | ||
128 | + | ||
129 | + data.forEach(function (d, i) { | ||
130 | + addColumn(el, d, i); | ||
131 | + }); | ||
132 | + | ||
133 | + // finally, return the height of the row.. | ||
134 | + return el.node().getBBox().height; | ||
135 | + } | ||
80 | 136 | ||
81 | - // D3 magic | ||
82 | function updateKeyItems() { | 137 | function updateKeyItems() { |
83 | - var keyItems = items.selectAll('.keyItem') | 138 | + var rows = items.selectAll('.qhRow').data(data); |
84 | - .data(data); | ||
85 | 139 | ||
86 | - var entering = keyItems.enter() | 140 | + yCount = offy; |
141 | + | ||
142 | + var entering = rows.enter() | ||
87 | .append('g') | 143 | .append('g') |
88 | .attr({ | 144 | .attr({ |
89 | - id: function (d) { return d.id; }, | 145 | + 'class': 'qhrow' |
90 | - class: 'keyItem' | ||
91 | }); | 146 | }); |
92 | 147 | ||
93 | - entering.each(function (d, i) { | 148 | + entering.each(function (r, i) { |
94 | var el = d3.select(this), | 149 | var el = d3.select(this), |
95 | - y = offy + dy * i; | 150 | + sep = r.type === 'sep', |
151 | + dy; | ||
152 | + | ||
153 | + el.attr('transform', translate(0, yCount)); | ||
96 | 154 | ||
97 | - if (d.id[0] === '_') { | 155 | + if (sep) { |
98 | - el.append('line') | 156 | + addSeparator(el, i); |
99 | - .attr({ x1: 0, y1: y, x2: 1, y2: y}); | 157 | + yCount += sepYDelta; |
100 | } else { | 158 | } else { |
101 | - el.append('text') | 159 | + dy = addContent(el, r.data, i); |
102 | - .text(d.key) | 160 | + yCount += dy; |
103 | - .attr({ | ||
104 | - class: 'key', | ||
105 | - x: 0, | ||
106 | - y: y | ||
107 | - }); | ||
108 | - // NOTE: used for sizing column width... | ||
109 | - keyAgg.append('text').text(d.key).attr('class', 'key'); | ||
110 | - | ||
111 | - el.append('text') | ||
112 | - .text(d.desc) | ||
113 | - .attr({ | ||
114 | - class: 'desc', | ||
115 | - x: offDesc, | ||
116 | - y: y | ||
117 | - }); | ||
118 | } | 161 | } |
119 | }); | 162 | }); |
120 | 163 | ||
121 | - var kbox = keyAgg.node().getBBox(); | 164 | + // size the backing rectangle |
122 | - items.selectAll('.desc').attr('x', kbox.width + offDesc); | 165 | + var ibox = items.node().getBBox(), |
166 | + paneW = ibox.width + pad * 2, | ||
167 | + paneH = ibox.height + offy; | ||
123 | 168 | ||
124 | - var box = items.node().getBBox(), | 169 | + items.selectAll('.qhrowsep').attr('x2', ibox.width); |
125 | - paneW = box.width + pad * 2, | ||
126 | - paneH = box.height + offy; | ||
127 | - | ||
128 | - items.selectAll('line').attr('x2', box.width); | ||
129 | items.attr('transform', translate(-paneW/2, -pad)); | 170 | items.attr('transform', translate(-paneW/2, -pad)); |
130 | rect.attr({ | 171 | rect.attr({ |
131 | width: paneW, | 172 | width: paneW, |
132 | height: paneH, | 173 | height: paneH, |
133 | transform: translate(-paneW/2-pad, 0) | 174 | transform: translate(-paneW/2-pad, 0) |
134 | }); | 175 | }); |
176 | + | ||
135 | } | 177 | } |
136 | 178 | ||
137 | function translate(x, y) { | 179 | function translate(x, y) { |
138 | return 'translate(' + x + ',' + y + ')'; | 180 | return 'translate(' + x + ',' + y + ')'; |
139 | } | 181 | } |
140 | 182 | ||
183 | + function checkFmt(fmt) { | ||
184 | + // should be a single array of keys, | ||
185 | + // or array of arrays of keys (one per column). | ||
186 | + // return null if there is a problem. | ||
187 | + var a = isA(fmt), | ||
188 | + n = a && a.length, | ||
189 | + ns = 0, | ||
190 | + na = 0; | ||
191 | + | ||
192 | + if (n) { | ||
193 | + // it is an array which has some content | ||
194 | + a.forEach(function (d) { | ||
195 | + isA(d) && na++; | ||
196 | + isS(d) && ns++; | ||
197 | + }); | ||
198 | + if (na === n || ns === n) { | ||
199 | + // all arrays or all strings... | ||
200 | + return a; | ||
201 | + } | ||
202 | + } | ||
203 | + return null; | ||
204 | + } | ||
205 | + | ||
206 | + function buildBlock(map, fmt) { | ||
207 | + var b = []; | ||
208 | + fmt.forEach(function (k) { | ||
209 | + var v = map.get(k), | ||
210 | + a = isA(v), | ||
211 | + d = (a && a[1]); | ||
212 | + | ||
213 | + // '-' marks a separator; d is the description | ||
214 | + if (k === '-' || d) { | ||
215 | + b.push([mkKeyDisp(k), d]); | ||
216 | + } | ||
217 | + }); | ||
218 | + return b; | ||
219 | + } | ||
220 | + | ||
221 | + function emptyRow() { | ||
222 | + return { type: 'row', data: []}; | ||
223 | + } | ||
224 | + | ||
225 | + function mkArrRow(fmt) { | ||
226 | + var d = emptyRow(); | ||
227 | + d.data.push(fmt); | ||
228 | + return d; | ||
229 | + } | ||
230 | + | ||
231 | + function mkColumnarRow(map, fmt) { | ||
232 | + var d = emptyRow(); | ||
233 | + fmt.forEach(function (a) { | ||
234 | + d.data.push(buildBlock(map, a)); | ||
235 | + }); | ||
236 | + return d; | ||
237 | + } | ||
238 | + | ||
239 | + function mkMapRow(map, fmt) { | ||
240 | + var d = emptyRow(); | ||
241 | + d.data.push(buildBlock(map, fmt)); | ||
242 | + return d; | ||
243 | + } | ||
244 | + | ||
245 | + function addRow(row) { | ||
246 | + var d = row || { type: 'sep' }; | ||
247 | + data.push(d); | ||
248 | + } | ||
249 | + | ||
141 | function aggregateData(bindings) { | 250 | function aggregateData(bindings) { |
142 | var hf = '_helpFormat', | 251 | var hf = '_helpFormat', |
143 | gmap = d3.map(bindings.globalKeys), | 252 | gmap = d3.map(bindings.globalKeys), |
253 | + gfmt = bindings.globalFormat, | ||
144 | vmap = d3.map(bindings.viewKeys), | 254 | vmap = d3.map(bindings.viewKeys), |
145 | - fmt = vmap.get(hf), | ||
146 | vgest = bindings.viewGestures, | 255 | vgest = bindings.viewGestures, |
147 | - gkeys = gmap.keys(), | 256 | + vfmt, vkeys; |
148 | - vkeys, | ||
149 | - sep = 0; | ||
150 | 257 | ||
151 | // filter out help format entry | 258 | // filter out help format entry |
259 | + vfmt = checkFmt(vmap.get(hf)); | ||
152 | vmap.remove(hf); | 260 | vmap.remove(hf); |
153 | - vkeys = vmap.keys(), | ||
154 | 261 | ||
155 | - gkeys.sort(); | 262 | + // if bad (or no) format, fallback to sorted keys |
156 | - vkeys.sort(); | 263 | + if (!vfmt) { |
264 | + vkeys = vmap.keys(); | ||
265 | + vfmt = vkeys.sort(); | ||
266 | + } | ||
157 | 267 | ||
158 | data = []; | 268 | data = []; |
159 | - gkeys.forEach(function (k) { | ||
160 | - addItem('glob', k, gmap.get(k)); | ||
161 | - }); | ||
162 | - addItem('sep'); | ||
163 | - vkeys.forEach(function (k) { | ||
164 | - addItem('view', k, vmap.get(k)); | ||
165 | - }); | ||
166 | - addItem('sep'); | ||
167 | - vgest.forEach(function (g) { | ||
168 | - if (g.length === 2) { | ||
169 | - addItem('gest', g[0], g[1]); | ||
170 | - } | ||
171 | - }); | ||
172 | 269 | ||
173 | - | 270 | + addRow(mkMapRow(gmap, gfmt)); |
174 | - function addItem(type, k, d) { | 271 | + addRow(); |
175 | - var id = type + '-' + k, | 272 | + addRow(isA(vfmt[0]) ? mkColumnarRow(vmap, vfmt) : mkMapRow(vmap, vfmt)); |
176 | - a = isA(d), | 273 | + addRow(); |
177 | - desc = a && a[1]; | 274 | + addRow(mkArrRow(vgest)); |
178 | - | ||
179 | - if (type === 'sep') { | ||
180 | - data.push({ | ||
181 | - id: '_' + sep++, | ||
182 | - type: type | ||
183 | - }); | ||
184 | - } else if (type === 'gest') { | ||
185 | - data.push({ | ||
186 | - id: id, | ||
187 | - type: type, | ||
188 | - key: k, | ||
189 | - desc: d | ||
190 | - }); | ||
191 | - } else if (desc) { | ||
192 | - data.push( | ||
193 | - { | ||
194 | - id: id, | ||
195 | - type: type, | ||
196 | - key: mkKeyDisp(k), | ||
197 | - desc: desc | ||
198 | - } | ||
199 | - ); | ||
200 | - } | ||
201 | - } | ||
202 | } | 275 | } |
203 | 276 | ||
277 | + | ||
204 | function popBind(bindings) { | 278 | function popBind(bindings) { |
205 | pane = svg.append('g') | 279 | pane = svg.append('g') |
206 | .attr({ | 280 | .attr({ |
... | @@ -220,8 +294,6 @@ | ... | @@ -220,8 +294,6 @@ |
220 | }); | 294 | }); |
221 | 295 | ||
222 | items = pane.append('g'); | 296 | items = pane.append('g'); |
223 | - keyAgg = pane.append('g').style('visibility', 'hidden'); | ||
224 | - | ||
225 | aggregateData(bindings); | 297 | aggregateData(bindings); |
226 | updateKeyItems(); | 298 | updateKeyItems(); |
227 | 299 | ... | ... |
-
Please register or login to post a comment