Skip to content

Commit

Permalink
Merge pull request #3539 from openstreetmap/vertex-keyboard-nav
Browse files Browse the repository at this point in the history
Add vertex keyboard navigation
  • Loading branch information
bhousel authored Nov 7, 2016
2 parents 3ddebc9 + 774b3a0 commit 20406f8
Show file tree
Hide file tree
Showing 6 changed files with 253 additions and 48 deletions.
5 changes: 5 additions & 0 deletions css/map.css
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ g.point .shadow {
stroke-opacity: 0;
}

g.point.related:not(.selected) .shadow,
g.point.hover:not(.selected) .shadow {
stroke-opacity: 0.5;
}
Expand Down Expand Up @@ -104,7 +105,9 @@ g.vertex.vertex-hover {
display: block;
}

g.vertex.related:not(.selected) .shadow,
g.vertex.hover:not(.selected) .shadow,
g.midpoint.related:not(.selected) .shadow,
g.midpoint.hover:not(.selected) .shadow {
fill-opacity: 0.5;
}
Expand Down Expand Up @@ -144,6 +147,7 @@ path.shadow {
stroke-opacity: 0;
}

path.shadow.related:not(.selected),
path.shadow.hover:not(.selected) {
stroke-opacity: 0.4;
}
Expand Down Expand Up @@ -1612,6 +1616,7 @@ text.gpx {
stroke-width: 8;
}

.fill-wireframe path.shadow.related:not(.selected),
.fill-wireframe path.shadow.hover:not(.selected) {
stroke-opacity: 0.4;
}
Expand Down
55 changes: 30 additions & 25 deletions modules/lib/d3.keybinding.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,40 +58,45 @@ export function d3keybinding(namespace) {
return keybinding;
};

keybinding.on = function(code, callback, capture) {
var binding = {
event: {
keyCode: 0,
shiftKey: false,
ctrlKey: false,
altKey: false,
metaKey: false
},
capture: capture,
callback: callback
};

code = code.toLowerCase().match(/(?:(?:[^+⇧⌃⌥⌘])+|[⇧⌃⌥⌘]|\+\+|^\+$)/g);

for (var i = 0; i < code.length; i++) {
// Normalise matching errors
if (code[i] === '++') code[i] = '+';

if (code[i] in d3keybinding.modifierCodes) {
binding.event[d3keybinding.modifierProperties[d3keybinding.modifierCodes[code[i]]]] = true;
} else if (code[i] in d3keybinding.keyCodes) {
binding.event.keyCode = d3keybinding.keyCodes[code[i]];
keybinding.on = function(codes, callback, capture) {
var arr = [].concat(codes);
for (var i = 0; i < arr.length; i++) {
var code = arr[i];
var binding = {
event: {
keyCode: 0,
shiftKey: false,
ctrlKey: false,
altKey: false,
metaKey: false
},
capture: capture,
callback: callback
};

code = code.toLowerCase().match(/(?:(?:[^+⇧⌃⌥⌘])+|[⇧⌃⌥⌘]|\+\+|^\+$)/g);

for (var j = 0; j < code.length; j++) {
// Normalise matching errors
if (code[j] === '++') code[i] = '+';

if (code[j] in d3keybinding.modifierCodes) {
binding.event[d3keybinding.modifierProperties[d3keybinding.modifierCodes[code[j]]]] = true;
} else if (code[j] in d3keybinding.keyCodes) {
binding.event.keyCode = d3keybinding.keyCodes[code[j]];
}
}
}

bindings.push(binding);
bindings.push(binding);
}

return keybinding;
};

return keybinding;
}


d3keybinding.modifierCodes = {
// Shift key, ⇧
'⇧': 16, shift: 16,
Expand Down
207 changes: 200 additions & 7 deletions modules/modes/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ import { modeBrowse } from './browse';
import { modeDragNode } from './drag_node';
import * as Operations from '../operations/index';
import { uiRadialMenu, uiSelectionList } from '../ui/index';
import { utilEntityOrMemberSelector } from '../util/index';
import { uiCmd } from '../ui/cmd';
import { utilEntityOrMemberSelector, utilEntitySelector } from '../util/index';


var relatedParent;


export function modeSelect(context, selectedIDs) {
Expand All @@ -52,7 +56,9 @@ export function modeSelect(context, selectedIDs) {
inspector,
radialMenu,
newFeature = false,
suppressMenu = false;
suppressMenu = false,
follow = false;


var wrap = context.container()
.select('.inspector-wrap');
Expand All @@ -65,6 +71,56 @@ export function modeSelect(context, selectedIDs) {
}


// find the common parent ways for nextVertex, previousVertex
function commonParents() {
var graph = context.graph(),
commonParents = [];

for (var i = 0; i < selectedIDs.length; i++) {
var entity = context.hasEntity(selectedIDs[i]);
if (!entity || entity.geometry(graph) !== 'vertex') {
return []; // selection includes some not vertexes
}

var currParents = _.map(graph.parentWays(entity), 'id');
if (!commonParents.length) {
commonParents = currParents;
continue;
}

commonParents = _.intersection(commonParents, currParents);
if (!commonParents.length) {
return [];
}
}

return commonParents;
}


function singularParent() {
var parents = commonParents();
if (!parents) {
relatedParent = null;
return null;
}

// relatedParent is used when we visit a vertex with multiple
// parents, and we want to remember which parent line we started on.

if (parents.length === 1) {
relatedParent = parents[0]; // remember this parent for later
return relatedParent;
}

if (parents.indexOf(relatedParent) !== -1) {
return relatedParent; // prefer the previously seen parent
}

return parents[0];
}


function closeMenu() {
if (radialMenu) {
context.surface().call(radialMenu.close);
Expand Down Expand Up @@ -139,6 +195,13 @@ export function modeSelect(context, selectedIDs) {
};


mode.follow = function(_) {
if (!arguments.length) return follow;
follow = _;
return mode;
};


mode.enter = function() {

function update() {
Expand Down Expand Up @@ -173,17 +236,30 @@ export function modeSelect(context, selectedIDs) {


function selectElements(drawn) {
var entity = singular();
var surface = context.surface(),
entity = singular();

if (entity && context.geometry(entity.id) === 'relation') {
suppressMenu = true;
return;
}

var parent = singularParent();
if (parent) {
surface.selectAll('.related')
.classed('related', false);
surface.selectAll(utilEntitySelector([relatedParent]))
.classed('related', true);
}

var selection = context.surface()
.selectAll(utilEntityOrMemberSelector(selectedIDs, context.graph()));
.selectAll(utilEntityOrMemberSelector(selectedIDs, context.graph()));

if (selection.empty()) {
if (drawn) { // Exit mode if selected DOM elements have disappeared..
// Return to browse mode if selected DOM elements have
// disappeared because the user moved them out of view..
var source = d3.event && d3.event.type === 'zoom' && d3.event.sourceEvent;
if (drawn && source && (source.type === 'mousemove' || source.type === 'touchmove')) {
context.enter(modeBrowse(context));
}
} else {
Expand All @@ -200,6 +276,98 @@ export function modeSelect(context, selectedIDs) {
}


function firstVertex() {
d3.event.preventDefault();
var parent = singularParent();
if (parent) {
var way = context.entity(parent);
context.enter(
modeSelect(context, [way.first()]).follow(true)
);
}
}


function lastVertex() {
d3.event.preventDefault();
var parent = singularParent();
if (parent) {
var way = context.entity(parent);
context.enter(
modeSelect(context, [way.last()]).follow(true)
);
}
}


function previousVertex() {
d3.event.preventDefault();
var parent = singularParent();
if (!parent) return;

var way = context.entity(parent),
length = way.nodes.length,
curr = way.nodes.indexOf(selectedIDs[0]),
index = -1;

if (curr > 0) {
index = curr - 1;
} else if (way.isClosed()) {
index = length - 2;
}

if (index !== -1) {
context.enter(
modeSelect(context, [way.nodes[index]]).follow(true)
);
}
}


function nextVertex() {
d3.event.preventDefault();
var parent = singularParent();
if (!parent) return;

var way = context.entity(parent),
length = way.nodes.length,
curr = way.nodes.indexOf(selectedIDs[0]),
index = -1;

if (curr < length - 1) {
index = curr + 1;
} else if (way.isClosed()) {
index = 0;
}

if (index !== -1) {
context.enter(
modeSelect(context, [way.nodes[index]]).follow(true)
);
}
}


function nextParent() {
d3.event.preventDefault();
var parents = _.uniq(commonParents());
if (!parents || parents.length < 2) return;

var index = parents.indexOf(relatedParent);
if (index < 0 || index > parents.length - 2) {
relatedParent = parents[0];
} else {
relatedParent = parents[index + 1];
}

var surface = context.surface();
surface.selectAll('.related')
.classed('related', false);
surface.selectAll(utilEntitySelector([relatedParent]))
.classed('related', true);
}


behaviors.forEach(function(behavior) {
context.install(behavior);
});
Expand All @@ -211,6 +379,11 @@ export function modeSelect(context, selectedIDs) {
operations.unshift(Operations.operationDelete(selectedIDs, context));

keybinding
.on(['[','pgup'], previousVertex)
.on([']', 'pgdown'], nextVertex)
.on([uiCmd('⌘['), 'home'], firstVertex)
.on([uiCmd('⌘]'), 'end'], lastVertex)
.on(['\\', 'pause'], nextParent)
.on('⎋', esc, true)
.on('space', toggleMenu);

Expand Down Expand Up @@ -248,6 +421,18 @@ export function modeSelect(context, selectedIDs) {
positionMenu();
}

if (follow) {
var extent = geoExtent(),
graph = context.graph();
selectedIDs.forEach(function(id) {
var entity = context.entity(id);
extent._extend(entity.extent(graph));
});

var loc = extent.center();
context.map().centerEase(loc);
}

timeout = window.setTimeout(function() {
if (show) {
showMenu();
Expand Down Expand Up @@ -281,11 +466,19 @@ export function modeSelect(context, selectedIDs) {
.on('undone.select', null)
.on('redone.select', null);

context.surface()
.on('dblclick.select', null)
var surface = context.surface();

surface
.on('dblclick.select', null);

surface
.selectAll('.selected')
.classed('selected', false);

surface
.selectAll('.related')
.classed('related', false);

context.map().on('drawn.select', null);
context.ui().sidebar.hide();
};
Expand Down
Loading

0 comments on commit 20406f8

Please sign in to comment.