diff --git a/assets/icons/edge_selection-arrow.svg b/assets/icons/edge_selection-arrow.svg new file mode 100644 index 00000000..e4c4aafd --- /dev/null +++ b/assets/icons/edge_selection-arrow.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/icons/edge_selection.svg b/assets/icons/edge_selection.svg new file mode 100644 index 00000000..e5285e63 --- /dev/null +++ b/assets/icons/edge_selection.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/assets/icons/skewer_selection-arrow.svg b/assets/icons/skewer_selection-arrow.svg new file mode 100644 index 00000000..75695d0c --- /dev/null +++ b/assets/icons/skewer_selection-arrow.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/assets/icons/skewer_selection_black.svg b/assets/icons/skewer_selection_black.svg new file mode 100644 index 00000000..9cc1d200 --- /dev/null +++ b/assets/icons/skewer_selection_black.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/js/Aladin.js b/src/js/Aladin.js index 853f9335..c9d88500 100644 --- a/src/js/Aladin.js +++ b/src/js/Aladin.js @@ -74,6 +74,7 @@ import { SimbadPointer } from "./gui/Button/SimbadPointer"; import { ColorPicker } from "./gui/Button/ColorPicker"; import { GridEnabler } from "./gui/Button/GridEnabler"; import { CooFrame } from "./gui/Input/CooFrame"; +import { SelectionMode } from "./gui/Button/SelectionMode"; import { Circle } from "./shapes/Circle"; import { Ellipse } from "./shapes/Ellipse"; import { Polyline } from "./shapes/Polyline"; @@ -110,6 +111,8 @@ import { Polyline } from "./shapes/Polyline"; * CSS class for that button is `aladin-grid-control` * @property {boolean} [showSettingsControl=false] - Whether to show the settings control toolbar. * 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. * CSS class for that button is `aladin-colorPicker-control` * @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} [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} [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 {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. @@ -674,15 +678,19 @@ export let Aladin = (function () { widgets["simbad"] = simbad } - // Add the projection control // Add the coo grid control if (options.showCooGridControl) { let grid = new GridEnabler(this); widgets["grid"] = grid; } - // Add the projection control - // Add the coo grid control + // Show selection mode control + if (options.showSelectionModeControl) { + let selectionMode = new SelectionMode(this); + widgets["selectionMode"] = selectionMode + } + + // Add the color picker control if (options.showColorPickerControl) { let picker = new ColorPicker(this); widgets["picker"] = picker; @@ -698,6 +706,7 @@ export let Aladin = (function () { this.toolbar.add(name, widget); } + // Add the projection control if (options.showProjectionControl) { this.projBtn = new ProjectionActionButton(this); this.addUI(this.projBtn); @@ -784,6 +793,7 @@ export let Aladin = (function () { showSimbadPointerControl: false, showCooGridControl: false, showSettingsControl: false, + showSelectionModeControl: false, showColorPickerControl: false, // Share toolbar showShareControl: false, diff --git a/src/js/Selector.js b/src/js/Selector.js index bd46dbb9..cfac364d 100644 --- a/src/js/Selector.js +++ b/src/js/Selector.js @@ -27,15 +27,16 @@ import { PolySelect } from "./FiniteStateMachine/PolySelect"; import { LineSelect } from "./FiniteStateMachine/LineSelect"; import { RectSelect } from "./FiniteStateMachine/RectSelect"; import { ALEvent } from "./events/ALEvent"; +import { Utils } from './Utils'; /****************************************************************************** * Aladin Lite project - * + * * Class Selector - * + * * A selector - * + * * Author: Matthieu Baumann[CDS] - * + * *****************************************************************************/ export class Selector { @@ -121,7 +122,7 @@ export class Selector { continue; } sources = cat.getSources(); - + for (var l = 0; l < sources.length; l++) { s = sources[l]; @@ -172,4 +173,60 @@ export class Selector { 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 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; + } } \ No newline at end of file diff --git a/src/js/View.js b/src/js/View.js index 5fac86fb..83c7e664 100644 --- a/src/js/View.js +++ b/src/js/View.js @@ -273,6 +273,13 @@ export let View = (function () { this.selector = new Selector(this, this.options.selector); 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 this.imageLayers = new Map(); @@ -359,6 +366,10 @@ export let View = (function () { View.TOOL_SIMBAD_POINTER = 2; 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 View.DRAW_SOURCES_WHILE_DRAGGING = true; View.DRAW_MOCS_WHILE_DRAGGING = true; @@ -514,6 +525,13 @@ export let View = (function () { } 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 this.colorPickerTool.domElement.style.display = "none"; // in case we are in the selection mode @@ -546,6 +564,14 @@ export let View = (function () { 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) { if (this.catalogCanvas.style.cursor == cursor) { return; @@ -713,12 +739,10 @@ export let View = (function () { var showContextMenu = true; var xystart; - var handleSelect = function(xy, tolerance) { + var handleSelect = function(xy, tolerance, withModifierKey=false) { tolerance = tolerance || 5; var objs = view.closestObjects(xy.x, xy.y, tolerance); - view.unselectObjects(); - if (objs) { var objClickedFunction = view.aladin.callbacksByEventName['objectClicked']; var footprintClickedFunction = view.aladin.callbacksByEventName['footprintClicked']; @@ -757,11 +781,14 @@ export let View = (function () { if (shapes.length > 0) { objs.push(shapes) } - view.selectObjects(objs); + view.selectObjects(objs, withModifierKey); view.lastClickedObject = objs; } else { + + view.unselectObjects(); + // If there is a past clicked object if (view.lastClickedObject) { // 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; Utils.on(view.catalogCanvas, "mousedown touchstart", function (e) { e.stopPropagation(); @@ -918,6 +1034,7 @@ export let View = (function () { // reacting on 'click' rather on 'mouseup' is more reliable when panning the view Utils.on(view.catalogCanvas, "mouseup mouseout touchend touchcancel", function (e) { const xymouse = Utils.relMouseCoords(e); + const withModifierKey = e.ctrlKey || e.metaKey; ALEvent.CANVAS_EVENT.dispatchedTo(view.aladinDiv, { state: { @@ -1006,11 +1123,19 @@ export let View = (function () { const elapsedTime = Date.now() - touchStartTime; if (elapsedTime < 100) { view.updateObjectsLookup(); - handleSelect(xymouse, 15); + if (view.selectionMode === View.SELECTION_MODE_SKEWER) { + handleSkewerSelect(e, withModifierKey) + } else { + handleSelect(xymouse, 15, withModifierKey); + } } } } 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; } - // 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 (view.selectionMode === View.SELECTION_MODE_EDGE) { + // We're in edge selection mode for footprints. closestObjects() will find those footprints by closeness to a footprint edge. + // 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) { - var objHoveredFunction = view.aladin.callbacksByEventName['objectHovered']; - var footprintHoveredFunction = view.aladin.callbacksByEventName['footprintHovered']; + if (closests) { + hoverObjects(closests, xymouse); + } 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'); - - for (let o of closests) { - - 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(); - } + if (skewerObjects.length > 0) { + hoverObjects(skewerObjects, xymouse); + } else { + unhoverObjects(view, xymouse); } - // 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") { return; } @@ -1391,6 +1476,7 @@ export let View = (function () { view.displayHpxGrid = false; view.displayCatalog = false; + view.skewerEnabled = false; }; View.prototype.requestRedrawAtDate = function (date) { @@ -1636,6 +1722,9 @@ export let View = (function () { return imageData; }; + /** + * Unselects all currently selected objects. + */ View.prototype.unselectObjects = function() { if (this.manualSelection) { return; @@ -1654,11 +1743,24 @@ export let View = (function () { 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) { return; } + if (Array.isArray(selection) && withModifierKey) { + selection = this.computeModifiedSelection(selection) + } + // unselect the previous selection 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} pending - Array of array of objects to potentially add/remove from selection. + * @returns {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 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) { return this.wasm.getVisibleCells(norder); }; @@ -2429,3 +2638,5 @@ export let View = (function () { return View; })(); + + diff --git a/src/js/gui/Button/SelectionMode.js b/src/js/gui/Button/SelectionMode.js new file mode 100644 index 00000000..2b5ab0b6 --- /dev/null +++ b/src/js/gui/Button/SelectionMode.js @@ -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 . +// + +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
(' + 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.
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.
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); + }, + }, + ] + } + +} + diff --git a/tutorials/UI.md b/tutorials/UI.md index 00db33ed..66e20dcd 100644 --- a/tutorials/UI.md +++ b/tutorials/UI.md @@ -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-grid-control` targets the coordinate grid trigger 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-projection-control` targets the projection selector button * `aladin-stack-box` targets the stack box