Skip to content

Commit

Permalink
Allow sub-pixel fill effect styles (e.g. hatches)
Browse files Browse the repository at this point in the history
  • Loading branch information
mbloch committed Oct 7, 2024
1 parent 1f6aded commit 5b0e185
Show file tree
Hide file tree
Showing 5 changed files with 175 additions and 67 deletions.
136 changes: 100 additions & 36 deletions src/gui/gui-context-menu.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,45 @@ import { internal, mapshaper, geom } from './gui-core';
import { El } from './gui-el';
import { saveFileContentToClipboard } from './gui-export-control';
import { deleteFeature } from './gui-drawing-utils';
import { GUI } from './gui-lib';

export function ContextMenu() {

var openMenu;

document.addEventListener('mousedown', function(e) {
if (e.target.classList.contains('contextmenu-item')) {
return; // don't close menu if clicking on a menu link
}
closeOpenMenu();
});

function closeOpenMenu() {
if (openMenu) {
openMenu.close();
openMenu = null;
}
}

export function openContextMenu(e, lyr, parent) {
var menu = new ContextMenu(parent);
closeOpenMenu();
menu.open(e, lyr);
}

export function ContextMenu(parentArg) {
var body = document.querySelector('body');
var menu = El('div').addClass('contextmenu rollover').appendTo(body);
var parent = parentArg || body;
// var menu = El('div').addClass('contextmenu rollover').appendTo(body);
var menu = El('div').addClass('contextmenu rollover').appendTo(parent);
var _open = false;
var _openCount = 0;
document.addEventListener('mousedown', close);

this.isOpen = function() {
return _open;
};

this.close = close;

function close() {
var count = _openCount;
if (!_open) return;
Expand All @@ -25,15 +52,18 @@ export function ContextMenu() {
}, 200);
}

function addMenuItem(label, func) {
var prefix = '•  ';

El('div')
function addMenuItem(label, func, prefixArg) {
var prefix = prefixArg === undefined ? '•  ' : prefixArg;
var item = El('div')
.appendTo(menu)
.addClass('contextmenu-item')
.html(prefix + label)
.on('click', func)
.show();

GUI.onClick(item, function(e) {
func();
closeOpenMenu();
});
}

function addMenuLabel(label) {
Expand All @@ -45,55 +75,89 @@ export function ContextMenu() {

this.open = function(e, lyr) {
var copyable = e.ids?.length;
if (lyr && !lyr.gui.geographic) return; // no popup for tabular data
if (_open) close();
menu.empty();

// menu contents
//
if (e.deleteVertex || e.deletePoint || copyable || e.deleteFeature) {
if (openMenu && openMenu != this) {
openMenu.close();
}
openMenu = this;

addMenuLabel('selection');
if (e.deleteVertex) {
addMenuItem('delete vertex', e.deleteVertex);
}
if (e.deletePoint) {
addMenuItem('delete point', e.deletePoint);
if (e.deleteLayer) {
addMenuItem('delete layer', e.deleteLayer, '');
}
if (e.selectLayer) {
addMenuItem('select layer', e.selectLayer, '');
}

if (lyr && lyr.gui.geographic) {
if (e.deleteVertex || e.deletePoint || copyable || e.deleteFeature) {

addMenuLabel('selection');
if (e.deleteVertex) {
addMenuItem('delete vertex', e.deleteVertex);
}
if (e.deletePoint) {
addMenuItem('delete point', e.deletePoint);
}
if (e.ids?.length) {
addMenuItem('copy as GeoJSON', copyGeoJSON);
}
if (e.deleteFeature) {
addMenuItem(getDeleteLabel(), e.deleteFeature);
}
}
if (e.ids?.length) {
addMenuItem('copy as GeoJSON', copyGeoJSON);

if (e.lonlat_coordinates) {
addMenuLabel('longitude, latitude');
addCoords(e.lonlat_coordinates);
}
if (e.deleteFeature) {
addMenuItem(getDeleteLabel(), e.deleteFeature);
if (e.projected_coordinates) {
addMenuLabel('x, y');
addCoords(e.projected_coordinates);
}
}

if (e.lonlat_coordinates) {
addMenuLabel('longitude, latitude');
addCoords(e.lonlat_coordinates);
}
if (e.projected_coordinates) {
addMenuLabel('x, y');
addCoords(e.projected_coordinates);
}

if (menu.node().childNodes.length === 0) {
return;
}

_open = true;
_openCount++;
var rspace = body.clientWidth - e.pageX;
var offs = getParentOffset();
var xoffs = 10;
if (rspace > 150) {
menu.css('left', e.pageX + xoffs + 'px');
menu.css('left', e.pageX - offs.left + xoffs + 'px');
menu.css('right', null);
} else {
menu.css('right', (body.clientWidth - e.pageX + xoffs) + 'px');
menu.css('right', (body.clientWidth - e.pageX - offs.left + xoffs) + 'px');
menu.css('left', null);
}
menu.css('top', (e.pageY - 15) + 'px');
menu.css('top', (e.pageY - offs.top - 15) + 'px');
menu.show();

_open = true;
_openCount++;

function getParentOffset() { // crossbrowser version
if (parent == body) {
return {top: 0, left: 0};
}

var box = parent.getBoundingClientRect();
var docEl = document.documentElement;

var scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
var scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;

var clientTop = docEl.clientTop || body.clientTop || 0;
var clientLeft = docEl.clientLeft || body.clientLeft || 0;

var top = box.top + scrollTop - clientTop;
var left = box.left + scrollLeft - clientLeft;

return { top: Math.round(top), left: Math.round(left) };
}

function getDeleteLabel() {
return 'delete ' + (lyr.geometry_type == 'point' ? 'point' : 'shape');
}
Expand Down
51 changes: 34 additions & 17 deletions src/gui/gui-layer-control.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { utils, internal } from './gui-core';
import { El } from './gui-el';
import { ClickText2 } from './gui-elements';
import { GUI } from './gui-lib';
import { openContextMenu } from './gui-context-menu';

export function LayerControl(gui) {
var model = gui.model;
Expand Down Expand Up @@ -216,7 +217,7 @@ export function LayerControl(gui) {
html = '<!-- ' + lyr.menu_id + '--><div class="' + classes + '">';
html += rowHTML('name', '<span class="layer-name colored-text dot-underline">' + formatLayerNameForDisplay(lyr.name) + '</span>', 'row1');
html += rowHTML('contents', describeLyr(lyr, dataset));
html += '<img class="close-btn" draggable="false" src="images/close.png">';
// html += '<img class="close-btn" draggable="false" src="images/close.png">';
if (opts.pinnable) {
html += '<img class="eye-btn black-eye" draggable="false" src="images/eye.png">';
html += '<img class="eye-btn green-eye" draggable="false" src="images/eye2.png">';
Expand Down Expand Up @@ -274,16 +275,35 @@ export function LayerControl(gui) {
function initMouseEvents2(entry, id, pinnable) {
initLayerDragging(entry, id);

// init delete button
GUI.onClick(entry.findChild('img.close-btn'), function(e) {
function deleteLayer() {
var target = findLayerById(id);
e.stopPropagation();
if (map.isVisibleLayer(target.layer)) {
// TODO: check for double map refresh after model.deleteLayer() below
setLayerPinning(target.layer, false);
}
model.deleteLayer(target.layer, target.dataset);
});
}

function selectLayer(closeMenu) {
var target = findLayerById(id);
// don't select if user is typing or dragging
if (GUI.textIsSelected() || dragging) return;
// undo any temporary hiding when layer is selected
target.layer.hidden = false;
if (!map.isActiveLayer(target.layer)) {
model.selectLayer(target.layer, target.dataset);
}
// close menu after a delay
if (closeMenu === true) setTimeout(function() {
gui.clearMode();
}, 230);
}

// init delete button
// GUI.onClick(entry.findChild('img.close-btn'), function(e) {
// e.stopPropagation();
// deleteLayer();
// });

if (pinnable) {
// init pin button
Expand Down Expand Up @@ -330,18 +350,15 @@ export function LayerControl(gui) {

// init click-to-select
GUI.onClick(entry, function() {
var target = findLayerById(id);
// don't select if user is typing or dragging
if (GUI.textIsSelected() || dragging) return;
// undo any temporary hiding when layer is selected
target.layer.hidden = false;
if (!map.isActiveLayer(target.layer)) {
model.selectLayer(target.layer, target.dataset);
}
// close menu after a delay
setTimeout(function() {
gui.clearMode();
}, 230);
selectLayer(true);
});

GUI.onContextClick(entry, function(e) {
e.deleteLayer = deleteLayer;
e.selectLayer = selectLayer;
// contextMenu.open(e);
// openContextMenu(e, null, entry.node())
openContextMenu(e, null, null);
});
}

Expand Down
22 changes: 21 additions & 1 deletion src/gui/gui-lib.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -97,16 +97,36 @@ GUI.blurActiveElement = function() {
};

// Filter out delayed click events, e.g. so users can highlight and copy text
// Filter out context menu clicks
GUI.onClick = function(el, cb) {
var time;
el.on('mousedown', function() {
time = +new Date();
});
el.on('mouseup', function(e) {
if (+new Date() - time < 300) cb(e);
if (looksLikeContextClick(e)) {
return;
}
if (+new Date() - time < 300) {
cb(e);
}
});
};

GUI.onContextClick = function(el, cb) {
el.on('mouseup', function(e) {
if (looksLikeContextClick(e)) {
e.stopPropagation();
e.preventDefault();
cb(e);
}
});
};

function looksLikeContextClick(e) {
return e.button > 1 || e.ctrlKey;
}

// tests if filename is a type that can be used
// GUI.isReadableFileType = function(filename) {
// return !!internal.guessInputFileType(filename) || internal.couldBeDsvFile(filename) ||
Expand Down
26 changes: 16 additions & 10 deletions src/svg/svg-hatch.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export function parseDashes(parts) {
var type = parts.shift();
var colors = [];
var background = parts.pop();
var spacing = parseInt(parts.pop());
var spacing = parseNum(parts.pop());
var tmp;
while (parts.length > 0) {
tmp = parts.pop();
Expand All @@ -55,11 +55,11 @@ export function parseDashes(parts) {
colors.push(tmp);
}
}
var width = parseInt(parts.pop());
var dashes = [parseInt(parts.pop()), parseInt(parts.pop())].reverse();
var width = parseNum(parts.pop());
var dashes = [parseNum(parts.pop()), parseNum(parts.pop())].reverse();
var rotation = 45;
if (parts.length > 0) {
rotation = parseInt(parts.pop());
rotation = parseNum(parts.pop());
}
if (parts.length > 0) {
return null;
Expand All @@ -84,10 +84,10 @@ export function parseHatches(parts) {
// 1px red 1px white 1px black
// -45deg 3 #eee 3 rgb(0,0,0)
var type = parts.shift();
var rot = parts.length % 2 == 1 ? parseInt(parts.shift()) : 45, // default is 45
var rot = parts.length % 2 == 1 ? parseNum(parts.shift()) : 45, // default is 45
colors = [], widths = [], a, b;
for (var i=0; i<parts.length; i+=2) {
widths.push(parseInt(parts[i]));
widths.push(parseNum(parts[i]));
colors.push(parts[i+1]);
}
if (Math.min.apply(null, widths) > 0 === false) return null;
Expand All @@ -101,7 +101,7 @@ export function parseHatches(parts) {
}

function isSize(str) {
return parseInt(str) > 0;
return parseNum(str) > 0;
}

export function parseDots(parts) {
Expand All @@ -114,11 +114,11 @@ export function parseDots(parts) {
var type = parts.shift();
var rot = 0;
if (isSize(parts[1])) { // if rotation is present, there are two numbers
rot = parseInt(parts.shift());
rot = parseNum(parts.shift());
}
var size = parseInt(parts.shift());
var size = parseNum(parts.shift());
var bg = parts.pop();
var spacing = parseInt(parts.pop());
var spacing = parseNum(parts.pop());
while (parts.length > 0) {
colors.push(parts.shift());
}
Expand All @@ -136,6 +136,12 @@ export function parseDots(parts) {
};
}

function parseNum(str) {
// return parseNum(str);
// support sub-pixel sizes
return parseFloat(str) || 0;
}

function splitPattern(str) {
// split apart space and comma-delimited tokens
// ... but don't split rgb(...) colors
Expand Down
Loading

0 comments on commit 5b0e185

Please sign in to comment.