Simon Hunt

GUI -- Key Bindings pop-up (new branch)

Change-Id: I544c80b1ce78c231985d7104f60b59bba3b7911e
......@@ -23,13 +23,41 @@
#keymap svg {
position: absolute;
bottom: 40px;
opacity: 0.5;
opacity: 0.7;
}
#keymap svg g.pane {
fill: black;
}
#keymap svg text.title {
font-size: 12pt;
font-style: italic;
fill: #666;
text-anchor: middle;
fill: #444;
}
#keymap svg g.keyItem {
fill: white;
}
#keymap svg g.keyItem line {
stroke: #444;
stroke-dasharray: 4 4;
}
#keymap svg text {
font-size: 10pt;
alignment-baseline: middle;
}
#keymap svg text.key {
font-size: 10pt;
fill: #8aa;
}
#keymap svg text.desc {
font-size: 10pt;
fill: #888;
}
......
......@@ -33,9 +33,14 @@
var w = '100%',
h = '80%',
fade = 750,
vb = '-200 -200 400 400',
xpad = 20,
ypad = 10;
vb = '-220 -220 440 440',
paneW = 400,
paneH = 340,
offy = 65,
dy = 20,
offKey = 40,
offDesc = offKey + 50,
lineW = paneW - (2*offKey);
// State variables
var data = [];
......@@ -43,104 +48,121 @@
// DOM elements and the like
var kmdiv = d3.select('#keymap');
function computeBox(el) {
var text = el.select('text'),
box = text.node().getBBox();
// center
box.x = -box.width / 2;
box.y = -box.height / 2;
function isA(a) {
return $.isArray(a) ? a : null;
}
// add some padding
box.x -= xpad;
box.width += xpad * 2;
box.y -= ypad;
box.height += ypad * 2;
return box;
}
var svg = kmdiv.select('svg'),
pane;
function updateKeymap() {
var items = svg.selectAll('.bindingItem')
function updateKeyItems() {
var items = pane.selectAll('.keyItem')
.data(data);
var entering = items.enter()
.append('g')
.attr({
class: 'bindingItem',
opacity: 0
})
.transition()
.duration(fade)
.attr('opacity', 1);
id: function (d) { return d.id; },
class: 'keyItem'
});
entering.each(function (d) {
entering.each(function (d, i) {
var el = d3.select(this),
box;
y = offy + dy * i;
d.el = el;
el.append('rect').attr({ rx: 10, ry: 10});
el.append('text').text(d.label);
box = computeBox(el);
el.select('rect').attr(box);
if (d.id === '_') {
el.append('line')
.attr({
class: 'sep',
x1: offKey,
y1: y,
x2: offKey + lineW,
y2: y
});
} else {
el.append('text')
.text(d.key)
.attr({
class: 'key',
x: offKey,
y: y
});
items.exit()
.transition()
.duration(fade)
.attr({ opacity: 0})
.remove();
el.append('text')
.text(d.desc)
.attr({
class: 'desc',
x: offDesc,
y: y
});
}
function clearFlash() {
if (timer) {
clearInterval(timer);
});
}
function aggregateData(bindings) {
var gmap = d3.map(bindings.globalKeys),
vmap = d3.map(bindings.viewKeys),
gkeys = gmap.keys(),
vkeys = vmap.keys();
gkeys.sort();
vkeys.sort();
data = [];
updateFeedback();
}
gkeys.forEach(function (k) {
addItem('global', k, gmap.get(k));
});
addItem('separator');
vkeys.forEach(function (k) {
addItem('view', k, vmap.get(k));
});
// for now, simply display some text feedback
function flash(text) {
// cancel old scheduled event if there was one
if (timer) {
clearInterval(timer);
function addItem(type, k, d) {
var id = type + '-' + k,
a = isA(d),
desc = a && a[1];
if (desc) {
data.push(
{
id: id,
type: type,
key: k,
desc: desc
}
);
} else if (type === 'separator') {
data.push({
id: '_',
type: type
});
}
timer = setInterval(function () {
clearFlash();
}, showFor);
data = [{
label: text
}];
updateFeedback();
}
// =====================================
var svg = kmdiv.select('svg');
}
function populateBindings(bindings) {
svg.append('g')
.attr({
class: 'keyBindings',
transform: 'translate(-200,-120)',
transform: 'translate(-200,-200)',
opacity: 0
})
.transition()
.duration(fade)
.attr('opacity', 1);
var g = svg.select('g');
pane = svg.select('g');
g.append('rect')
pane.append('rect')
.attr({
width: 400,
height: 240,
width: paneW,
height: paneH,
rx: 8
});
g.append('text')
.text('Key Bindings')
pane.append('text')
.text('Keyboard Shortcuts')
.attr({
x: 200,
y: 0,
......@@ -148,11 +170,12 @@
class: 'title'
});
// TODO: append .keyItems to rectangle
aggregateData(bindings);
updateKeyItems();
}
function fadeBindings() {
svg.selectAll('g')
svg.selectAll('g.keyBindings')
.transition()
.duration(fade)
.attr('opacity', 0);
......@@ -178,7 +201,8 @@
if (svg.empty()) {
addSvg();
populateBindings(bindings);
console.log("SHOW KEY MAP");
} else {
hideKeyMap();
}
}
......@@ -187,7 +211,6 @@
if (!svg.empty()) {
fadeBindings();
removeSvg();
console.log("HIDE KEY MAP");
return true;
}
return false;
......
......@@ -413,9 +413,9 @@
function setupGlobalKeys() {
keyHandler.globalKeys = {
slash: keyMap,
esc: escapeKey,
T: toggleTheme
slash: [keyMap, 'Show / hide keyboard shortcuts'],
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.
......@@ -476,7 +476,8 @@
var event = d3.event,
keyCode = event.keyCode,
key = whatKey(keyCode),
gcb = isF(keyHandler.globalKeys[key]),
gk = keyHandler.globalKeys[key],
gcb = isF(gk) || (isA(gk) && isF(gk[0])),
vk = keyHandler.viewKeys[key],
vcb = isF(vk) || (isA(vk) && isF(vk[0])) || isF(keyHandler.viewFn);
......
......@@ -144,12 +144,12 @@
B: [toggleBg, 'Toggle background image'],
L: [cycleLabels, 'Cycle Device labels'],
P: togglePorts,
U: unpin,
R: resetZoomPan,
H: toggleHover,
V: showTrafficAction,
A: showAllTrafficAction,
F: showDeviceLinkFlowsAction,
U: [unpin, 'Unpin node'],
R: [resetZoomPan, 'Reset zoom/pan'],
H: [cycleHoverMode, 'Cycle hover mode'],
V: [showTrafficAction, 'Show traffic'],
A: [showAllTrafficAction, 'Show all traffic'],
F: [showDeviceLinkFlowsAction, 'Show device link flows'],
esc: handleEscape
};
......@@ -322,7 +322,7 @@
});
}
function toggleHover(view) {
function cycleHoverMode(view) {
hoverMode++;
if (hoverMode === hoverModes.length) {
hoverMode = 0;
......