GUI - Rebuilt Quick Help layout to allow multi-column key binding section.

Change-Id: Icada50c695ce60c8cbedb38d86434a842d935e77
This commit is contained in:
Simon Hunt 2014-12-04 16:10:40 -08:00
parent 2bea44e615
commit a1a00c247e
3 changed files with 201 additions and 127 deletions

View File

@ -417,19 +417,23 @@
} }
function setupGlobalKeys() { function setupGlobalKeys() {
keyHandler.globalKeys = { $.extend(keyHandler, {
slash: [quickHelp, 'Show / hide Quick Help'], globalKeys: {
backSlash: [quickHelp, 'Show / hide Quick Help'], backSlash: [quickHelp, 'Show / hide Quick Help'],
esc: [escapeKey, 'Dismiss dialog or cancel selections'], slash: [quickHelp, 'Show / hide Quick Help'],
T: [toggleTheme, "Toggle theme"] esc: [escapeKey, 'Dismiss dialog or cancel selections'],
}; T: [toggleTheme, "Toggle theme"]
// Masked keys are global key handlers that always return true. },
// That is, the view will never see the event for that key. globalFormat: ['backSlash', 'slash', 'esc', 'T'],
keyHandler.maskedKeys = {
slash: true, // Masked keys are global key handlers that always return true.
backSlash: true, // That is, the view will never see the event for that key.
T: true maskedKeys: {
}; slash: true,
backSlash: true,
T: true
}
});
} }
function quickHelp(view, key, code, ev) { function quickHelp(view, key, code, ev) {

View File

@ -26,7 +26,7 @@
#quickhelp svg { #quickhelp svg {
position: absolute; position: absolute;
bottom: 40px; top: 180px;
opacity: 1; opacity: 1;
} }
@ -36,7 +36,7 @@
} }
#quickhelp svg text.title { #quickhelp svg text.title {
font-size: 8pt; font-size: 10pt;
font-style: italic; font-style: italic;
text-anchor: middle; text-anchor: middle;
fill: #999; fill: #999;
@ -46,23 +46,21 @@
fill: white; fill: white;
} }
#quickhelp svg g.keyItem line { #quickhelp svg g line.qhrowsep {
stroke: #888; stroke: #888;
stroke-dasharray: 2 2; stroke-dasharray: 2 2;
} }
#quickhelp svg text { #quickhelp svg text {
font-size: 5pt; font-size: 7pt;
alignment-baseline: middle; alignment-baseline: middle;
} }
#quickhelp svg text.key { #quickhelp svg text.key {
font-size: 5pt;
fill: #add; fill: #add;
} }
#quickhelp svg text.desc { #quickhelp svg text.desc {
font-size: 5pt;
fill: #ddd; fill: #ddd;
} }

View File

@ -26,29 +26,35 @@
(function (onos){ (function (onos){
'use strict'; 'use strict';
// API's
var api = onos.api;
// Config variables // Config variables
var w = '100%', var w = '100%',
h = '80%', h = '80%',
fade = 500, fade = 500,
vb = '-200 0 400 400'; vb = '-200 0 400 400';
// layout configuration
var pad = 10,
offy = 45,
sepYDelta = 20,
colXDelta = 16,
yTextSpc = 12,
offDesc = 8;
// State variables // State variables
var data = []; var data = [],
yCount;
// DOM elements and the like // DOM elements and the like
var qhdiv = d3.select('#quickhelp'), var qhdiv = d3.select('#quickhelp'),
svg = qhdiv.select('svg'), svg = qhdiv.select('svg'),
pane, pane, rect, items;
rect,
items,
keyAgg;
// General functions // General functions
function isA(a) { function isA(a) { return $.isArray(a) ? a : null; }
return $.isArray(a) ? a : null; function isS(s) { return typeof s === 'string'; }
function cap(s) {
return s.replace(/^[a-z]/, function (m) { return m.toUpperCase(); });
} }
var keyDisp = { var keyDisp = {
@ -63,144 +69,212 @@
downArrow: 'D-arrow' downArrow: 'D-arrow'
}; };
function cap(s) {
return s.replace(/^[a-z]/, function (m) { return m.toUpperCase(); });
}
function mkKeyDisp(id) { function mkKeyDisp(id) {
var v = keyDisp[id] || id; var v = keyDisp[id] || id;
return cap(v); return cap(v);
} }
// layout configuration function addSeparator(el, i) {
var pad = 8, var y = sepYDelta/2 - 5;
offy = 45, el.append('line')
dy = 10, .attr({ 'class': 'qhrowsep', x1: 0, y1: y, x2: 0, y2: y });
offDesc = 8; }
// D3 magic function addContent(el, data, ri) {
function updateKeyItems() { var xCount = 0,
var keyItems = items.selectAll('.keyItem') clsPfx = 'qh-r' + ri + '-c';
.data(data);
var entering = keyItems.enter() function addColumn(el, c, i) {
.append('g') var cls = clsPfx + i,
.attr({ oy = 0,
id: function (d) { return d.id; }, aggKey = el.append('g').attr('visibility', 'hidden'),
class: 'keyItem' gcol = el.append('g').attr({
'class': cls,
transform: translate(xCount, 0)
});
c.forEach(function (j) {
var k = j[0],
v = j[1];
if (k !== '-') {
aggKey.append('text').text(k);
gcol.append('text').text(k)
.attr({
'class': 'key',
y: oy
});
gcol.append('text').text(v)
.attr({
'class': 'desc',
y: oy
});
}
oy += yTextSpc;
}); });
entering.each(function (d, i) { // adjust position of descriptions, based on widest key
var kbox = aggKey.node().getBBox(),
ox = kbox.width + offDesc;
gcol.selectAll('.desc').attr('x', ox);
aggKey.remove();
// now update x-offset for next column
var bbox = gcol.node().getBBox();
xCount += bbox.width + colXDelta;
}
data.forEach(function (d, i) {
addColumn(el, d, i);
});
// finally, return the height of the row..
return el.node().getBBox().height;
}
function updateKeyItems() {
var rows = items.selectAll('.qhRow').data(data);
yCount = offy;
var entering = rows.enter()
.append('g')
.attr({
'class': 'qhrow'
});
entering.each(function (r, i) {
var el = d3.select(this), var el = d3.select(this),
y = offy + dy * i; sep = r.type === 'sep',
dy;
if (d.id[0] === '_') { el.attr('transform', translate(0, yCount));
el.append('line')
.attr({ x1: 0, y1: y, x2: 1, y2: y}); if (sep) {
addSeparator(el, i);
yCount += sepYDelta;
} else { } else {
el.append('text') dy = addContent(el, r.data, i);
.text(d.key) yCount += dy;
.attr({
class: 'key',
x: 0,
y: y
});
// NOTE: used for sizing column width...
keyAgg.append('text').text(d.key).attr('class', 'key');
el.append('text')
.text(d.desc)
.attr({
class: 'desc',
x: offDesc,
y: y
});
} }
}); });
var kbox = keyAgg.node().getBBox(); // size the backing rectangle
items.selectAll('.desc').attr('x', kbox.width + offDesc); var ibox = items.node().getBBox(),
paneW = ibox.width + pad * 2,
paneH = ibox.height + offy;
var box = items.node().getBBox(), items.selectAll('.qhrowsep').attr('x2', ibox.width);
paneW = box.width + pad * 2,
paneH = box.height + offy;
items.selectAll('line').attr('x2', box.width);
items.attr('transform', translate(-paneW/2, -pad)); items.attr('transform', translate(-paneW/2, -pad));
rect.attr({ rect.attr({
width: paneW, width: paneW,
height: paneH, height: paneH,
transform: translate(-paneW/2-pad, 0) transform: translate(-paneW/2-pad, 0)
}); });
} }
function translate(x, y) { function translate(x, y) {
return 'translate(' + x + ',' + y + ')'; return 'translate(' + x + ',' + y + ')';
} }
function checkFmt(fmt) {
// should be a single array of keys,
// or array of arrays of keys (one per column).
// return null if there is a problem.
var a = isA(fmt),
n = a && a.length,
ns = 0,
na = 0;
if (n) {
// it is an array which has some content
a.forEach(function (d) {
isA(d) && na++;
isS(d) && ns++;
});
if (na === n || ns === n) {
// all arrays or all strings...
return a;
}
}
return null;
}
function buildBlock(map, fmt) {
var b = [];
fmt.forEach(function (k) {
var v = map.get(k),
a = isA(v),
d = (a && a[1]);
// '-' marks a separator; d is the description
if (k === '-' || d) {
b.push([mkKeyDisp(k), d]);
}
});
return b;
}
function emptyRow() {
return { type: 'row', data: []};
}
function mkArrRow(fmt) {
var d = emptyRow();
d.data.push(fmt);
return d;
}
function mkColumnarRow(map, fmt) {
var d = emptyRow();
fmt.forEach(function (a) {
d.data.push(buildBlock(map, a));
});
return d;
}
function mkMapRow(map, fmt) {
var d = emptyRow();
d.data.push(buildBlock(map, fmt));
return d;
}
function addRow(row) {
var d = row || { type: 'sep' };
data.push(d);
}
function aggregateData(bindings) { function aggregateData(bindings) {
var hf = '_helpFormat', var hf = '_helpFormat',
gmap = d3.map(bindings.globalKeys), gmap = d3.map(bindings.globalKeys),
gfmt = bindings.globalFormat,
vmap = d3.map(bindings.viewKeys), vmap = d3.map(bindings.viewKeys),
fmt = vmap.get(hf),
vgest = bindings.viewGestures, vgest = bindings.viewGestures,
gkeys = gmap.keys(), vfmt, vkeys;
vkeys,
sep = 0;
// filter out help format entry // filter out help format entry
vfmt = checkFmt(vmap.get(hf));
vmap.remove(hf); vmap.remove(hf);
vkeys = vmap.keys(),
gkeys.sort(); // if bad (or no) format, fallback to sorted keys
vkeys.sort(); if (!vfmt) {
vkeys = vmap.keys();
vfmt = vkeys.sort();
}
data = []; data = [];
gkeys.forEach(function (k) {
addItem('glob', k, gmap.get(k));
});
addItem('sep');
vkeys.forEach(function (k) {
addItem('view', k, vmap.get(k));
});
addItem('sep');
vgest.forEach(function (g) {
if (g.length === 2) {
addItem('gest', g[0], g[1]);
}
});
addRow(mkMapRow(gmap, gfmt));
function addItem(type, k, d) { addRow();
var id = type + '-' + k, addRow(isA(vfmt[0]) ? mkColumnarRow(vmap, vfmt) : mkMapRow(vmap, vfmt));
a = isA(d), addRow();
desc = a && a[1]; addRow(mkArrRow(vgest));
if (type === 'sep') {
data.push({
id: '_' + sep++,
type: type
});
} else if (type === 'gest') {
data.push({
id: id,
type: type,
key: k,
desc: d
});
} else if (desc) {
data.push(
{
id: id,
type: type,
key: mkKeyDisp(k),
desc: desc
}
);
}
}
} }
function popBind(bindings) { function popBind(bindings) {
pane = svg.append('g') pane = svg.append('g')
.attr({ .attr({
@ -220,8 +294,6 @@
}); });
items = pane.append('g'); items = pane.append('g');
keyAgg = pane.append('g').style('visibility', 'hidden');
aggregateData(bindings); aggregateData(bindings);
updateKeyItems(); updateKeyItems();