mirror of
https://github.com/cds-astro/aladin-lite.git
synced 2026-06-12 11:01:37 -07:00
Squash all skewer_selection commits to date
This commit is contained in:
committed by
Matthieu Baumann
parent
44fdfad61c
commit
6ce7e9365d
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 24 24">
|
||||||
|
<!-- Generator: Adobe Illustrator 30.2.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 1) -->
|
||||||
|
<path d="M.7,12.3l11.3-4.9,11.3,4.9-11.1,7.1L.7,12.3ZM12,16.6l7.4-4.1-7.1-3.4-7.4,3.4s7.1,4.1,7.1,4.1Z"/>
|
||||||
|
<polygon points="4.9 1.8 5 8 6.3 9.1 7.6 8 7.6 1.8 4.9 1.8"/>
|
||||||
|
<polygon points="24 24 20.4 24 24 20.4 24 24"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 453 B |
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 24 24">
|
||||||
|
<!-- Generator: Adobe Illustrator 30.2.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 1) -->
|
||||||
|
<path d="M.7,12.3l11.3-4.9,11.3,4.9-11.1,7.1L.7,12.3ZM12,16.6l7.4-4.1-7.1-3.4-7.4,3.4,7.1,4.1Z"/>
|
||||||
|
<polygon points="4.9 1.8 5 8 6.3 9.1 7.6 8 7.6 1.8 4.9 1.8"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 395 B |
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 24 24">
|
||||||
|
<!-- Generator: Adobe Illustrator 30.2.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 1) -->
|
||||||
|
<polygon points="11.6 7.9 14 7.9 14 4.4 14 4.4 14 2.9 14 2.9 14 .8 11.6 .8 11.6 2.4 11.6 2.4 11.6 4 11.6 4 11.6 7.9"/>
|
||||||
|
<path d="M22.5,7.5l-7.5-3.2v1.7l3.5,1.7-6,3.6-6.5-3.5,4.7-2.2v-1.7L2.2,7.5l4.2,2.5-5,2.6,11.1,6.5,10.8-6.6-5.1-2.4,4.2-2.6s.1,0,.1,0ZM19.8,12.4l-7.2,4-7.7-4.1,2.9-1.5,4.7,2.8,4.3-2.6,3,1.4Z"/>
|
||||||
|
<polygon points="11.3 19.6 11.3 22.1 12.6 23.3 14 22.1 14 19.5 12.5 20.3 11.3 19.6"/>
|
||||||
|
<polygon points="24 24 20.4 24 24 20.4 24 24"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 685 B |
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 24 24">
|
||||||
|
<!-- Generator: Adobe Illustrator 30.2.1, SVG Export Plug-In . SVG Version: 2.1.1 Build 1) -->
|
||||||
|
<polygon points="11.6 7.9 14 7.9 14 4.4 14 4.4 14 2.9 14 2.9 14 .8 11.6 .8 11.6 2.4 11.6 2.4 11.6 4 11.6 4 11.6 7.9"/>
|
||||||
|
<path d="M22.5,7.5l-7.5-3.2v1.7c0,0,3.5,1.7,3.5,1.7l-6,3.6-6.5-3.5,4.7-2.2v-1.7S2.2,7.5,2.2,7.5l4.2,2.5-5,2.6,11.1,6.5,10.8-6.6-5.1-2.4,4.2-2.6h.1ZM19.8,12.4l-7.2,4-7.7-4.1,2.9-1.5,4.7,2.8,4.3-2.6s3,1.4,3,1.4Z"/>
|
||||||
|
<polygon points="11.3 19.6 11.3 22.1 12.6 23.3 14 22.1 14 19.5 12.5 20.3 11.3 19.6"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 655 B |
+13
-3
@@ -74,6 +74,7 @@ import { SimbadPointer } from "./gui/Button/SimbadPointer";
|
|||||||
import { ColorPicker } from "./gui/Button/ColorPicker";
|
import { ColorPicker } from "./gui/Button/ColorPicker";
|
||||||
import { GridEnabler } from "./gui/Button/GridEnabler";
|
import { GridEnabler } from "./gui/Button/GridEnabler";
|
||||||
import { CooFrame } from "./gui/Input/CooFrame";
|
import { CooFrame } from "./gui/Input/CooFrame";
|
||||||
|
import { SelectionMode } from "./gui/Button/SelectionMode";
|
||||||
import { Circle } from "./shapes/Circle";
|
import { Circle } from "./shapes/Circle";
|
||||||
import { Ellipse } from "./shapes/Ellipse";
|
import { Ellipse } from "./shapes/Ellipse";
|
||||||
import { Polyline } from "./shapes/Polyline";
|
import { Polyline } from "./shapes/Polyline";
|
||||||
@@ -110,6 +111,8 @@ import { Polyline } from "./shapes/Polyline";
|
|||||||
* CSS class for that button is `aladin-grid-control`
|
* CSS class for that button is `aladin-grid-control`
|
||||||
* @property {boolean} [showSettingsControl=false] - Whether to show the settings control toolbar.
|
* @property {boolean} [showSettingsControl=false] - Whether to show the settings control toolbar.
|
||||||
* CSS class for that button is `aladin-settings-control`
|
* CSS class for that button is `aladin-settings-control`
|
||||||
|
* @property {boolean} [showSelectionModeControl=false] - Whether to show the selection mode menu opener button.
|
||||||
|
* CSS class for that button is `aladin-selectionMode-control`
|
||||||
* @property {boolean} [showColorPickerControl=false] - Whether to show the color picker tool.
|
* @property {boolean} [showColorPickerControl=false] - Whether to show the color picker tool.
|
||||||
* CSS class for that button is `aladin-colorPicker-control`
|
* CSS class for that button is `aladin-colorPicker-control`
|
||||||
* @property {boolean} [showShareControl=false] - Whether to show the share control toolbar.
|
* @property {boolean} [showShareControl=false] - Whether to show the share control toolbar.
|
||||||
@@ -155,6 +158,7 @@ import { Polyline } from "./shapes/Polyline";
|
|||||||
* @property {boolean} [pixelateCanvas=true] - Whether to pixelate the canvas.
|
* @property {boolean} [pixelateCanvas=true] - Whether to pixelate the canvas.
|
||||||
* @property {boolean} [manualSelection=false] - When set to true, no selection will be performed, only events will be generated.
|
* @property {boolean} [manualSelection=false] - When set to true, no selection will be performed, only events will be generated.
|
||||||
* @property {string} [mode] - Interface theme, can be either 'dark' or 'light'. If not set, the mode will be retrieved from your browser preference or your localStorage.
|
* @property {string} [mode] - Interface theme, can be either 'dark' or 'light'. If not set, the mode will be retrieved from your browser preference or your localStorage.
|
||||||
|
* @property {string} [selectionMode="edge"] - When set to 'skewer' footprints are selected by clicking inside them. For 'edge' footprints are selected by clicking on their edges.
|
||||||
* @property {Object} [selector] - More options for the the selector.
|
* @property {Object} [selector] - More options for the the selector.
|
||||||
* @property {string} [selector.color] - Color of the selector, defaults to the color of the reticle. Can be a hex color or a function returning a hex color.
|
* @property {string} [selector.color] - Color of the selector, defaults to the color of the reticle. Can be a hex color or a function returning a hex color.
|
||||||
* @property {number} [selector.lineWidth=2] - Width of the selector line.
|
* @property {number} [selector.lineWidth=2] - Width of the selector line.
|
||||||
@@ -674,15 +678,19 @@ export let Aladin = (function () {
|
|||||||
widgets["simbad"] = simbad
|
widgets["simbad"] = simbad
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the projection control
|
|
||||||
// Add the coo grid control
|
// Add the coo grid control
|
||||||
if (options.showCooGridControl) {
|
if (options.showCooGridControl) {
|
||||||
let grid = new GridEnabler(this);
|
let grid = new GridEnabler(this);
|
||||||
widgets["grid"] = grid;
|
widgets["grid"] = grid;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the projection control
|
// Show selection mode control
|
||||||
// Add the coo grid control
|
if (options.showSelectionModeControl) {
|
||||||
|
let selectionMode = new SelectionMode(this);
|
||||||
|
widgets["selectionMode"] = selectionMode
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the color picker control
|
||||||
if (options.showColorPickerControl) {
|
if (options.showColorPickerControl) {
|
||||||
let picker = new ColorPicker(this);
|
let picker = new ColorPicker(this);
|
||||||
widgets["picker"] = picker;
|
widgets["picker"] = picker;
|
||||||
@@ -698,6 +706,7 @@ export let Aladin = (function () {
|
|||||||
this.toolbar.add(name, widget);
|
this.toolbar.add(name, widget);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add the projection control
|
||||||
if (options.showProjectionControl) {
|
if (options.showProjectionControl) {
|
||||||
this.projBtn = new ProjectionActionButton(this);
|
this.projBtn = new ProjectionActionButton(this);
|
||||||
this.addUI(this.projBtn);
|
this.addUI(this.projBtn);
|
||||||
@@ -784,6 +793,7 @@ export let Aladin = (function () {
|
|||||||
showSimbadPointerControl: false,
|
showSimbadPointerControl: false,
|
||||||
showCooGridControl: false,
|
showCooGridControl: false,
|
||||||
showSettingsControl: false,
|
showSettingsControl: false,
|
||||||
|
showSelectionModeControl: false,
|
||||||
showColorPickerControl: false,
|
showColorPickerControl: false,
|
||||||
// Share toolbar
|
// Share toolbar
|
||||||
showShareControl: false,
|
showShareControl: false,
|
||||||
|
|||||||
+62
-5
@@ -27,15 +27,16 @@ import { PolySelect } from "./FiniteStateMachine/PolySelect";
|
|||||||
import { LineSelect } from "./FiniteStateMachine/LineSelect";
|
import { LineSelect } from "./FiniteStateMachine/LineSelect";
|
||||||
import { RectSelect } from "./FiniteStateMachine/RectSelect";
|
import { RectSelect } from "./FiniteStateMachine/RectSelect";
|
||||||
import { ALEvent } from "./events/ALEvent";
|
import { ALEvent } from "./events/ALEvent";
|
||||||
|
import { Utils } from './Utils';
|
||||||
/******************************************************************************
|
/******************************************************************************
|
||||||
* Aladin Lite project
|
* Aladin Lite project
|
||||||
*
|
*
|
||||||
* Class Selector
|
* Class Selector
|
||||||
*
|
*
|
||||||
* A selector
|
* A selector
|
||||||
*
|
*
|
||||||
* Author: Matthieu Baumann[CDS]
|
* Author: Matthieu Baumann[CDS]
|
||||||
*
|
*
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
|
|
||||||
export class Selector {
|
export class Selector {
|
||||||
@@ -121,7 +122,7 @@ export class Selector {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
sources = cat.getSources();
|
sources = cat.getSources();
|
||||||
|
|
||||||
for (var l = 0; l < sources.length; l++) {
|
for (var l = 0; l < sources.length; l++) {
|
||||||
s = sources[l];
|
s = sources[l];
|
||||||
|
|
||||||
@@ -172,4 +173,60 @@ export class Selector {
|
|||||||
|
|
||||||
return objList;
|
return objList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves objects skewered by the cursor position or specified coordinates. An object is
|
||||||
|
* skewered if it is a shape that contains the specified coordinate, or is a catalog object within 3 pixels
|
||||||
|
* of the specified coordinate.
|
||||||
|
*
|
||||||
|
* If e is a mouse event (as opposed to an object with x and y values), the mouse coordinates
|
||||||
|
* of the event are used.
|
||||||
|
*
|
||||||
|
* This is implemented by simulating the interactive selection of a circle region with a 3 pixel radius)
|
||||||
|
* around the given coordinates and returns all catalog sources and overlay items intersecting with it.
|
||||||
|
*
|
||||||
|
* @param {Event|Object} e - Mouse coordinate via mouse event or object with x and y properties
|
||||||
|
* @param {Object} view - The Aladin View instance containing catalogs and overlays
|
||||||
|
* @returns {Array<Array>} Array of object lists, where each subarray contains objects
|
||||||
|
* from a single catalog or overlay that intersect with the selection region.
|
||||||
|
* Returns empty array if no objects are found.
|
||||||
|
*/
|
||||||
|
static getSkewerObjects(e, view) {
|
||||||
|
// Get the xy from the event
|
||||||
|
let xymouse;
|
||||||
|
if (e instanceof Event) {
|
||||||
|
xymouse = Utils.relMouseCoords(e);
|
||||||
|
} else {
|
||||||
|
xymouse = e;
|
||||||
|
}
|
||||||
|
const x = xymouse.x;
|
||||||
|
const y = xymouse.y;
|
||||||
|
|
||||||
|
// Perform a selection using a circle around x, y as if drawn by dragging 3 pixels.
|
||||||
|
const r2 = 9;
|
||||||
|
const r = Math.sqrt(r2);
|
||||||
|
|
||||||
|
let selectorObject = {
|
||||||
|
x, y, r,
|
||||||
|
label: 'circle',
|
||||||
|
contains(s) {
|
||||||
|
let dx = (s.x - x)
|
||||||
|
let dy = (s.y - y);
|
||||||
|
|
||||||
|
return dx*dx + dy*dy <= r2;
|
||||||
|
},
|
||||||
|
bbox() {
|
||||||
|
return {
|
||||||
|
x: x - r,
|
||||||
|
y: y - r,
|
||||||
|
w: 2*r,
|
||||||
|
h: 2*r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let objList = Selector.getObjects(selectorObject, view);
|
||||||
|
|
||||||
|
return objList;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+277
-66
@@ -273,6 +273,13 @@ export let View = (function () {
|
|||||||
this.selector = new Selector(this, this.options.selector);
|
this.selector = new Selector(this, this.options.selector);
|
||||||
this.manualSelection = (this.options && this.options.manualSelection) || false;
|
this.manualSelection = (this.options && this.options.manualSelection) || false;
|
||||||
|
|
||||||
|
// Selection mode
|
||||||
|
this.selectionMode = View.SELECTION_MODE_EDGE;
|
||||||
|
if (this.options.selectionMode === 'skewer') {
|
||||||
|
this.selectionMode = View.SELECTION_MODE_SKEWER;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// current reference image survey displayed
|
// current reference image survey displayed
|
||||||
this.imageLayers = new Map();
|
this.imageLayers = new Map();
|
||||||
|
|
||||||
@@ -359,6 +366,10 @@ export let View = (function () {
|
|||||||
View.TOOL_SIMBAD_POINTER = 2;
|
View.TOOL_SIMBAD_POINTER = 2;
|
||||||
View.TOOL_COLOR_PICKER = 3;
|
View.TOOL_COLOR_PICKER = 3;
|
||||||
|
|
||||||
|
// Selection modes
|
||||||
|
View.SELECTION_MODE_EDGE = 0;
|
||||||
|
View.SELECTION_MODE_SKEWER = 1;
|
||||||
|
|
||||||
// TODO: should be put as an option at layer level
|
// TODO: should be put as an option at layer level
|
||||||
View.DRAW_SOURCES_WHILE_DRAGGING = true;
|
View.DRAW_SOURCES_WHILE_DRAGGING = true;
|
||||||
View.DRAW_MOCS_WHILE_DRAGGING = true;
|
View.DRAW_MOCS_WHILE_DRAGGING = true;
|
||||||
@@ -514,6 +525,13 @@ export let View = (function () {
|
|||||||
}
|
}
|
||||||
|
|
||||||
View.prototype.setMode = function (mode, params) {
|
View.prototype.setMode = function (mode, params) {
|
||||||
|
|
||||||
|
// Undo the specialized cursors, if any.
|
||||||
|
const prevMode = this.mode;
|
||||||
|
if (prevMode == View.TOOL_SIMBAD_POINTER) {
|
||||||
|
this.catalogCanvas.classList.remove('aladin-sp-cursor');
|
||||||
|
}
|
||||||
|
|
||||||
// hide the picker tooltip
|
// hide the picker tooltip
|
||||||
this.colorPickerTool.domElement.style.display = "none";
|
this.colorPickerTool.domElement.style.display = "none";
|
||||||
// in case we are in the selection mode
|
// in case we are in the selection mode
|
||||||
@@ -546,6 +564,14 @@ export let View = (function () {
|
|||||||
ALEvent.MODE.dispatchedTo(this.aladin.aladinDiv, {mode});
|
ALEvent.MODE.dispatchedTo(this.aladin.aladinDiv, {mode});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
View.prototype.setSelectionMode = function (selectionMode) {
|
||||||
|
this.selectionMode = selectionMode;
|
||||||
|
};
|
||||||
|
|
||||||
|
View.prototype.getSelectionMode = function () {
|
||||||
|
return this.selectionMode;
|
||||||
|
};
|
||||||
|
|
||||||
View.prototype.setCursor = function (cursor) {
|
View.prototype.setCursor = function (cursor) {
|
||||||
if (this.catalogCanvas.style.cursor == cursor) {
|
if (this.catalogCanvas.style.cursor == cursor) {
|
||||||
return;
|
return;
|
||||||
@@ -713,12 +739,10 @@ export let View = (function () {
|
|||||||
var showContextMenu = true;
|
var showContextMenu = true;
|
||||||
var xystart;
|
var xystart;
|
||||||
|
|
||||||
var handleSelect = function(xy, tolerance) {
|
var handleSelect = function(xy, tolerance, withModifierKey=false) {
|
||||||
tolerance = tolerance || 5;
|
tolerance = tolerance || 5;
|
||||||
var objs = view.closestObjects(xy.x, xy.y, tolerance);
|
var objs = view.closestObjects(xy.x, xy.y, tolerance);
|
||||||
|
|
||||||
view.unselectObjects();
|
|
||||||
|
|
||||||
if (objs) {
|
if (objs) {
|
||||||
var objClickedFunction = view.aladin.callbacksByEventName['objectClicked'];
|
var objClickedFunction = view.aladin.callbacksByEventName['objectClicked'];
|
||||||
var footprintClickedFunction = view.aladin.callbacksByEventName['footprintClicked'];
|
var footprintClickedFunction = view.aladin.callbacksByEventName['footprintClicked'];
|
||||||
@@ -757,11 +781,14 @@ export let View = (function () {
|
|||||||
if (shapes.length > 0) {
|
if (shapes.length > 0) {
|
||||||
objs.push(shapes)
|
objs.push(shapes)
|
||||||
}
|
}
|
||||||
view.selectObjects(objs);
|
view.selectObjects(objs, withModifierKey);
|
||||||
|
|
||||||
view.lastClickedObject = objs;
|
view.lastClickedObject = objs;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
|
view.unselectObjects();
|
||||||
|
|
||||||
// If there is a past clicked object
|
// If there is a past clicked object
|
||||||
if (view.lastClickedObject) {
|
if (view.lastClickedObject) {
|
||||||
// TODO: do we need to keep that triggering ?
|
// TODO: do we need to keep that triggering ?
|
||||||
@@ -772,6 +799,95 @@ export let View = (function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a skewer-based selection of objects at the specified mouse position.
|
||||||
|
*
|
||||||
|
* @param {Event|Object} e - Mouse coordinate via mouse event or object with x and y properties
|
||||||
|
* @param {boolean} withModifierKey - If true, toggles selection (adds/removes from existing selection);
|
||||||
|
* if false, replaces current selection
|
||||||
|
*/
|
||||||
|
var handleSkewerSelect = function(e, withModifierKey) {
|
||||||
|
|
||||||
|
const objList = Selector.getSkewerObjects(e, view);
|
||||||
|
view.selectObjects(objList, withModifierKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Make the selected objects appear hovered and make all other objects appear unhovered,
|
||||||
|
* firing the appropriate callbacks for both cases.
|
||||||
|
*
|
||||||
|
* The also sets the cursor to a pointer to indicate that some object(s) would be
|
||||||
|
* select on click in the current mouse position.
|
||||||
|
*
|
||||||
|
* @param {Array} objects - Array of objects to apply hover state to
|
||||||
|
* @param {Object} xymouse - Mouse coordinates with x and y properties
|
||||||
|
*/
|
||||||
|
var hoverObjects = function(objects, xymouse) {
|
||||||
|
var objHoveredFunction = view.aladin.callbacksByEventName['objectHovered'];
|
||||||
|
var footprintHoveredFunction = view.aladin.callbacksByEventName['footprintHovered'];
|
||||||
|
|
||||||
|
view.setCursor('pointer');
|
||||||
|
|
||||||
|
for (let o of objects) {
|
||||||
|
|
||||||
|
if (typeof objHoveredFunction === 'function' && (!view.lastHoveredObject || !view.lastHoveredObject.includes(o))) {
|
||||||
|
var ret = objHoveredFunction(o, xymouse);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (o.isFootprint()) {
|
||||||
|
if (typeof footprintHoveredFunction === 'function' && (!view.lastHoveredObject || !view.lastHoveredObject.includes(o))) {
|
||||||
|
var ret = footprintHoveredFunction(o, xymouse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!view.lastHoveredObject || !view.lastHoveredObject.includes(o)) {
|
||||||
|
o.hover();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// unhover the objects in lastHoveredObjects that are not in closest anymore
|
||||||
|
if (view.lastHoveredObject) {
|
||||||
|
var objHoveredStopFunction = view.aladin.callbacksByEventName['objectHoveredStop'];
|
||||||
|
|
||||||
|
for (let lho of view.lastHoveredObject) {
|
||||||
|
if (!objects.includes(lho)) {
|
||||||
|
lho.unhover();
|
||||||
|
|
||||||
|
if (typeof objHoveredStopFunction === 'function') {
|
||||||
|
objHoveredStopFunction(lho, xymouse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
view.lastHoveredObject = objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes hover state from all previously hovered objects and fires the
|
||||||
|
* apropriate callbacks.
|
||||||
|
*
|
||||||
|
* The also resets the cursor to the default to indicate that no objects would be
|
||||||
|
* selected on click in the current mouse position.
|
||||||
|
*
|
||||||
|
* @param {Object} xymouse - Mouse coordinates with x and y properties
|
||||||
|
*/
|
||||||
|
var unhoverObjects = function(xymouse) {
|
||||||
|
view.setCursor('default');
|
||||||
|
if (view.lastHoveredObject) {
|
||||||
|
var objHoveredStopFunction = view.aladin.callbacksByEventName['objectHoveredStop'];
|
||||||
|
for (let lho of view.lastHoveredObject) {
|
||||||
|
lho.unhover();
|
||||||
|
|
||||||
|
if (typeof objHoveredStopFunction === 'function') {
|
||||||
|
objHoveredStopFunction(lho, xymouse);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
view.lastHoveredObject = null;
|
||||||
|
}
|
||||||
|
|
||||||
var touchStartTime;
|
var touchStartTime;
|
||||||
Utils.on(view.catalogCanvas, "mousedown touchstart", function (e) {
|
Utils.on(view.catalogCanvas, "mousedown touchstart", function (e) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
@@ -918,6 +1034,7 @@ export let View = (function () {
|
|||||||
// reacting on 'click' rather on 'mouseup' is more reliable when panning the view
|
// reacting on 'click' rather on 'mouseup' is more reliable when panning the view
|
||||||
Utils.on(view.catalogCanvas, "mouseup mouseout touchend touchcancel", function (e) {
|
Utils.on(view.catalogCanvas, "mouseup mouseout touchend touchcancel", function (e) {
|
||||||
const xymouse = Utils.relMouseCoords(e);
|
const xymouse = Utils.relMouseCoords(e);
|
||||||
|
const withModifierKey = e.ctrlKey || e.metaKey;
|
||||||
|
|
||||||
ALEvent.CANVAS_EVENT.dispatchedTo(view.aladinDiv, {
|
ALEvent.CANVAS_EVENT.dispatchedTo(view.aladinDiv, {
|
||||||
state: {
|
state: {
|
||||||
@@ -1006,11 +1123,19 @@ export let View = (function () {
|
|||||||
const elapsedTime = Date.now() - touchStartTime;
|
const elapsedTime = Date.now() - touchStartTime;
|
||||||
if (elapsedTime < 100) {
|
if (elapsedTime < 100) {
|
||||||
view.updateObjectsLookup();
|
view.updateObjectsLookup();
|
||||||
handleSelect(xymouse, 15);
|
if (view.selectionMode === View.SELECTION_MODE_SKEWER) {
|
||||||
|
handleSkewerSelect(e, withModifierKey)
|
||||||
|
} else {
|
||||||
|
handleSelect(xymouse, 15, withModifierKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
handleSelect(xymouse);
|
if (view.selectionMode === View.SELECTION_MODE_EDGE) {
|
||||||
|
handleSelect(xymouse, 5, withModifierKey);
|
||||||
|
} else {
|
||||||
|
handleSkewerSelect(e, withModifierKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1193,71 +1318,31 @@ export let View = (function () {
|
|||||||
lastMouseMovePos = pos;
|
lastMouseMovePos = pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
// closestObjects is very costly, we would like to not do it
|
if (view.selectionMode === View.SELECTION_MODE_EDGE) {
|
||||||
// especially if the objectHovered function is not defined.
|
// We're in edge selection mode for footprints. closestObjects() will find those footprints by closeness to a footprint edge.
|
||||||
var closests = view.closestObjects(xymouse.x, xymouse.y, 5);
|
// closestObjects is very costly, we would like to not do it
|
||||||
|
// especially if the objectHovered function is not defined.
|
||||||
|
var closests = view.closestObjects(xymouse.x, xymouse.y, 5);
|
||||||
|
|
||||||
if (closests) {
|
if (closests) {
|
||||||
var objHoveredFunction = view.aladin.callbacksByEventName['objectHovered'];
|
hoverObjects(closests, xymouse);
|
||||||
var footprintHoveredFunction = view.aladin.callbacksByEventName['footprintHovered'];
|
} else {
|
||||||
|
unhoverObjects(view, xymouse);
|
||||||
|
}
|
||||||
|
} else if (view.selectionMode === View.SELECTION_MODE_SKEWER) {
|
||||||
|
// We're in skewer mode. Let's see what would be selected.
|
||||||
|
const skewerTargetsByLayer = Selector.getSkewerObjects(e, view);
|
||||||
|
const skewerObjects = skewerTargetsByLayer.flat();
|
||||||
|
|
||||||
view.setCursor('pointer');
|
if (skewerObjects.length > 0) {
|
||||||
|
hoverObjects(skewerObjects, xymouse);
|
||||||
for (let o of closests) {
|
} else {
|
||||||
|
unhoverObjects(view, xymouse);
|
||||||
if (typeof objHoveredFunction === 'function' && (!view.lastHoveredObject || !view.lastHoveredObject.includes(o))) {
|
|
||||||
var ret = objHoveredFunction(o, xymouse);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (o.isFootprint()) {
|
|
||||||
if (typeof footprintHoveredFunction === 'function' && (!view.lastHoveredObject || !view.lastHoveredObject.includes(o))) {
|
|
||||||
var ret = footprintHoveredFunction(o, xymouse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!view.lastHoveredObject || !view.lastHoveredObject.includes(o)) {
|
|
||||||
o.hover();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// unhover the objects in lastHoveredObjects that are not in closest anymore
|
|
||||||
if (view.lastHoveredObject) {
|
|
||||||
var objHoveredStopFunction = view.aladin.callbacksByEventName['objectHoveredStop'];
|
|
||||||
|
|
||||||
for (let lho of view.lastHoveredObject) {
|
|
||||||
if (!closests.includes(lho)) {
|
|
||||||
lho.unhover();
|
|
||||||
|
|
||||||
if (typeof objHoveredStopFunction === 'function') {
|
|
||||||
objHoveredStopFunction(lho, xymouse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
view.lastHoveredObject = closests;
|
|
||||||
} else {
|
|
||||||
view.setCursor('default');
|
|
||||||
if (view.lastHoveredObject) {
|
|
||||||
var objHoveredStopFunction = view.aladin.callbacksByEventName['objectHoveredStop'];
|
|
||||||
|
|
||||||
/*if (typeof objHoveredStopFunction === 'function') {
|
|
||||||
// call callback function to notify we left the hovered object
|
|
||||||
var ret = objHoveredStopFunction(view.lastHoveredObject, xymouse);
|
|
||||||
}
|
|
||||||
|
|
||||||
view.lastHoveredObject.unhover();*/
|
|
||||||
for (let lho of view.lastHoveredObject) {
|
|
||||||
lho.unhover();
|
|
||||||
|
|
||||||
if (typeof objHoveredStopFunction === 'function') {
|
|
||||||
objHoveredStopFunction(lho, xymouse);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
view.lastHoveredObject = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
if (e.type === "mousemove") {
|
if (e.type === "mousemove") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -1391,6 +1476,7 @@ export let View = (function () {
|
|||||||
|
|
||||||
view.displayHpxGrid = false;
|
view.displayHpxGrid = false;
|
||||||
view.displayCatalog = false;
|
view.displayCatalog = false;
|
||||||
|
view.skewerEnabled = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
View.prototype.requestRedrawAtDate = function (date) {
|
View.prototype.requestRedrawAtDate = function (date) {
|
||||||
@@ -1636,6 +1722,9 @@ export let View = (function () {
|
|||||||
return imageData;
|
return imageData;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unselects all currently selected objects.
|
||||||
|
*/
|
||||||
View.prototype.unselectObjects = function() {
|
View.prototype.unselectObjects = function() {
|
||||||
if (this.manualSelection) {
|
if (this.manualSelection) {
|
||||||
return;
|
return;
|
||||||
@@ -1654,11 +1743,24 @@ export let View = (function () {
|
|||||||
this.requestRedraw();
|
this.requestRedraw();
|
||||||
}
|
}
|
||||||
|
|
||||||
View.prototype.selectObjects = function(selection) {
|
/**
|
||||||
|
* Selects the specified objects in the view.
|
||||||
|
*
|
||||||
|
* If withModifierKey is true, it modifies the existing selection (adds/removes).
|
||||||
|
* Otherwise, it replaces the current selection.
|
||||||
|
*
|
||||||
|
* @param {Array|Object} selection - The objects to select, either an array or a selector object.
|
||||||
|
* @param {boolean} [withModifierKey=false] - Whether to modify (versus replace) the existing selections.
|
||||||
|
*/
|
||||||
|
View.prototype.selectObjects = function(selection, withModifierKey=false) {
|
||||||
if (this.manualSelection) {
|
if (this.manualSelection) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(selection) && withModifierKey) {
|
||||||
|
selection = this.computeModifiedSelection(selection)
|
||||||
|
}
|
||||||
|
|
||||||
// unselect the previous selection
|
// unselect the previous selection
|
||||||
this.unselectObjects();
|
this.unselectObjects();
|
||||||
|
|
||||||
@@ -1736,6 +1838,113 @@ export let View = (function () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
View.prototype._getLayerForObj = function(obj) {
|
||||||
|
let layer = null;
|
||||||
|
if (obj.getCatalog) {
|
||||||
|
layer = obj.getCatalog()
|
||||||
|
} else {
|
||||||
|
layer = obj.overlay
|
||||||
|
}
|
||||||
|
return layer
|
||||||
|
}
|
||||||
|
|
||||||
|
View.prototype._copySelectionsToStage = function(selections, stage, overlays, exclude) {
|
||||||
|
for (const group of selections) {
|
||||||
|
for (const obj of group) {
|
||||||
|
const objExcluded = exclude.includes(obj)
|
||||||
|
if (!objExcluded) {
|
||||||
|
const layer = this._getLayerForObj(obj)
|
||||||
|
const idx = overlays.findIndex(item => item.uuid === layer.uuid);
|
||||||
|
if (idx >= 0) {
|
||||||
|
stage[idx].push(obj)
|
||||||
|
} else {
|
||||||
|
console.warn("Layer not found for selected obj: " + obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Computes the full set of selections that should result if the specified (pending) objects were
|
||||||
|
* selected with a modifier key pressed.
|
||||||
|
*
|
||||||
|
* If there are existing selections, it adds pending items that aren't selected,
|
||||||
|
* or removes all pending items if they are all already selected.
|
||||||
|
*
|
||||||
|
* Organizes selections by overlay layers as expected by selectObjects.
|
||||||
|
*
|
||||||
|
* @param {Array<Array>} pending - Array of array of objects to potentially add/remove from selection.
|
||||||
|
* @returns {Array<Array>} The modified selection array.
|
||||||
|
*/
|
||||||
|
View.prototype.computeModifiedSelection = function(pending) {
|
||||||
|
const current = this.selection
|
||||||
|
let modSelection = pending
|
||||||
|
if (current && current.length > 0) {
|
||||||
|
// There are some items already selected.
|
||||||
|
// We will be adding all the pending selections that are not already selected,
|
||||||
|
// UNLESS all of the pending selections are already selected, in which case
|
||||||
|
// they will all be unselected.
|
||||||
|
const toAdd = []
|
||||||
|
let mightRemove = []
|
||||||
|
modSelection = [] // We will build a new selection list from the current and pending selections
|
||||||
|
|
||||||
|
// stage will have one row for each existing overlay in which to collect all desired selections.
|
||||||
|
const overlays = this.aladin.getOverlays()
|
||||||
|
const stage = new Array(overlays.length).fill(null).map(() => []);
|
||||||
|
|
||||||
|
// Put already-selected items in mightRemove and not-yet-selected items in toAdd.
|
||||||
|
for (const group of pending) {
|
||||||
|
for (const obj of group) {
|
||||||
|
if (obj.isSelected) {
|
||||||
|
mightRemove.push(obj)
|
||||||
|
} else {
|
||||||
|
toAdd.push(obj)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// If there is anything in toAdd, then clear mightRemove since they will be left selected.
|
||||||
|
if (toAdd.length > 0) {
|
||||||
|
mightRemove = []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy current selections to stage except for anything in mightRemove
|
||||||
|
this._copySelectionsToStage(current, stage, overlays, mightRemove)
|
||||||
|
|
||||||
|
// Copy toAdd selections to stage
|
||||||
|
this._copySelectionsToStage([toAdd], stage, overlays, [])
|
||||||
|
|
||||||
|
// Build new modified selections list from stage.
|
||||||
|
// I can preserve the layer order, but I don't know how to preserve the order within
|
||||||
|
// layers without looping through all objects. Hopefully that order doesn't matter.
|
||||||
|
for (let i=0; i<stage.length; i++) {
|
||||||
|
if (stage[i].length > 0) {
|
||||||
|
// We have selected objects in this layer so will add the layer (or list of overlays) to modSelection
|
||||||
|
|
||||||
|
if (overlays[i].type === 'catalog') {
|
||||||
|
// The layer is a catalog so we add one entry for all its selections
|
||||||
|
const catLayer = []
|
||||||
|
modSelection.push(catLayer)
|
||||||
|
for (const obj of stage[i]) {
|
||||||
|
catLayer.push(obj)
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Assume it's a graphicalOverlay and add separate entries for each selected obj
|
||||||
|
// (That is the way objects in graphicalOverlays are currently selected. If they start
|
||||||
|
// being selected all in one list per overlay, then that can change here.)
|
||||||
|
for (const obj of stage[i]) {
|
||||||
|
modSelection.push([obj])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return modSelection;
|
||||||
|
}
|
||||||
|
|
||||||
View.prototype.getVisibleCells = function (norder) {
|
View.prototype.getVisibleCells = function (norder) {
|
||||||
return this.wasm.getVisibleCells(norder);
|
return this.wasm.getVisibleCells(norder);
|
||||||
};
|
};
|
||||||
@@ -2429,3 +2638,5 @@ export let View = (function () {
|
|||||||
|
|
||||||
return View;
|
return View;
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,149 @@
|
|||||||
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
// Copyright 2013 - UDS/CNRS
|
||||||
|
// The Aladin Lite program is distributed under the terms
|
||||||
|
// of the GNU Lesser General Public License version 3
|
||||||
|
// or (at your option) any later version.
|
||||||
|
//
|
||||||
|
// This file is part of Aladin Lite.
|
||||||
|
//
|
||||||
|
// Aladin Lite is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU Lesser General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// Aladin Lite is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU Lesser General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU Lesser General Public License
|
||||||
|
// along with Aladin Lite. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import { CtxMenuActionButtonOpener } from "./CtxMenuOpener";
|
||||||
|
import skewerSelectionIconArrow from '../../../../assets/icons/skewer_selection-arrow.svg';
|
||||||
|
import skewerSelectionIcon from '../../../../assets/icons/skewer_selection_black.svg';
|
||||||
|
import edgeSelectionIconArrow from '../../../../assets/icons/edge_selection-arrow.svg';
|
||||||
|
import edgeSelectionIcon from '../../../../assets/icons/edge_selection.svg';
|
||||||
|
import { View } from "../../View.js";
|
||||||
|
|
||||||
|
/******************************************************************************
|
||||||
|
* Aladin Lite project
|
||||||
|
*
|
||||||
|
* File gui/Button/SelectionMode.js
|
||||||
|
*
|
||||||
|
* Class representing a button for bringing up a menu for choosing selection mode.
|
||||||
|
* The appearance of the button changes depending on which selection mode (View.getSelectionMode())
|
||||||
|
* is active.
|
||||||
|
*
|
||||||
|
* There are two possible selection modes, Edge and Skewer, which affect how footprints are
|
||||||
|
* interactively selected.
|
||||||
|
*
|
||||||
|
* In Edge mode (View.SELECTION_MODE_EDGE), footprints are selected by clicking on their edges.
|
||||||
|
*
|
||||||
|
* In Skewer mode (View.SELECTION_MODE_SKEWER), footprints are selecting by clicking anywhere
|
||||||
|
* inside the footprint.
|
||||||
|
*
|
||||||
|
* Using a modifier key (Cmd on Mac, Ctrl otherwise) during select toggles the potential selections:
|
||||||
|
* - If any of the potential selections are not already selected, those objects are added to the current selections.
|
||||||
|
* - If all of the potential selections are already selected, then they are deselected.
|
||||||
|
*
|
||||||
|
* This uses the CSS class aladin-selectionMode-control.
|
||||||
|
*
|
||||||
|
* Author: Tom Donaldson (STScI)
|
||||||
|
*
|
||||||
|
*****************************************************************************/
|
||||||
|
export class SelectionMode extends CtxMenuActionButtonOpener {
|
||||||
|
/**
|
||||||
|
* Class representing a button for bringing up a menu for choosing selection mode.
|
||||||
|
* @param {Aladin} aladin - The aladin instance.
|
||||||
|
*/
|
||||||
|
constructor(aladin, options) {
|
||||||
|
|
||||||
|
// If we're on Mac, the modifier key will be Cmd instead of Ctrl.
|
||||||
|
let modifierKey = 'Ctrl';
|
||||||
|
const userAgent = window.navigator.userAgent.toLowerCase();
|
||||||
|
if (userAgent.indexOf('mac') > -1) {
|
||||||
|
modifierKey = 'Cmd';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the initial button icon based on the current View selection mode.
|
||||||
|
const initialMode = aladin.view.getSelectionMode();
|
||||||
|
let initialIcon = edgeSelectionIconArrow;
|
||||||
|
if (initialMode === View.SELECTION_MODE_SKEWER) {
|
||||||
|
initialIcon = skewerSelectionIconArrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
super({
|
||||||
|
icon: {
|
||||||
|
size: 'medium',
|
||||||
|
monochrome: true,
|
||||||
|
url: initialIcon,
|
||||||
|
},
|
||||||
|
classList: ['aladin-selectionMode-control'],
|
||||||
|
tooltip: {
|
||||||
|
content: 'Choose the selection mode<br />(' + modifierKey + ' for multiselect)',
|
||||||
|
position: { direction: 'top right', top: '10%', left: '80%' },
|
||||||
|
},
|
||||||
|
ctxMenu: undefined,
|
||||||
|
...options
|
||||||
|
}, aladin);
|
||||||
|
|
||||||
|
this.aladin = aladin;
|
||||||
|
this.modifierKey = modifierKey;
|
||||||
|
let ctxMenu = this._buildLayout()
|
||||||
|
this.update({ctxMenu})
|
||||||
|
}
|
||||||
|
|
||||||
|
setCustomIcon(icon) {
|
||||||
|
this.update({icon: {
|
||||||
|
size: 'medium',
|
||||||
|
monochrome: true,
|
||||||
|
url: icon
|
||||||
|
}})
|
||||||
|
}
|
||||||
|
|
||||||
|
_buildLayout() {
|
||||||
|
let self = this;
|
||||||
|
let aladin = this.aladin;
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
label: {
|
||||||
|
icon: {
|
||||||
|
url: skewerSelectionIcon,
|
||||||
|
monochrome: true,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
content: 'Click inside shapes to select.<br />Multiselect with ' + self.modifierKey + '.',
|
||||||
|
position: { direction: 'top right', left: '50%' },
|
||||||
|
},
|
||||||
|
content: "Skewer Selection",
|
||||||
|
},
|
||||||
|
action: (e) => {
|
||||||
|
aladin.view.setSelectionMode(View.SELECTION_MODE_SKEWER);
|
||||||
|
self.setCustomIcon(skewerSelectionIconArrow);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: {
|
||||||
|
icon: {
|
||||||
|
url: edgeSelectionIcon,
|
||||||
|
monochrome: true,
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
content: 'Click on objects to select.<br />Multiselect with ' + self.modifierKey + '.',
|
||||||
|
position: { direction: 'top right', left: '60%' },
|
||||||
|
},
|
||||||
|
content: "Edge Selection",
|
||||||
|
},
|
||||||
|
action: (e) => {
|
||||||
|
aladin.view.setSelectionMode(View.SELECTION_MODE_EDGE);
|
||||||
|
self.setCustomIcon(edgeSelectionIconArrow);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ There are distincts CSS class names for users wanting to personnalize the defaul
|
|||||||
* `aladin-simbadPointer-control` targets the Simbad pointer control button
|
* `aladin-simbadPointer-control` targets the Simbad pointer control button
|
||||||
* `aladin-grid-control` targets the coordinate grid trigger button
|
* `aladin-grid-control` targets the coordinate grid trigger button
|
||||||
* `aladin-settings-control` targets the settings menu opener button
|
* `aladin-settings-control` targets the settings menu opener button
|
||||||
|
* `aladin-selectionMode-control` targets the selection mode menu opener button
|
||||||
* `aladin-share-control` targets the share menu opener button
|
* `aladin-share-control` targets the share menu opener button
|
||||||
* `aladin-projection-control` targets the projection selector button
|
* `aladin-projection-control` targets the projection selector button
|
||||||
* `aladin-stack-box` targets the stack box
|
* `aladin-stack-box` targets the stack box
|
||||||
|
|||||||
Reference in New Issue
Block a user